[ruby-oci8-commit] [274] trunk/ruby-oci8: * ext/oci8/oci8.h, ext/oci8/ocidatetime.c, lib/oci8. rb.in,

nobody at rubyforge.org nobody at rubyforge.org
Sat Aug 2 10:21:56 EDT 2008


Revision: 274
Author:   kubo
Date:     2008-08-02 10:21:56 -0400 (Sat, 02 Aug 2008)

Log Message:
-----------
* ext/oci8/oci8.h, ext/oci8/ocidatetime.c, lib/oci8.rb.in,
  lib/oci8/datetime.rb: change binding code for timestamps
    and intervals. C side is a thin wrapper which converts Oracle's
    date, timestamp and interval values to ruby arrays and vise
    versa. Ruby side converts the arrays to ruby's Time, DateTime,
    Integer (for interval year to month) and Rational (for interval
    day to second).
* lib/oci8/oci8.rb: change mapping for binding date, timestamp and
    timestamp with local time zone from OCI8::BindType::DateTime
    to OCI8::BindType::Time.
* test/test_array_dml.rb, test/test_datetime.rb, test/test_dbi.rb,
  test/test_oci8.rb: fix test cases for a change above.

Modified Paths:
--------------
    trunk/ruby-oci8/ChangeLog
    trunk/ruby-oci8/ext/oci8/oci8.h
    trunk/ruby-oci8/ext/oci8/ocidatetime.c
    trunk/ruby-oci8/lib/oci8/oci8.rb
    trunk/ruby-oci8/lib/oci8.rb.in
    trunk/ruby-oci8/test/test_array_dml.rb
    trunk/ruby-oci8/test/test_datetime.rb
    trunk/ruby-oci8/test/test_dbi.rb
    trunk/ruby-oci8/test/test_oci8.rb

Added Paths:
-----------
    trunk/ruby-oci8/lib/oci8/datetime.rb

Modified: trunk/ruby-oci8/ChangeLog
===================================================================
--- trunk/ruby-oci8/ChangeLog	2008-07-17 15:01:30 UTC (rev 273)
+++ trunk/ruby-oci8/ChangeLog	2008-08-02 14:21:56 UTC (rev 274)
@@ -1,3 +1,17 @@
+2008-08-02  KUBO Takehiro  <kubo at jiubao.org>
+	* ext/oci8/oci8.h, ext/oci8/ocidatetime.c, lib/oci8.rb.in,
+	  lib/oci8/datetime.rb: change binding code for timestamps
+	    and intervals. C side is a thin wrapper which converts Oracle's
+	    date, timestamp and interval values to ruby arrays and vise
+	    versa. Ruby side converts the arrays to ruby's Time, DateTime,
+	    Integer (for interval year to month) and Rational (for interval
+	    day to second).
+	* lib/oci8/oci8.rb: change mapping for binding date, timestamp and
+	    timestamp with local time zone from OCI8::BindType::DateTime
+	    to OCI8::BindType::Time.
+	* test/test_array_dml.rb, test/test_datetime.rb, test/test_dbi.rb,
+	  test/test_oci8.rb: fix test cases for a change above.
+
 2008-07-17  KUBO Takehiro  <kubo at jiubao.org>
 	* Makefile: add a rdoc target to make rdoc document.
 	* custom-rdoc.rb: add a file which customizes rdoc to handle 

Modified: trunk/ruby-oci8/ext/oci8/oci8.h
===================================================================
--- trunk/ruby-oci8/ext/oci8/oci8.h	2008-07-17 15:01:30 UTC (rev 273)
+++ trunk/ruby-oci8/ext/oci8/oci8.h	2008-08-02 14:21:56 UTC (rev 274)
@@ -357,8 +357,10 @@
 
 /* ocidatetim.c */
 void Init_oci_datetime(void);
-VALUE oci8_make_datetime_from_ocidate(OCIDate *s);
-VALUE oci8_make_datetime_from_ocidatetime(OCIDateTime *s);
+VALUE oci8_make_ocidate(OCIDate *od);
+OCIDate *oci8_set_ocidate(OCIDate *od, VALUE val);
+VALUE oci8_make_ocidatetime(OCIDateTime *dttm);
+OCIDateTime *oci8_set_ocidatetime(OCIDateTime *dttm, VALUE val);
 VALUE oci8_make_interval_ym(OCIInterval *s);
 VALUE oci8_make_interval_ds(OCIInterval *s);
 

Modified: trunk/ruby-oci8/ext/oci8/ocidatetime.c
===================================================================
--- trunk/ruby-oci8/ext/oci8/ocidatetime.c	2008-07-17 15:01:30 UTC (rev 273)
+++ trunk/ruby-oci8/ext/oci8/ocidatetime.c	2008-08-02 14:21:56 UTC (rev 274)
@@ -10,120 +10,99 @@
  */
 #include "oci8.h"
 
-#ifdef RUBY_VM
-/* workaround NODE_ZSUPER BUG */
-static VALUE oci8_funcall0(VALUE obj, ID id)
+VALUE oci8_make_ocidate(OCIDate *od)
 {
-    static VALUE proc = Qfalse;
-    static ID id_call;
-    if (proc == Qfalse) {
-        proc = rb_eval_string("Proc.new {|obj, id| obj.send(id)}");
-        rb_global_variable(&proc);
-        id_call = rb_intern("call");
-    }
-    return rb_funcall(proc, id_call, 2, obj, ID2SYM(id));
+    return rb_ary_new3(6,
+                       INT2FIX(od->OCIDateYYYY),
+                       INT2FIX(od->OCIDateMM),
+                       INT2FIX(od->OCIDateDD),
+                       INT2FIX(od->OCIDateTime.OCITimeHH),
+                       INT2FIX(od->OCIDateTime.OCITimeMI),
+                       INT2FIX(od->OCIDateTime.OCITimeSS));
 }
-#else
-#define oci8_funcall0(obj, id) rb_funcall((obj), (id), 0)
-#endif
 
-static VALUE cDateTime;
-static ID id_parse;
-static ID id_civil;
-static ID id_to_r;
-static ID id_add;
-static ID id_mul;
-static ID id_div;
-static ID id_divmod;
-static ID id_less;
-static ID id_uminus;
-static ID id_year;
-static ID id_mon;
-static ID id_month;
-static ID id_mday;
-static ID id_day;
-static ID id_hour;
-static ID id_min;
-static ID id_sec;
-static ID id_sec_fraction;
-static ID id_offset;
-static ID id_utc_offset;
-static VALUE hour_base;
-static VALUE minute_base;
-static VALUE sec_base;
-static VALUE fsec_base;
-static VALUE fsec_mul;
-
-typedef struct {
-    oci8_bind_t obind;
-    ub4 type;
-} oci8_bind_dsc_t;
-
-void oci8_bind_dsc_free(oci8_base_t *base)
+OCIDate *oci8_set_ocidate(OCIDate *od, VALUE val)
 {
-    oci8_bind_t *obind = (oci8_bind_t *)base;
-    ub4 type = ((oci8_bind_dsc_t *)base)->type;
-    if (obind->valuep != NULL) {
-        ub4 idx = 0;
-        void **pp = (void**)obind->valuep;
+    long year, month, day, hour, minute, second;
 
-        do {
-            if (pp[idx] != NULL) {
-                OCIDescriptorFree(pp[idx], type);
-                pp[idx] = NULL;
-            }
-        } while (++idx < obind->maxar_sz);
+    Check_Type(val, T_ARRAY);
+    if (RARRAY_LEN(val) != 6) {
+        rb_raise(rb_eRuntimeError, "invalid array size %d", RARRAY_LEN(val));
     }
-    oci8_bind_free(base);
+    /* year */
+    year = NUM2LONG(RARRAY_PTR(val)[0]);
+    if (year < -4712 || 9999 < year) {
+        rb_raise(rb_eRuntimeError, "out of year range: %ld", year);
+    }
+    od->OCIDateYYYY = (sb2)year;
+    /* month */
+    month = NUM2LONG(RARRAY_PTR(val)[1]);
+    if (month < 0 || 12 < month) {
+        rb_raise(rb_eRuntimeError, "out of month range: %ld", month);
+    }
+    od->OCIDateMM = (ub1)month;
+    /* day */
+    day = NUM2LONG(RARRAY_PTR(val)[2]);
+    if (day < 0 || 31 < day) {
+        rb_raise(rb_eRuntimeError, "out of day range: %ld", day);
+    }
+    od->OCIDateDD = (ub1)day;
+    /* hour */
+    hour = NUM2LONG(RARRAY_PTR(val)[3]);
+    if (hour < 0 || 23 < hour) {
+        rb_raise(rb_eRuntimeError, "out of hour range: %ld", hour);
+    }
+    od->OCIDateTime.OCITimeHH = (ub1)hour;
+    /* minute */
+    minute = NUM2LONG(RARRAY_PTR(val)[4]);
+    if (minute < 0 || 59 < minute) {
+        rb_raise(rb_eRuntimeError, "out of minute range: %ld", minute);
+    }
+    od->OCIDateTime.OCITimeHH = (ub1)minute;
+    /* second */
+    second = NUM2LONG(RARRAY_PTR(val)[5]);
+    if (second < 0 || 59 < second) {
+        rb_raise(rb_eRuntimeError, "out of second range: %ld", second);
+    }
+    od->OCIDateTime.OCITimeSS = (ub1)second;
+    return od;
 }
 
-static VALUE make_datetime(sb2 year, ub1 month, ub1 day, ub1 hour, ub1 minute, ub1 sec, ub4 fsec, sb1 tz_hour, sb1 tz_minute)
+static VALUE bind_ocidate_get(oci8_bind_t *obind, void *data, void *null_struct)
 {
-    VALUE tz;
-    VALUE tzm;
+    return oci8_make_ocidate((OCIDate *)data);
+}
 
-    if (fsec != 0) {
-        /* make a DateTime object via String. */
-        char buf[60];
-        char sign = '+';
-
-        if (tz_hour < 0) {
-            sign = '-';
-            tz_hour = - tz_hour;
-            tz_minute = - tz_minute;
-        }
-        snprintf(buf, sizeof(buf), "%04d-%02d-%02dT%02d:%02d:%02d.%09d%c%02d:%02d",
-                 year, month, day, hour, minute, sec, fsec, sign,
-                 tz_hour, tz_minute);
-        return rb_funcall(cDateTime, id_parse, 1, rb_str_new2(buf));
-    }
-    if (tz_hour == 0 && tz_minute == 0) {
-        tz = INT2FIX(0);
-    } else {
-        /* tz = tz.to_r */
-        tz = rb_funcall(INT2FIX(tz_hour), id_to_r, 0);
-        /* tz = tz / 24 */
-        tz = rb_funcall(tz, id_div, 1, INT2FIX(24));
-        if (tz_minute != 0) {
-            /* tzm = tzm.to_r */
-            tzm = rb_funcall(INT2FIX(tz_minute), id_to_r, 0);
-            /* tzm = tzm / 1440 */
-            tzm = rb_funcall(tzm, id_div, 1, INT2FIX(1440));
-            /* tz = tz + tzm */
-            tz = rb_funcall(tz, id_add, 1, tzm);
-        }
-    }
-    return rb_funcall(cDateTime, id_civil, 7,
-                      INT2FIX(year), INT2FIX(month), INT2FIX(day),
-                      INT2FIX(hour), INT2FIX(minute), INT2FIX(sec), tz);
+static void bind_ocidate_set(oci8_bind_t *obind, void *data, void **null_structp, VALUE val)
+{
+    oci8_set_ocidate((OCIDate *)data, val);
 }
 
-VALUE oci8_make_datetime_from_ocidate(OCIDate *s)
+static void bind_ocidate_init(oci8_bind_t *obind, VALUE svc, VALUE val, VALUE length)
 {
-    return make_datetime(s->OCIDateYYYY, s->OCIDateMM, s->OCIDateDD, s->OCIDateTime.OCITimeHH, s->OCIDateTime.OCITimeMI, s->OCIDateTime.OCITimeSS, 0, 0, 0);
+    obind->value_sz = sizeof(OCIDate);
+    obind->alloc_sz = sizeof(OCIDate);
 }
 
-VALUE oci8_make_datetime_from_ocidatetime(OCIDateTime *s)
+static const oci8_bind_class_t bind_ocidate_class = {
+    {
+        NULL,
+        oci8_bind_free,
+        sizeof(oci8_bind_t)
+    },
+    bind_ocidate_get,
+    bind_ocidate_set,
+    bind_ocidate_init,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    SQLT_ODT,
+};
+
+#if defined RUNTIME_API_CHECK || ORACLE_CLIENT_VERSION >= 900
+
+VALUE oci8_make_ocitimestamp(OCIDateTime *dttm)
 {
     sb2 year;
     ub1 month;
@@ -135,265 +114,132 @@
     sb1 tz_hour;
     sb1 tz_minute;
     sword rv;
+    int have_tz;
 
-    oci_lc(OCIDateTimeGetDate(oci8_envhp, oci8_errhp, s, &year, &month, &day));
-    oci_lc(OCIDateTimeGetTime(oci8_envhp, oci8_errhp, s, &hour, &minute, &sec, &fsec));
-    rv = OCIDateTimeGetTimeZoneOffset(oci8_envhp, oci8_errhp, s, &tz_hour, &tz_minute);
-    if (rv != OCI_SUCCESS) {
-        tz_hour = tz_minute = 0;
-    }
-    return make_datetime(year, month, day, hour, minute, sec, fsec, tz_hour, tz_minute);
+    oci_lc(OCIDateTimeGetDate(oci8_envhp, oci8_errhp, dttm, &year, &month, &day));
+    oci_lc(OCIDateTimeGetTime(oci8_envhp, oci8_errhp, dttm, &hour, &minute, &sec, &fsec));
+    rv = OCIDateTimeGetTimeZoneOffset(oci8_envhp, oci8_errhp, dttm, &tz_hour, &tz_minute);
+    have_tz = (rv == OCI_SUCCESS);
+    return rb_ary_new3(9,
+                       INT2FIX(year),
+                       INT2FIX(month),
+                       INT2FIX(day),
+                       INT2FIX(hour),
+                       INT2FIX(minute),
+                       INT2FIX(sec),
+                       INT2FIX(fsec),
+                       have_tz ? INT2FIX(tz_hour) : Qnil,
+                       have_tz ? INT2FIX(tz_minute) : Qnil);
 }
 
-VALUE oci8_make_interval_ym(OCIInterval *s)
+OCIDateTime *oci8_set_ocitimestamp(OCIDateTime *dttm, VALUE val)
 {
-    sb4 year;
-    sb4 month;
-
-    oci_lc(OCIIntervalGetYearMonth(oci8_envhp, oci8_errhp, &year, &month, s));
-    return INT2NUM(year * 12 + month);
-}
-
-VALUE oci8_make_interval_ds(OCIInterval *s)
-{
-    sb4 day;
-    sb4 hour;
-    sb4 minute;
-    sb4 sec;
-    sb4 fsec;
-    VALUE days;
-
-    oci_lc(OCIIntervalGetDaySecond(oci8_envhp, oci8_errhp, &day, &hour, &minute, &sec, &fsec, s));
-    days = INT2NUM(day);
-    if (hour != 0) {
-        days = rb_funcall(days, id_add, 1, rb_funcall(INT2FIX(hour), id_div, 1, hour_base));
-    }
-    if (minute != 0) {
-        days = rb_funcall(days, id_add, 1, rb_funcall(INT2FIX(minute), id_div, 1, minute_base));
-    }
-    if (sec != 0) {
-        days = rb_funcall(days, id_add, 1, rb_funcall(INT2FIX(sec), id_div, 1, sec_base));
-    }
-    if (fsec != 0) {
-        days = rb_funcall(days, id_add, 1, rb_funcall(INT2FIX(fsec), id_div, 1, fsec_base));
-    }
-    return days;
-}
-
-/*
- * Document-class: OCI8::BindType::DateTime
- *
- * This is a helper class to bind ruby's
- * DateTime[http://www.ruby-doc.org/core/classes/DateTime.html]
- * object as Oracle's <tt>TIMESTAMP WITH TIME ZONE</tt> datatype.
- *
- * == Select
- *
- * The fetched value for a <tt>DATE</tt>, <tt>TIMESTAMP</tt>, <tt>TIMESTAMP WITH
- * TIME ZONE</tt> or <tt>TIMESTAMP WITH LOCAL TIME ZONE</tt> column
- * is a DateTime[http://www.ruby-doc.org/core/classes/DateTime.html].
- * The time zone part is a session time zone if the Oracle datatype doesn't
- * have time zone information. The session time zone is the client machine's
- * time zone by default.
- *
- * You can change the session time zone by executing the following SQL.
- *
- *   ALTER SESSION SET TIME_ZONE='-05:00'
- *
- * == Bind
- *
- * To bind a DateTime[http://www.ruby-doc.org/core/classes/DateTime.html]
- * value implicitly:
- *
- *   conn.exec("INSERT INTO lunar_landings(ship_name, landing_time) VALUES(:1, :2)",
- *             'Apollo 11',
- *             DateTime.parse('1969-7-20 20:17:40 00:00'))
- *
- * The bind variable <code>:2</code> is bound as <tt>TIMESTAMP WITH TIME ZONE</tt> on Oracle.
- *
- * To bind explicitly:
- *
- *   cursor = conn.exec("INSERT INTO lunar_landings(ship_name, landing_time) VALUES(:1, :2)")
- *   cursor.bind_param(':1', nil, String, 60)
- *   cursor.bind_param(':2', nil, DateTime)
- *   [['Apollo 11', DateTime.parse('1969-07-20 20:17:40 00:00'))],
- *    ['Apollo 12', DateTime.parse('1969-11-19 06:54:35 00:00'))],
- *    ['Apollo 14', DateTime.parse('1971-02-05 09:18:11 00:00'))],
- *    ['Apollo 15', DateTime.parse('1971-07-30 22:16:29 00:00'))],
- *    ['Apollo 16', DateTime.parse('1972-04-21 02:23:35 00:00'))],
- *    ['Apollo 17', DateTime.parse('1972-12-11 19:54:57 00:00'))]
- *   ].each do |ship_name, landing_time|
- *     cursor[':1'] = ship_name
- *     cursor[':2'] = landing_time
- *     cursor.exec
- *   end
- *   cursor.close
- *
- * On setting a object to the bind variable, you can use any object
- * which has at least three instance methods _year_, _mon_ (or _month_)
- * and _mday_ (or _day_). If the object responses to _hour_, _min_,
- * _sec_ or _sec_fraction_, the responsed values are used for hour,
- * minute, second or fraction of a second respectively.
- * If not, zeros are set. If the object responses to _offset_ or
- * _utc_offset_, it is used for time zone. If not, the session time
- * zone is used.
- *
- * The acceptable value are listed below.
- * _year_:: -4712 to 9999 [excluding year 0]
- * _mon_ (or _month_):: 0 to 12
- * _mday_ (or _day_):: 0 to 31 [depends on the month]
- * _hour_:: 0 to 23
- * _min_:: 0 to 59
- * _sec_:: 0 to 59
- * _sec_fraction_:: 0 to (999_999_999.to_r / (24*60*60* 1_000_000_000)) [999,999,999 nanoseconds]
- * _offset_:: (-12.to_r / 24) to (14.to_r / 24) [-12:00 to +14:00]
- * _utc_offset_:: -12*3600 <= utc_offset <= 24*3600 [-12:00 to +14:00]
- *
- * The output value of the bind varible is always a
- * DateTime[http://www.ruby-doc.org/core/classes/DateTime.html].
- *
- *   cursor = conn.exec("BEGIN :ts := current_timestamp; END")
- *   cursor.bind_param(:ts, nil, DateTime)
- *   cursor.exec
- *   cursor[:ts] # => a DateTime.
- *   cursor.close
- *
- */
-static VALUE bind_datetime_get(oci8_bind_t *obind, void *data, void *null_struct)
-{
-    OCIDateTime **datepp = (OCIDateTime **)data;
-
-    return oci8_make_datetime_from_ocidatetime(*datepp);
-}
-
-static void bind_datetime_set(oci8_bind_t *obind, void *data, void **null_structp, VALUE val)
-{
-    OCIDateTime **datepp = (OCIDateTime **)data;
-    int ival;
-    sb2 year;
-    ub1 month;
-    ub1 day;
-    ub1 hour;
-    ub1 minute;
-    ub1 sec;
-    ub4 fsec;
+    long year;
+    long month;
+    long day;
+    long hour;
+    long minute;
+    long sec;
+    long fsec;
     char tz_str[32];
 
+    Check_Type(val, T_ARRAY);
+    if (RARRAY_LEN(val) != 9) {
+        rb_raise(rb_eRuntimeError, "invalid array size %d", RARRAY_LEN(val));
+    }
     /* year */
-    ival = NUM2INT(rb_funcall(val, id_year, 0));
-    if (ival < -4712 || 9999 < ival) {
-        rb_raise(rb_eRuntimeError, "out of year range: %d", ival);
+    year = NUM2LONG(RARRAY_PTR(val)[0]);
+    if (year < -4712 || 9999 < year) {
+        rb_raise(rb_eRuntimeError, "out of year range: %ld", year);
     }
-    year = (sb2)ival;
     /* month */
-    if (rb_respond_to(val, id_mon)) {
-        ival = NUM2INT(rb_funcall(val, id_mon, 0));
-    } else if (rb_respond_to(val, id_month)) {
-        ival = NUM2INT(rb_funcall(val, id_month, 0));
-    } else {
-        rb_raise(rb_eRuntimeError, "expect Time, Date or DateTime but %s",
-                 rb_class2name(CLASS_OF(val)));
+    month = NUM2LONG(RARRAY_PTR(val)[1]);
+    if (month < 0 || 12 < month) {
+        rb_raise(rb_eRuntimeError, "out of month range: %ld", month);
     }
-    if (ival < 0 || 12 < ival) {
-        rb_raise(rb_eRuntimeError, "out of month range: %d", ival);
-    }
-    month = (ub1)ival;
     /* day */
-    if (rb_respond_to(val, id_mday)) {
-        ival = NUM2INT(rb_funcall(val, id_mday, 0));
-    } else if (rb_respond_to(val, id_day)) {
-        ival = NUM2INT(rb_funcall(val, id_day, 0));
-    } else {
-        rb_raise(rb_eRuntimeError, "expect Time, Date or DateTime but %s",
-                 rb_class2name(CLASS_OF(val)));
+    day = NUM2LONG(RARRAY_PTR(val)[2]);
+    if (day < 0 || 31 < day) {
+        rb_raise(rb_eRuntimeError, "out of day range: %ld", day);
     }
-    if (ival < 0 || 31 < ival) {
-        rb_raise(rb_eRuntimeError, "out of day range: %d", ival);
-    }
-    day = (ub1)ival;
     /* hour */
-    if (rb_respond_to(val, id_hour)) {
-        ival = NUM2INT(oci8_funcall0(val, id_hour));
-    } else {
-        ival = 0;
+    hour = NUM2LONG(RARRAY_PTR(val)[3]);
+    if (hour < 0 || 23 < hour) {
+        rb_raise(rb_eRuntimeError, "out of hour range: %ld", hour);
     }
-    if (ival < 0 || 24 < ival) {
-        rb_raise(rb_eRuntimeError, "out of hour range: %d", ival);
-    }
-    hour = (ub1)ival;
     /* minute */
-    if (rb_respond_to(val, id_min)) {
-        ival = NUM2INT(oci8_funcall0(val, id_min));
-        if (ival < 0 || 60 < ival) {
-            rb_raise(rb_eRuntimeError, "out of minute range: %d", ival);
-        }
-        minute = (ub1)ival;
-    } else {
-        minute = 0;
+    minute = NUM2LONG(RARRAY_PTR(val)[4]);
+    if (minute < 0 || 60 < minute) {
+        rb_raise(rb_eRuntimeError, "out of minute range: %ld", minute);
     }
     /* second */
-    if (rb_respond_to(val, id_sec)) {
-        ival = NUM2INT(oci8_funcall0(val, id_sec));
-        if (ival < 0 || 61 < ival) {
-            rb_raise(rb_eRuntimeError, "out of second range: %d", ival);
-        }
-        sec = (ub1)ival;
-    } else {
-        sec = 0;
+    sec = NUM2LONG(RARRAY_PTR(val)[5]);
+    if (sec < 0 || 60 < sec) {
+        rb_raise(rb_eRuntimeError, "out of second range: %ld", sec);
     }
     /* sec_fraction */
-    if (rb_respond_to(val, id_sec_fraction)) {
-        VALUE fs = oci8_funcall0(val, id_sec_fraction);
-        if (RTEST(rb_funcall(fs, id_less, 1, INT2FIX(0)))) {
-            /* if fs < 0 */
-            rb_raise(rb_eRangeError, "sec_fraction is less then zero.");
-        }
-        fs = rb_funcall(fs, id_mul, 1, fsec_mul);
-        if (RTEST(rb_funcall(INT2FIX(1000000000), id_less, 1, fs))) {
-            /* if 1000000000 < fs */
-            rb_raise(rb_eRangeError, "sec_fraction is greater than or equals to one second.");
-        }
-        fsec = NUM2UINT(fs);
-    } else {
-        fsec = 0;
+    fsec = NUM2LONG(RARRAY_PTR(val)[6]);
+    if (fsec < 0 || 1000000000 < fsec) {
+        rb_raise(rb_eRuntimeError, "out of sec_fraction range: %ld", fsec);
     }
     /* time zone */
-    if (rb_respond_to(val, id_offset)) {
-        VALUE of = oci8_funcall0(val, id_offset);
-        char sign = '+';
-        ival = NUM2INT(rb_funcall(of, id_mul, 1, INT2FIX(1440)));
-        if (ival < 0) {
-            sign = '-';
-            ival = - ival;
-        }
-        sprintf(tz_str, "%c%02d:%02d", sign, ival / 60, ival % 60);
-    } else if (rb_respond_to(val, id_utc_offset)) {
-        char sign = '+';
-        ival = NUM2INT(rb_funcall(val, id_utc_offset, 0));
-        if (ival < 0) {
-            sign = '-';
-            ival = -ival;
-        }
-        ival /= 60;
-        sprintf(tz_str, "%c%02d:%02d", sign, ival / 60, ival % 60);
-    } else {
+    if (NIL_P(RARRAY_PTR(val)[7]) && NIL_P(RARRAY_PTR(val)[8])) {
         /* use session timezone. */
         tz_str[0] = '\0';
+    } else {
+        sprintf(tz_str, "%+02ld:%02ld",
+                NUM2LONG(RARRAY_PTR(val)[7]),
+                NUM2LONG(RARRAY_PTR(val)[8]));
     }
-    oci_lc(OCIDateTimeConstruct(oci8_envhp, oci8_errhp, *datepp,
-                                year,
-                                month,
-                                day,
-                                hour,
-                                minute,
-                                sec,
-                                fsec,
+    /* construct */
+    oci_lc(OCIDateTimeConstruct(oci8_envhp, oci8_errhp, dttm,
+                                (sb2)year,
+                                (ub1)month,
+                                (ub1)day,
+                                (ub1)hour,
+                                (ub1)minute,
+                                (ub1)sec,
+                                (ub4)fsec,
                                 (OraText *)tz_str,
                                 strlen(tz_str)));
+    return dttm;
 }
 
-static void bind_datetime_init(oci8_bind_t *obind, VALUE svc, VALUE val, VALUE length)
+typedef struct {
+    oci8_bind_t obind;
+    ub4 type;
+} oci8_bind_dsc_t;
+
+static void oci8_bind_dsc_free(oci8_base_t *base)
 {
+    oci8_bind_t *obind = (oci8_bind_t *)base;
+    ub4 type = ((oci8_bind_dsc_t *)base)->type;
+    if (obind->valuep != NULL) {
+        ub4 idx = 0;
+        void **pp = (void**)obind->valuep;
+
+        do {
+            if (pp[idx] != NULL) {
+                OCIDescriptorFree(pp[idx], type);
+                pp[idx] = NULL;
+            }
+        } while (++idx < obind->maxar_sz);
+    }
+    oci8_bind_free(base);
+}
+
+static VALUE bind_ocitimestamp_get(oci8_bind_t *obind, void *data, void *null_struct)
+{
+    return oci8_make_ocitimestamp(*(OCIDateTime **)data);
+}
+
+static void bind_ocitimestamp_set(oci8_bind_t *obind, void *data, void **null_structp, VALUE val)
+{
+    oci8_set_ocitimestamp(*(OCIDateTime **)data, val);
+}
+
+static void bind_ocitimestamp_init(oci8_bind_t *obind, VALUE svc, VALUE val, VALUE length)
+{
     oci8_bind_dsc_t *obind_dsc = (oci8_bind_dsc_t *)obind;
 
     obind->value_sz = sizeof(OCIDateTime *);
@@ -401,7 +247,7 @@
     obind_dsc->type = OCI_DTYPE_TIMESTAMP_TZ;
 }
 
-static void bind_datetime_init_elem(oci8_bind_t *obind, VALUE svc)
+static void bind_ocitimestamp_init_elem(oci8_bind_t *obind, VALUE svc)
 {
     ub4 idx = 0;
     sword rv;
@@ -413,76 +259,97 @@
     } while (++idx < obind->maxar_sz);
 }
 
-/*
- * Document-class: OCI8::BindType::IntervalYM
- *
- * This is a helper class to bind ruby's
- * Integer[http://www.ruby-doc.org/core/classes/Integer.html]
- * object as Oracle's <tt>INTERVAL YEAR TO MONTH</tt> datatype.
- *
- * == Select
- *
- * The fetched value for a <tt>INTERVAL YEAR TO MONTH</tt> column
- * is an Integer[http://www.ruby-doc.org/core/classes/Integer.html]
- * which means the months between two timestamps.
- *
- * == Bind
- *
- * You cannot bind as <tt>INTERVAL YEAR TO MONTH</tt> implicitly.
- * It must be bound explicitly with :interval_ym.
- *
- *   # output bind variable
- *   cursor = conn.parse(<<-EOS)
- *     BEGIN
- *       :interval := (:ts1 - :ts2) YEAR TO MONTH;
- *     END;
- *   EOS
- *   cursor.bind_param(:interval, nil, :interval_ym)
- *   cursor.bind_param(:ts1, DateTime.parse('1969-11-19 06:54:35 00:00'))
- *   cursor.bind_param(:ts2, DateTime.parse('1969-07-20 20:17:40 00:00'))
- *   cursor.exec
- *   cursor[:interval] # => 4 (months)
- *   cursor.close
- *
- *   # input bind variable
- *   cursor = conn.parse(<<-EOS)
- *     BEGIN
- *       :ts1 := :ts2 + :interval;
- *     END;
- *   EOS
- *   cursor.bind_param(:ts1, nil, DateTime)
- *   cursor.bind_param(:ts2, Date.parse('1969-11-19'))
- *   cursor.bind_param(:interval, 4, :interval_ym)
- *   cursor.exec
- *   cursor[:ts1].strftime('%Y-%m-%d') # => 1970-03-19
- *   cursor.close
- *
- */
-static VALUE bind_interval_ym_get(oci8_bind_t *obind, void *data, void *null_struct)
+static const oci8_bind_class_t bind_ocitimestamp_class = {
+    {
+        NULL,
+        oci8_bind_dsc_free,
+        sizeof(oci8_bind_dsc_t)
+    },
+    bind_ocitimestamp_get,
+    bind_ocitimestamp_set,
+    bind_ocitimestamp_init,
+    bind_ocitimestamp_init_elem,
+    NULL,
+    NULL,
+    NULL,
+    SQLT_TIMESTAMP_TZ
+};
+
+VALUE oci8_make_ociinterval_ym(OCIInterval *s)
 {
-    OCIInterval **intvlpp = (OCIInterval **)data;
+    sb4 year;
+    sb4 month;
 
-    return oci8_make_interval_ym(*intvlpp);
+    oci_lc(OCIIntervalGetYearMonth(oci8_envhp, oci8_errhp, &year, &month, s));
+    return rb_ary_new3(2, INT2FIX(year), INT2FIX(month));
 }
 
-static void bind_interval_ym_set(oci8_bind_t *obind, void *data, void **null_structp, VALUE val)
+OCIInterval *oci8_set_ociinterval_ym(OCIInterval *intvl, VALUE val)
 {
-    OCIInterval **intvlpp = (OCIInterval **)data;
-    int months = NUM2INT(val);
-    int sign = 1;
+    sb4 year;
+    sb4 month;
 
-    if (months < 0) {
-        sign = -1;
-        months = - months;
+    Check_Type(val, T_ARRAY);
+    if (RARRAY_LEN(val) != 2) {
+        rb_raise(rb_eRuntimeError, "invalid array size %d", RARRAY_LEN(val));
     }
+    year = NUM2INT(RARRAY_PTR(val)[0]);
+    month = NUM2INT(RARRAY_PTR(val)[1]);
     oci_lc(OCIIntervalSetYearMonth(oci8_envhp, oci8_errhp,
-                                   (months / 12) * sign,
-                                   (months % 12) * sign,
-                                   *intvlpp));
+                                   year, month, intvl));
+    return intvl;
 }
 
-static void bind_interval_ym_init(oci8_bind_t *obind, VALUE svc, VALUE val, VALUE length)
+VALUE oci8_make_ociinterval_ds(OCIInterval *s)
 {
+    sb4 day;
+    sb4 hour;
+    sb4 minute;
+    sb4 sec;
+    sb4 fsec;
+
+    oci_lc(OCIIntervalGetDaySecond(oci8_envhp, oci8_errhp, &day, &hour, &minute, &sec, &fsec, s));
+    return rb_ary_new3(5,
+                       INT2FIX(day), INT2FIX(hour),
+                       INT2FIX(minute), INT2FIX(sec),
+                       INT2FIX(fsec));
+}
+
+OCIInterval *oci8_set_ociinterval_ds(OCIInterval *intvl, VALUE val)
+{
+    sb4 day;
+    sb4 hour;
+    sb4 minute;
+    sb4 sec;
+    sb4 fsec;
+
+    Check_Type(val, T_ARRAY);
+    if (RARRAY_LEN(val) != 5) {
+        rb_raise(rb_eRuntimeError, "invalid array size %d", RARRAY_LEN(val));
+    }
+    day = NUM2INT(RARRAY_PTR(val)[0]);
+    hour = NUM2INT(RARRAY_PTR(val)[1]);
+    minute = NUM2INT(RARRAY_PTR(val)[2]);
+    sec = NUM2INT(RARRAY_PTR(val)[3]);
+    fsec = NUM2INT(RARRAY_PTR(val)[4]);
+    oci_lc(OCIIntervalSetDaySecond(oci8_envhp, oci8_errhp,
+                                   day, hour, minute, sec, fsec, intvl));
+    return intvl;
+}
+
+
+static VALUE bind_ociinterval_ym_get(oci8_bind_t *obind, void *data, void *null_struct)
+{
+    return oci8_make_ociinterval_ym(*(OCIInterval **)data);
+}
+
+static void bind_ociinterval_ym_set(oci8_bind_t *obind, void *data, void **null_structp, VALUE val)
+{
+    oci8_set_ociinterval_ym(*(OCIInterval **)data, val);
+}
+
+static void bind_ociinterval_ym_init(oci8_bind_t *obind, VALUE svc, VALUE val, VALUE length)
+{
     oci8_bind_dsc_t *obind_dsc = (oci8_bind_dsc_t *)obind;
 
     obind->value_sz = sizeof(OCIInterval*);
@@ -490,7 +357,7 @@
     obind_dsc->type = OCI_DTYPE_INTERVAL_YM;
 }
 
-static void bind_interval_ym_init_elem(oci8_bind_t *obind, VALUE svc)
+static void bind_ociinterval_ym_init_elem(oci8_bind_t *obind, VALUE svc)
 {
     ub4 idx = 0;
     sword rv;
@@ -502,130 +369,17 @@
     } while (++idx < obind->maxar_sz);
 }
 
-/*
- * Document-class: OCI8::BindType::IntervalDS
- *
- * This is a helper class to bind ruby's
- * Rational[http://www.ruby-doc.org/core/classes/Rational.html]
- * object as Oracle's <tt>INTERVAL DAY TO SECOND</tt> datatype.
- *
- * == Select
- *
- * The fetched value for a <tt>INTERVAL DAY TO SECOND</tt> column
- * is a Rational[http://www.ruby-doc.org/core/classes/Rational.html]
- * or an Integer[http://www.ruby-doc.org/core/classes/Integer.html].
- * The value is usable to apply to
- * DateTime[http://www.ruby-doc.org/core/classes/DateTime.html]#+ and
- * DateTime[http://www.ruby-doc.org/core/classes/DateTime.html]#-.
- *
- * == Bind
- *
- * You cannot bind as <tt>INTERVAL YEAR TO MONTH</tt> implicitly.
- * It must be bound explicitly with :interval_ds.
- *
- *   # output
- *   ts1 = DateTime.parse('1969-11-19 06:54:35 00:00')
- *   ts2 = DateTime.parse('1969-07-20 20:17:40 00:00')
- *   cursor = conn.parse(<<-EOS)
- *     BEGIN
- *       :itv := (:ts1 - :ts2) DAY TO SECOND;
- *     END;
- *   EOS
- *   cursor.bind_param(:itv, nil, :interval_ds)
- *   cursor.bind_param(:ts1, ts1)
- *   cursor.bind_param(:ts2, ts2)
- *   cursor.exec
- *   cursor[:itv] # == ts1 - ts2
- *   cursor.close
- *
- *   # input
- *   ts2 = DateTime.parse('1969-07-20 20:17:40 00:00')
- *   itv = 121 + 10.to_r/24 + 36.to_r/(24*60) + 55.to_r/(24*60*60)
- *   # 121 days, 10 hours,    36 minutes,       55 seconds
- *   cursor = conn.parse(<<-EOS)
- *     BEGIN
- *       :ts1 := :ts2 + :itv;
- *     END;
- *   EOS
- *   cursor.bind_param(:ts1, nil, DateTime)
- *   cursor.bind_param(:ts2, ts2)
- *   cursor.bind_param(:itv, itv, :interval_ds)
- *   cursor.exec
- *   cursor[:ts1].strftime('%Y-%m-%d %H:%M:%S') # => 1969-11-19 06:54:35
- *   cursor.close
- */
-static VALUE bind_interval_ds_get(oci8_bind_t *obind, void *data, void *null_struct)
+static VALUE bind_ociinterval_ds_get(oci8_bind_t *obind, void *data, void *null_struct)
 {
-    OCIInterval **intvlpp = (OCIInterval **)data;
-
-    return oci8_make_interval_ds(*intvlpp);
+    return oci8_make_ociinterval_ds(*(OCIInterval **)data);
 }
 
-static void bind_interval_ds_set(oci8_bind_t *obind, void *data, void **null_structp, VALUE val)
+static void bind_ociinterval_ds_set(oci8_bind_t *obind, void *data, void **null_structp, VALUE val)
 {
-    OCIInterval **intvlpp = (OCIInterval **)data;
-    sb4 day;
-    sb4 hour;
-    sb4 minute;
-    sb4 sec;
-    sb4 fsec;
-    VALUE ary;
-    int is_negative = 0;
-
-    if (!rb_obj_is_kind_of(val, rb_cNumeric)) {
-        rb_raise(rb_eTypeError, "expected numeric but %s", rb_class2name(CLASS_OF(val)));
-    }
-    /* sign */
-    if (RTEST(rb_funcall(val, id_less, 1, INT2FIX(0)))) {
-        is_negative = 1;
-        val = rb_funcall(val, id_uminus, 0);
-    }
-    /* day */
-    ary = rb_funcall(val, id_divmod, 1, INT2FIX(1));
-    Check_Type(ary, T_ARRAY);
-    if (RARRAY_LEN(ary) != 2) {
-        rb_raise(rb_eRuntimeError, "invalid array size");
-    }
-    day = NUM2INT(RARRAY_PTR(ary)[0]);
-    /* hour */
-    val = rb_funcall(RARRAY_PTR(ary)[1], id_mul, 1, INT2FIX(24));
-    ary = rb_funcall(val, id_divmod, 1, INT2FIX(1));
-    Check_Type(ary, T_ARRAY);
-    if (RARRAY_LEN(ary) != 2) {
-        rb_raise(rb_eRuntimeError, "invalid array size");
-    }
-    hour = NUM2INT(RARRAY_PTR(ary)[0]);
-    /* minute */
-    val = rb_funcall(RARRAY_PTR(ary)[1], id_mul, 1, INT2FIX(60));
-    ary = rb_funcall(val, id_divmod, 1, INT2FIX(1));
-    Check_Type(ary, T_ARRAY);
-    if (RARRAY_LEN(ary) != 2) {
-        rb_raise(rb_eRuntimeError, "invalid array size");
-    }
-    minute = NUM2INT(RARRAY_PTR(ary)[0]);
-    /* second */
-    val = rb_funcall(RARRAY_PTR(ary)[1], id_mul, 1, INT2FIX(60));
-    ary = rb_funcall(val, id_divmod, 1, INT2FIX(1));
-    Check_Type(ary, T_ARRAY);
-    if (RARRAY_LEN(ary) != 2) {
-        rb_raise(rb_eRuntimeError, "invalid array size");
-    }
-    sec = NUM2INT(RARRAY_PTR(ary)[0]);
-    /* fsec */
-    val = rb_funcall(RARRAY_PTR(ary)[1], id_mul, 1, INT2FIX(1000000000));
-    fsec = NUM2INT(val);
-    if (is_negative) {
-        day = - day;
-        hour = - hour;
-        minute = - minute;
-        sec = - sec;
-        fsec = - fsec;
-    }
-    oci_lc(OCIIntervalSetDaySecond(oci8_envhp, oci8_errhp,
-                                   day, hour, minute, sec, fsec, *intvlpp));
+    oci8_set_ociinterval_ds(*(OCIInterval **)data, val);
 }
 
-static void bind_interval_ds_init(oci8_bind_t *obind, VALUE svc, VALUE val, VALUE length)
+static void bind_ociinterval_ds_init(oci8_bind_t *obind, VALUE svc, VALUE val, VALUE length)
 {
     oci8_bind_dsc_t *obind_dsc = (oci8_bind_dsc_t *)obind;
 
@@ -634,7 +388,7 @@
     obind_dsc->type = OCI_DTYPE_INTERVAL_DS;
 }
 
-static void bind_interval_ds_init_elem(oci8_bind_t *obind, VALUE svc)
+static void bind_ociinterval_ds_init_elem(oci8_bind_t *obind, VALUE svc)
 {
     ub4 idx = 0;
     sword rv;
@@ -646,92 +400,50 @@
     } while (++idx < obind->maxar_sz);
 }
 
-static const oci8_bind_class_t bind_datetime_class = {
+static const oci8_bind_class_t bind_ociinterval_ym_class = {
     {
         NULL,
         oci8_bind_dsc_free,
         sizeof(oci8_bind_dsc_t)
     },
-    bind_datetime_get,
-    bind_datetime_set,
-    bind_datetime_init,
-    bind_datetime_init_elem,
+    bind_ociinterval_ym_get,
+    bind_ociinterval_ym_set,
+    bind_ociinterval_ym_init,
+    bind_ociinterval_ym_init_elem,
     NULL,
     NULL,
     NULL,
-    SQLT_TIMESTAMP_TZ
-};
-
-static const oci8_bind_class_t bind_interval_ym_class = {
-    {
-        NULL,
-        oci8_bind_dsc_free,
-        sizeof(oci8_bind_dsc_t)
-    },
-    bind_interval_ym_get,
-    bind_interval_ym_set,
-    bind_interval_ym_init,
-    bind_interval_ym_init_elem,
-    NULL,
-    NULL,
-    NULL,
     SQLT_INTERVAL_YM
 };
 
-static const oci8_bind_class_t bind_interval_ds_class = {
+static const oci8_bind_class_t bind_ociinterval_ds_class = {
     {
         NULL,
         oci8_bind_dsc_free,
         sizeof(oci8_bind_dsc_t)
     },
-    bind_interval_ds_get,
-    bind_interval_ds_set,
-    bind_interval_ds_init,
-    bind_interval_ds_init_elem,
+    bind_ociinterval_ds_get,
+    bind_ociinterval_ds_set,
+    bind_ociinterval_ds_init,
+    bind_ociinterval_ds_init_elem,
     NULL,
     NULL,
     NULL,
     SQLT_INTERVAL_DS
 };
 
+#endif /* defined RUNTIME_API_CHECK || ORACLE_CLIENT_VERSION >= 900 */
+
 void Init_oci_datetime(void)
 {
-    rb_require("date");
+    oci8_define_bind_class("OCIDate", &bind_ocidate_class);
 
-    cDateTime = rb_eval_string("DateTime");
-    id_civil = rb_intern("civil");
-    id_parse = rb_intern("parse");
-    id_to_r = rb_intern("to_r");
-    id_add = rb_intern("+");
-    id_mul = rb_intern("*");
-    id_div = rb_intern("/");
-    id_divmod = rb_intern("divmod");
-    id_less = rb_intern("<");
-    id_uminus = rb_intern("-@");
-    id_year = rb_intern("year");
-    id_mon = rb_intern("mon");
-    id_month = rb_intern("month");
-    id_mday = rb_intern("mday");
-    id_day = rb_intern("day");
-    id_hour = rb_intern("hour");
-    id_min = rb_intern("min");
-    id_sec = rb_intern("sec");
-    id_sec_fraction = rb_intern("sec_fraction");
-    id_offset = rb_intern("offset");
-    id_utc_offset = rb_intern("utc_offset");
+#if defined RUNTIME_API_CHECK || ORACLE_CLIENT_VERSION >= 900
+    if (oracle_client_version >= 900) {
+        oci8_define_bind_class("OCITimestamp", &bind_ocitimestamp_class);
+        oci8_define_bind_class("OCIIntervalYM", &bind_ociinterval_ym_class);
+        oci8_define_bind_class("OCIIntervalDS", &bind_ociinterval_ds_class);
+    }
+#endif
 
-    hour_base = rb_funcall(INT2FIX(24), id_to_r, 0);
-    minute_base = rb_funcall(hour_base, id_mul, 1, INT2FIX(60));
-    sec_base = rb_funcall(minute_base, id_mul, 1, INT2FIX(60));
-    fsec_base = rb_funcall(sec_base, id_mul, 1, INT2FIX(1000000000));
-    fsec_mul = rb_eval_string("(100_000_000 / DateTime.parse('0001-01-01 00:00:00.1').sec_fraction).to_i");
-    rb_global_variable(&hour_base);
-    rb_global_variable(&minute_base);
-    rb_global_variable(&sec_base);
-    rb_global_variable(&fsec_base);
-    rb_global_variable(&fsec_mul);
-
-    oci8_define_bind_class("DateTime", &bind_datetime_class);
-    oci8_define_bind_class("IntervalYM", &bind_interval_ym_class);
-    oci8_define_bind_class("IntervalDS", &bind_interval_ds_class);
 }

Added: trunk/ruby-oci8/lib/oci8/datetime.rb
===================================================================
--- trunk/ruby-oci8/lib/oci8/datetime.rb	                        (rev 0)
+++ trunk/ruby-oci8/lib/oci8/datetime.rb	2008-08-02 14:21:56 UTC (rev 274)
@@ -0,0 +1,455 @@
+require 'date'
+
+class OCI8
+
+  module BindType
+
+    module Util # :nodoc:
+
+      @@datetime_fsec_base = (1 / ::DateTime.parse('0001-01-01 00:00:00.000000001').sec_fraction).to_i
+      @@time_offset = ::Time.now.utc_offset
+      @@datetime_offset = ::DateTime.now.offset
+
+      private
+
+      def datetime_to_array(val, full)
+        # year
+        year = val.year
+        # month
+        if val.respond_to? :mon
+          month = val.mon
+        elsif val.respond_to? :month
+          month = val.month
+        else
+          raise "expect Time, Date or DateTime but #{val.class}"
+        end
+        # day
+        if val.respond_to? :mday
+          day = val.mday
+        elsif val.respond_to? :day
+          day = val.day
+        else
+          raise "expect Time, Date or DateTime but #{val.class}"
+        end
+        # hour
+        if val.respond_to? :hour
+          hour = val.hour
+        else
+          hour = 0
+        end
+        # minute
+        if val.respond_to? :min
+          minute = val.min
+        else
+          minute = 0
+        end
+        # second
+        if val.respond_to? :sec
+          sec = val.sec
+        else
+          sec = 0
+        end
+        return [year, month, day, hour, minute, sec] unless full
+
+        # sec_fraction
+        if val.respond_to? :sec_fraction
+          fsec = (val.sec_fraction * @@datetime_fsec_base).to_i
+        else
+          fsec = 0
+        end
+        # time zone
+        if val.respond_to? :offset
+          # DateTime
+          tz_min = (val.offset * 1440).to_i
+        elsif val.respond_to? :utc_offset
+          # Time
+          tz_min = val.utc_offset / 60
+        else
+          tz_hour = nil
+          tz_min = nil
+        end
+        if tz_min
+          if tz_min < 0
+            tz_min = - tz_min
+            tz_hour = - (tz_min / 60)
+            tz_min = (tz_min % 60)
+          else
+            tz_hour = tz_min / 60
+            tz_min = tz_min % 60
+          end
+        end
+        [year, month, day, hour, minute, sec, fsec, tz_hour, tz_min]
+      end
+
+      def ocidate_to_datetime(ary)
+        year, month, day, hour, minute, sec = ary
+        ::DateTime.civil(year, month, day, hour, minute, sec, @@datetime_offset)
+      end
+
+      def ocidate_to_time(ary)
+        year, month, day, hour, minute, sec = ary
+        begin
+          ::Time.local(year, month, day, hour, minute, sec)
+        rescue StandardError
+        end
+        ocidate_to_datetime(ary)
+      end
+
+      if OCI8.oracle_client_version >= 900
+
+        def ocitimestamp_to_datetime(ary)
+          year, month, day, hour, minute, sec, fsec, tz_hour, tz_min = ary
+          if sec >= 59 and fsec != 0
+            # convert to a DateTime via a String as a last resort.
+            if tz_hour >= 0 && tz_min >= 0
+              sign = ?+
+            else
+              sign = ?-
+              tz_hour = - tz_hour
+              tz_min = - tz_min
+            end
+            time_str = format("%04d-%02d-%02dT%02d:%02d:%02d.%09d%c%02d:%02d",
+                              year, month, day, hour, minute, sec, fsec, sign, tz_hour, tz_min)
+            ::DateTime.parse(time_str)
+          else
+            sec += fsec.to_r / 1000000000
+            offset = tz_hour.to_r / 24 + tz_min.to_r / 1440
+            ::DateTime.civil(year, month, day, hour, minute, sec, offset)
+          end
+        end
+
+        def ocitimestamp_to_time(ary)
+          year, month, day, hour, minute, sec, fsec, tz_hour, tz_min = ary
+
+          if tz_hour == 0 and tz_min == 0
+            timezone = :utc
+          elsif @@time_offset == tz_hour * 3600 + tz_min * 60
+            timezone = :local
+          end
+          if timezone
+            begin
+              return ::Time.send(timezone, year, month, day, hour, minute, sec, fsec / 1000)
+            rescue StandardError
+            end
+          end
+          ocitimestamp_to_datetime(ary)
+        end
+      end
+    end
+
+    class DateTimeViaOCIDate < OCI8::BindType::OCIDate
+      include OCI8::BindType::Util
+
+      def set(val) # :nodoc:
+        val &&= datetime_to_array(val, false)
+        super(val)
+      end
+
+      def get() # :nodoc:
+        val = super()
+        val ? ocidate_to_datetime(val) : nil
+      end
+    end
+
+    class TimeViaOCIDate < OCI8::BindType::OCIDate
+      include OCI8::BindType::Util
+
+      def set(val) # :nodoc:
+        val &&= datetime_to_array(val, false)
+        super(val)
+      end
+
+      def get() # :nodoc:
+        val = super()
+        val ? ocidate_to_time(val) : nil
+      end
+    end
+
+    if OCI8.oracle_client_version >= 900
+      class DateTimeViaOCITimestamp < OCI8::BindType::OCITimestamp
+        include OCI8::BindType::Util
+
+        def set(val) # :nodoc:
+          val &&= datetime_to_array(val, true)
+          super(val)
+        end
+
+        def get() # :nodoc:
+          val = super()
+          val ? ocitimestamp_to_datetime(val) : nil
+        end
+      end
+
+      class TimeViaOCITimestamp < OCI8::BindType::OCITimestamp
+        include OCI8::BindType::Util
+
+        def set(val) # :nodoc:
+          val &&= datetime_to_array(val, true)
+          super(val)
+        end
+
+        def get() # :nodoc:
+          val = super()
+          val ? ocitimestamp_to_time(val) : nil
+        end
+      end
+    end
+
+
+    #--
+    # OCI8::BindType::DateTime
+    #++
+    # This is a helper class to bind ruby's
+    # DateTime[http://www.ruby-doc.org/core/classes/DateTime.html]
+    # object as Oracle's <tt>TIMESTAMP WITH TIME ZONE</tt> datatype.
+    #
+    # == Select
+    #
+    # The fetched value for a <tt>DATE</tt>, <tt>TIMESTAMP</tt>, <tt>TIMESTAMP WITH
+    # TIME ZONE</tt> or <tt>TIMESTAMP WITH LOCAL TIME ZONE</tt> column
+    # is a DateTime[http://www.ruby-doc.org/core/classes/DateTime.html].
+    # The time zone part is a session time zone if the Oracle datatype doesn't
+    # have time zone information. The session time zone is the client machine's
+    # time zone by default.
+    #
+    # You can change the session time zone by executing the following SQL.
+    #
+    #   ALTER SESSION SET TIME_ZONE='-05:00'
+    #
+    # == Bind
+    #
+    # To bind a DateTime[http://www.ruby-doc.org/core/classes/DateTime.html]
+    # value implicitly:
+    #
+    #   conn.exec("INSERT INTO lunar_landings(ship_name, landing_time) VALUES(:1, :2)",
+    #             'Apollo 11',
+    #             DateTime.parse('1969-7-20 20:17:40 00:00'))
+    #
+    # The bind variable <code>:2</code> is bound as <tt>TIMESTAMP WITH TIME ZONE</tt> on Oracle.
+    #
+    # To bind explicitly:
+    #
+    #   cursor = conn.exec("INSERT INTO lunar_landings(ship_name, landing_time) VALUES(:1, :2)")
+    #   cursor.bind_param(':1', nil, String, 60)
+    #   cursor.bind_param(':2', nil, DateTime)
+    #   [['Apollo 11', DateTime.parse('1969-07-20 20:17:40 00:00'))],
+    #    ['Apollo 12', DateTime.parse('1969-11-19 06:54:35 00:00'))],
+    #    ['Apollo 14', DateTime.parse('1971-02-05 09:18:11 00:00'))],
+    #    ['Apollo 15', DateTime.parse('1971-07-30 22:16:29 00:00'))],
+    #    ['Apollo 16', DateTime.parse('1972-04-21 02:23:35 00:00'))],
+    #    ['Apollo 17', DateTime.parse('1972-12-11 19:54:57 00:00'))]
+    #   ].each do |ship_name, landing_time|
+    #     cursor[':1'] = ship_name
+    #     cursor[':2'] = landing_time
+    #     cursor.exec
+    #   end
+    #   cursor.close
+    #
+    # On setting a object to the bind variable, you can use any object
+    # which has at least three instance methods _year_, _mon_ (or _month_)
+    # and _mday_ (or _day_). If the object responses to _hour_, _min_,
+    # _sec_ or _sec_fraction_, the responsed values are used for hour,
+    # minute, second or fraction of a second respectively.
+    # If not, zeros are set. If the object responses to _offset_ or
+    # _utc_offset_, it is used for time zone. If not, the session time
+    # zone is used.
+    #
+    # The acceptable value are listed below.
+    # _year_:: -4712 to 9999 [excluding year 0]
+    # _mon_ (or _month_):: 0 to 12
+    # _mday_ (or _day_):: 0 to 31 [depends on the month]
+    # _hour_:: 0 to 23
+    # _min_:: 0 to 59
+    # _sec_:: 0 to 59
+    # _sec_fraction_:: 0 to (999_999_999.to_r / (24*60*60* 1_000_000_000)) [999,999,999 nanoseconds]
+    # _offset_:: (-12.to_r / 24) to (14.to_r / 24) [-12:00 to +14:00]
+    # _utc_offset_:: -12*3600 <= utc_offset <= 24*3600 [-12:00 to +14:00]
+    #
+    # The output value of the bind varible is always a
+    # DateTime[http://www.ruby-doc.org/core/classes/DateTime.html].
+    #
+    #   cursor = conn.exec("BEGIN :ts := current_timestamp; END")
+    #   cursor.bind_param(:ts, nil, DateTime)
+    #   cursor.exec
+    #   cursor[:ts] # => a DateTime.
+    #   cursor.close
+    #
+    class DateTime
+      if OCI8.oracle_client_version >= 900
+        def self.create(con, val, param, max_array_size)
+          DateTimeViaOCITimestamp.new(con, val, param, max_array_size)
+        end
+      else
+        def self.create(con, val, param, max_array_size)
+          DateTimeViaOCIDate.new(con, val, param, max_array_size)
+        end
+      end
+    end
+
+    class Time
+      if OCI8.oracle_client_version >= 900
+        def self.create(con, val, param, max_array_size)
+          TimeViaOCITimestamp.new(con, val, param, max_array_size)
+        end
+      else
+        def self.create(con, val, param, max_array_size)
+          TimeViaOCIDate.new(con, val, param, max_array_size)
+        end
+      end
+    end
+
+    if OCI8.oracle_client_version >= 900
+      #--
+      # OCI8::BindType::IntervalYM
+      #++
+      #
+      # This is a helper class to bind ruby's
+      # Integer[http://www.ruby-doc.org/core/classes/Integer.html]
+      # object as Oracle's <tt>INTERVAL YEAR TO MONTH</tt> datatype.
+      #
+      # == Select
+      #
+      # The fetched value for a <tt>INTERVAL YEAR TO MONTH</tt> column
+      # is an Integer[http://www.ruby-doc.org/core/classes/Integer.html]
+      # which means the months between two timestamps.
+      #
+      # == Bind
+      #
+      # You cannot bind as <tt>INTERVAL YEAR TO MONTH</tt> implicitly.
+      # It must be bound explicitly with :interval_ym.
+      #
+      #   # output bind variable
+      #   cursor = conn.parse(<<-EOS)
+      #     BEGIN
+      #       :interval := (:ts1 - :ts2) YEAR TO MONTH;
+      #     END;
+      #   EOS
+      #   cursor.bind_param(:interval, nil, :interval_ym)
+      #   cursor.bind_param(:ts1, DateTime.parse('1969-11-19 06:54:35 00:00'))
+      #   cursor.bind_param(:ts2, DateTime.parse('1969-07-20 20:17:40 00:00'))
+      #   cursor.exec
+      #   cursor[:interval] # => 4 (months)
+      #   cursor.close
+      #
+      #   # input bind variable
+      #   cursor = conn.parse(<<-EOS)
+      #     BEGIN
+      #       :ts1 := :ts2 + :interval;
+      #     END;
+      #   EOS
+      #   cursor.bind_param(:ts1, nil, DateTime)
+      #   cursor.bind_param(:ts2, Date.parse('1969-11-19'))
+      #   cursor.bind_param(:interval, 4, :interval_ym)
+      #   cursor.exec
+      #   cursor[:ts1].strftime('%Y-%m-%d') # => 1970-03-19
+      #   cursor.close
+      #
+      class IntervalYM < OCI8::BindType::OCIIntervalYM
+        def set(val) # :nodoc:
+          unless val.nil?
+            val = [val / 12, val % 12]
+          end
+          super(val)
+        end
+        def get() # :nodoc:
+          val = super()
+          return nil if val.nil?
+          year, month = val
+          year * 12 + month
+        end
+      end # OCI8::BindType::IntervalYM
+
+      #--
+      # OCI8::BindType::IntervalDS
+      #++
+      #
+      # This is a helper class to bind ruby's
+      # Rational[http://www.ruby-doc.org/core/classes/Rational.html]
+      # object as Oracle's <tt>INTERVAL DAY TO SECOND</tt> datatype.
+      #
+      # == Select
+      #
+      # The fetched value for a <tt>INTERVAL DAY TO SECOND</tt> column
+      # is a Rational[http://www.ruby-doc.org/core/classes/Rational.html]
+      # or an Integer[http://www.ruby-doc.org/core/classes/Integer.html].
+      # The value is usable to apply to
+      # DateTime[http://www.ruby-doc.org/core/classes/DateTime.html]#+ and
+      # DateTime[http://www.ruby-doc.org/core/classes/DateTime.html]#-.
+      #
+      # == Bind
+      #
+      # You cannot bind as <tt>INTERVAL YEAR TO MONTH</tt> implicitly.
+      # It must be bound explicitly with :interval_ds.
+      #
+      #   # output
+      #   ts1 = DateTime.parse('1969-11-19 06:54:35 00:00')
+      #   ts2 = DateTime.parse('1969-07-20 20:17:40 00:00')
+      #   cursor = conn.parse(<<-EOS)
+      #     BEGIN
+      #       :itv := (:ts1 - :ts2) DAY TO SECOND;
+      #     END;
+      #   EOS
+      #   cursor.bind_param(:itv, nil, :interval_ds)
+      #   cursor.bind_param(:ts1, ts1)
+      #   cursor.bind_param(:ts2, ts2)
+      #   cursor.exec
+      #   cursor[:itv] # == ts1 - ts2
+      #   cursor.close
+      #
+      #   # input
+      #   ts2 = DateTime.parse('1969-07-20 20:17:40 00:00')
+      #   itv = 121 + 10.to_r/24 + 36.to_r/(24*60) + 55.to_r/(24*60*60)
+      #   # 121 days, 10 hours,    36 minutes,       55 seconds
+      #   cursor = conn.parse(<<-EOS)
+      #     BEGIN
+      #       :ts1 := :ts2 + :itv;
+      #     END;
+      #   EOS
+      #   cursor.bind_param(:ts1, nil, DateTime)
+      #   cursor.bind_param(:ts2, ts2)
+      #   cursor.bind_param(:itv, itv, :interval_ds)
+      #   cursor.exec
+      #   cursor[:ts1].strftime('%Y-%m-%d %H:%M:%S') # => 1969-11-19 06:54:35
+      #   cursor.close
+      #
+      class IntervalDS < OCI8::BindType::OCIIntervalDS
+        @@hour = 1 / 24.to_r
+        @@minute = @@hour / 60
+        @@sec = @@minute / 60
+        @@fsec = @@sec / 1000000000
+
+        def set(val) # :nodoc:
+          unless val.nil?
+            if val < 0
+              is_minus = true
+              val = -val
+            else
+              is_minus = false
+            end
+            day, val = val.divmod 1
+            hour, val = (val * 24).divmod 1
+            minute, val = (val * 60).divmod 1
+            sec, val = (val * 60).divmod 1
+            fsec, val = (val * 1000000000).divmod 1
+            if is_minus
+              day = - day
+              hour = - hour
+              minute = - minute
+              sec = - sec
+              fsec = - fsec
+            end
+            val = [day, hour, minute, sec, fsec]
+          end
+          super(val)
+        end
+
+        def get() # :nodoc:
+          val = super()
+          return nil if val.nil?
+          day, hour, minute, sec, fsec = val
+          day + (hour * @@hour) + (minute * @@minute) + (sec * @@sec) + (fsec * @@fsec)
+        end
+      end # OCI8::BindType::IntervalDS
+    end
+  end # OCI8::BindType
+end # OCI8

Modified: trunk/ruby-oci8/lib/oci8/oci8.rb
===================================================================
--- trunk/ruby-oci8/lib/oci8/oci8.rb	2008-07-17 15:01:30 UTC (rev 273)
+++ trunk/ruby-oci8/lib/oci8/oci8.rb	2008-08-02 14:21:56 UTC (rev 274)
@@ -144,16 +144,6 @@
       end
     end
 
-    # get/set Time
-    class Time < OCI8::BindType::OraDate
-      def set(val)
-        super(val && ::OraDate.new(val.year, val.mon, val.mday, val.hour, val.min, val.sec))
-      end
-      def get()
-        (val = super()) && val.to_time
-      end
-    end
-
     # get/set Date
     class Date < OCI8::BindType::OraDate
       def set(val)
@@ -698,13 +688,15 @@
 # datatype        type     size prec scale
 # -------------------------------------------------
 # DATE          SQLT_DAT      7    0    0
-OCI8::BindType::Mapping[:date] = OCI8::BindType::DateTime
+OCI8::BindType::Mapping[:date] = OCI8::BindType::Time
 
-OCI8::BindType::Mapping[:timestamp] = OCI8::BindType::DateTime
-OCI8::BindType::Mapping[:timestamp_tz] = OCI8::BindType::DateTime
-OCI8::BindType::Mapping[:timestamp_ltz] = OCI8::BindType::DateTime
-OCI8::BindType::Mapping[:interval_ym] = OCI8::BindType::IntervalYM
-OCI8::BindType::Mapping[:interval_ds] = OCI8::BindType::IntervalDS
+if OCI8.oracle_client_version >= 900
+  OCI8::BindType::Mapping[:timestamp] = OCI8::BindType::Time
+  OCI8::BindType::Mapping[:timestamp_tz] = OCI8::BindType::DateTime
+  OCI8::BindType::Mapping[:timestamp_ltz] = OCI8::BindType::Time
+  OCI8::BindType::Mapping[:interval_ym] = OCI8::BindType::IntervalYM
+  OCI8::BindType::Mapping[:interval_ds] = OCI8::BindType::IntervalDS
+end
 
 # datatype        type     size prec scale
 # -------------------------------------------------

Modified: trunk/ruby-oci8/lib/oci8.rb.in
===================================================================
--- trunk/ruby-oci8/lib/oci8.rb.in	2008-07-17 15:01:30 UTC (rev 273)
+++ trunk/ruby-oci8/lib/oci8.rb.in	2008-08-02 14:21:56 UTC (rev 274)
@@ -19,6 +19,8 @@
 end
 
 require 'oci8lib'
+
+require 'oci8/datetime.rb'
 require 'oci8/oci8.rb'
 require 'oci8/metadata.rb'
 require 'oci8/compat.rb'

Modified: trunk/ruby-oci8/test/test_array_dml.rb
===================================================================
--- trunk/ruby-oci8/test/test_array_dml.rb	2008-07-17 15:01:30 UTC (rev 273)
+++ trunk/ruby-oci8/test/test_array_dml.rb	2008-08-02 14:21:56 UTC (rev 274)
@@ -87,8 +87,8 @@
       assert_equal(format("%10d", i * 10), rv[0])
       assert_equal(i.to_s, rv[1])
       assert_equal(i, rv[2])
-      dttm = DateTime.civil(2000 + i, 12, 24, 23, 59, 59, Time.now.utc_offset.to_r/86400)
-      assert_equal(dttm, rv[3])
+      tm = Time.local(2000 + i, 12, 24, 23, 59, 59)
+      assert_equal(tm, rv[3])
       assert_equal(i * 11111111111, rv[4])
       assert_equal(i * 10000000000, rv[5])
     end

Modified: trunk/ruby-oci8/test/test_datetime.rb
===================================================================
--- trunk/ruby-oci8/test/test_datetime.rb	2008-07-17 15:01:30 UTC (rev 273)
+++ trunk/ruby-oci8/test/test_datetime.rb	2008-08-02 14:21:56 UTC (rev 274)
@@ -1,6 +1,7 @@
 require 'oci8'
 require 'test/unit'
 require './config'
+require 'scanf'
 
 class TestDateTime < Test::Unit::TestCase
 
@@ -27,7 +28,7 @@
       @conn.exec(<<-EOS) do |row|
 SELECT TO_DATE('#{date}', 'YYYY-MM-DD HH24:MI:SS') FROM dual
 EOS
-        assert_equal(DateTime.parse(date + @local_timezone), row[0])
+        assert_equal(Time.local(*date.scanf("%d-%d-%d %d:%d:%d.%06d")), row[0])
       end
     end
   end
@@ -75,7 +76,7 @@
       @conn.exec(<<-EOS) do |row|
 SELECT TO_TIMESTAMP('#{date}', 'YYYY-MM-DD HH24:MI:SS.FF') FROM dual
 EOS
-        assert_equal(DateTime.parse(date + @local_timezone), row[0])
+        assert_equal(Time.local(*date.scanf("%d-%d-%d %d:%d:%d.%06d")), row[0])
       end
     end
   end

Modified: trunk/ruby-oci8/test/test_dbi.rb
===================================================================
--- trunk/ruby-oci8/test/test_dbi.rb	2008-07-17 15:01:30 UTC (rev 273)
+++ trunk/ruby-oci8/test/test_dbi.rb	2008-08-02 14:21:56 UTC (rev 274)
@@ -134,7 +134,7 @@
         tm = Time.local(2000 + i, 8, 3, 23, 59, 59)
 	dt = Date.civil(2000 + i, 8, 3)
 	dttm = DateTime.civil(2000 + i, 8, 3, 23, 59, 59, Time.now.utc_offset.to_r/86400)
-	assert_equal(dttm, rv[3])
+	assert_equal(tm, rv[3])
 	assert_equal(tm, rv[4])
 	assert_equal(dt, rv[5])
 	assert_equal(dttm, rv[6])

Modified: trunk/ruby-oci8/test/test_oci8.rb
===================================================================
--- trunk/ruby-oci8/test/test_oci8.rb	2008-07-17 15:01:30 UTC (rev 273)
+++ trunk/ruby-oci8/test/test_oci8.rb	2008-08-02 14:21:56 UTC (rev 274)
@@ -84,7 +84,7 @@
         tm = Time.local(2000 + i, 8, 3, 23, 59, 59)
 	dt = Date.civil(2000 + i, 8, 3)
 	dttm = DateTime.civil(2000 + i, 8, 3, 23, 59, 59, Time.now.utc_offset.to_r/86400)
-	assert_equal(dttm, rv[3])
+	assert_equal(tm, rv[3])
 	assert_equal(tm, rv[4])
 	assert_equal(dt, rv[5])
 	assert_equal(dttm, rv[6])
@@ -110,7 +110,7 @@
         tm = Time.local(2000 + i, 8, 3, 23, 59, 59)
 	dt = Date.civil(2000 + i, 8, 3)
 	dttm = DateTime.civil(2000 + i, 8, 3, 23, 59, 59, Time.now.utc_offset.to_r/86400)
-	assert_equal(dttm, row['D1'])
+	assert_equal(tm, row['D1'])
 	assert_equal(tm, row['D2'])
 	assert_equal(dt, row['D3'])
 	assert_equal(dttm, row['D4'])
@@ -174,7 +174,7 @@
 	dttm = DateTime.civil(2000 + i, 8, 3, 23, 59, 59, Time.now.utc_offset.to_r/86400)
         tm = Time.local(2000 + i, 8, 3, 23, 59, 59)
 	dt = Date.civil(2000 + i, 8, 3)
-	assert_equal(dttm, rv[3])
+	assert_equal(tm, rv[3])
 	assert_equal(tm, rv[4])
 	assert_equal(dt, rv[5])
       end




More information about the ruby-oci8-commit mailing list