[ruby-oci8-commit] [359] trunk/ruby-oci8: * lib/oci8/.document, lib/oci8/datetime.rb: 1.

nobody at rubyforge.org nobody at rubyforge.org
Tue Sep 22 06:46:32 EDT 2009


Revision: 359
Author:   kubo
Date:     2009-09-22 06:46:31 -0400 (Tue, 22 Sep 2009)

Log Message:
-----------
* lib/oci8/.document, lib/oci8/datetime.rb: 1. Add
    OCI8::BindType.default_timezone and OCI8::BindType.default_timezone=.
    2. Change the logic to convert to Time and DateTime to adapt DST.
    3. Use new features of Time class in ruby 1.9.2 if they are available.
    4. Add rdoc comments.
* spec/fetch_datetime8_spec.rb, spec/fetch_datetime_spec.rb,
  spec/fetch_time8_spec.rb, spec/fetch_time_spec.rb,
  spec/spec_helper.rb: Add tests for OCI8::BindType::DateTime and
    OCI8::BindType::Time.

Modified Paths:
--------------
    trunk/ruby-oci8/ChangeLog
    trunk/ruby-oci8/lib/oci8/.document
    trunk/ruby-oci8/lib/oci8/datetime.rb

Added Paths:
-----------
    trunk/ruby-oci8/spec/
    trunk/ruby-oci8/spec/fetch_datetime8_spec.rb
    trunk/ruby-oci8/spec/fetch_datetime_spec.rb
    trunk/ruby-oci8/spec/fetch_time8_spec.rb
    trunk/ruby-oci8/spec/fetch_time_spec.rb
    trunk/ruby-oci8/spec/spec_helper.rb

Modified: trunk/ruby-oci8/ChangeLog
===================================================================
--- trunk/ruby-oci8/ChangeLog	2009-09-13 08:37:53 UTC (rev 358)
+++ trunk/ruby-oci8/ChangeLog	2009-09-22 10:46:31 UTC (rev 359)
@@ -1,3 +1,14 @@
+2009-09-22  KUBO Takehiro  <kubo at jiubao.org>
+	* lib/oci8/.document, lib/oci8/datetime.rb: 1. Add
+	    OCI8::BindType.default_timezone and OCI8::BindType.default_timezone=.
+	    2. Change the logic to convert to Time and DateTime to adapt DST.
+	    3. Use new features of Time class in ruby 1.9.2 if they are available.
+	    4. Add rdoc comments.
+	* spec/fetch_datetime8_spec.rb, spec/fetch_datetime_spec.rb,
+	  spec/fetch_time8_spec.rb, spec/fetch_time_spec.rb,
+	  spec/spec_helper.rb: Add tests for OCI8::BindType::DateTime and
+	    OCI8::BindType::Time.
+
 2009-09-13  KUBO Takehiro  <kubo at jiubao.org>
 	* ext/oci8/lob.c, ext/oci8/oci8.h, test/test_clob.rb: Change
 	    OCI8::LOB#write to accept an object which is not a String and

Modified: trunk/ruby-oci8/lib/oci8/.document
===================================================================
--- trunk/ruby-oci8/lib/oci8/.document	2009-09-13 08:37:53 UTC (rev 358)
+++ trunk/ruby-oci8/lib/oci8/.document	2009-09-22 10:46:31 UTC (rev 359)
@@ -1,3 +1,4 @@
+datetime.rb
 object.rb
 metadata.rb
 oracle_version.rb

Modified: trunk/ruby-oci8/lib/oci8/datetime.rb
===================================================================
--- trunk/ruby-oci8/lib/oci8/datetime.rb	2009-09-13 08:37:53 UTC (rev 358)
+++ trunk/ruby-oci8/lib/oci8/datetime.rb	2009-09-22 10:46:31 UTC (rev 359)
@@ -4,23 +4,44 @@
 
   module BindType
 
+    # call-seq:
+    #   OCI8::BindType.default_timezone -> :local or :utc
+    #
+    # Returns the default time zone when using Oracle 8.x client.
+    # The value is unused when using Oracle 9i or upper client.
+    #
+    # See also: OCI8::BindType::Time
+    def self.default_timezone
+      OCI8::BindType::Util.default_timezone
+    end
+
+    # call-seq:
+    #   OCI8::BindType.default_timezone = :local or :utc
+    #
+    # Sets the default time zone when using Oracle 8.x client.
+    # The value is unused when using Oracle 9i or upper client.
+    #
+    # See also: OCI8::BindType::Time
+    def self.default_timezone=(tz)
+      OCI8::BindType::Util.default_timezone = tz
+    end
+
     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
 
       @@default_timezone = :local
+      begin
+        Time.new(2001, 1, 1, 0, 0, 0, '+00:00')
+        @@time_new_accepts_timezone = true  # after ruby 1.9.2
+      rescue ArgumentError
+        @@time_new_accepts_timezone = false # prior to ruby 1.9.2
+      end
+
       def self.default_timezone
         @@default_timezone
       end
 
-      # Determines default timezone of Time and DateTime retrived from Oracle.
-      # This accepts :local or :utc. The default is :local.
-      #
-      # This parameter is used when both or either of Oracle server and client
-      # version is Oracle 8i or lower. If both versions are Oracle 9i or upper,
-      # the default timezone is determined by the session timezone.
       def self.default_timezone=(tz)
         if tz != :local and tz != :utc
           raise ArgumentError, "expected :local or :utc but #{tz}"
@@ -106,7 +127,12 @@
 
         year, month, day, hour, minute, sec = ary
         if @@default_timezone == :local
-          offset = @@datetime_offset
+          if ::DateTime.respond_to? :local_offset
+            offset = ::DateTime.local_offset # Use a method defined by active support.
+          else
+            # Do as active support does.
+            offset = ::Time.local(2007).utc_offset.to_r / 86400
+          end
         else
           offset = 0
         end
@@ -117,7 +143,7 @@
         return nil if ary.nil?
 
         year, month, day, hour, minute, sec = ary
-        if year >= 139
+        if @@time_new_accepts_timezone || year >= 139 || year < 0
           begin
             return ::Time.send(@@default_timezone, year, month, day, hour, minute, sec)
           rescue StandardError
@@ -151,31 +177,46 @@
           end
         end
 
-        def ocitimestamp_to_time(ary)
-          return nil if ary.nil?
+        if @@time_new_accepts_timezone
 
-          year, month, day, hour, minute, sec, fsec, tz_hour, tz_min = ary
+          # after ruby 1.9.2
+          def ocitimestamp_to_time(ary)
+            return nil if ary.nil?
 
-          if tz_hour == 0 and tz_min == 0
-            timezone = :utc
-          elsif @@time_offset == tz_hour * 3600 + tz_min * 60
-            timezone = :local
+            year, month, day, hour, minute, sec, fsec, tz_hour, tz_min = ary
+
+            sec += fsec / Rational(1000000000)
+            utc_offset = tz_hour * 3600 + tz_min * 60
+            return ::Time.new(year, month, day, hour, minute, sec, utc_offset)
           end
-          if timezone and year >= 139
-            begin
-              # Ruby 1.9 Time class's resolution is nanosecond.
-              # But the last argument type is millisecond.
-              # 'fsec' is converted to a Float to pass sub-millisecond part.
-              return ::Time.send(timezone, year, month, day, hour, minute, sec, fsec / 1000.0)
-            rescue StandardError
+
+        else
+
+          # prior to ruby 1.9.2
+          def ocitimestamp_to_time(ary)
+            return nil if ary.nil?
+
+            year, month, day, hour, minute, sec, fsec, tz_hour, tz_min = ary
+
+            if year >= 139 || year < 0
+              begin
+                if tz_hour == 0 and tz_min == 0
+                  return ::Time.utc(year, month, day, hour, minute, sec, fsec / Rational(1000))
+                else
+                  tm = ::Time.local(year, month, day, hour, minute, sec, fsec / Rational(1000))
+                  return tm if tm.utc_offset == tz_hour * 3600 + tz_min * 60
+                end
+              rescue StandardError
+              end
             end
+            ocitimestamp_to_datetime(ary)
           end
-          ocitimestamp_to_datetime(ary)
+
         end
       end
     end
 
-    class DateTimeViaOCIDate < OCI8::BindType::OCIDate
+    class DateTimeViaOCIDate < OCI8::BindType::OCIDate # :nodoc:
       include OCI8::BindType::Util
 
       def set(val) # :nodoc:
@@ -187,7 +228,7 @@
       end
     end
 
-    class TimeViaOCIDate < OCI8::BindType::OCIDate
+    class TimeViaOCIDate < OCI8::BindType::OCIDate # :nodoc:
       include OCI8::BindType::Util
 
       def set(val) # :nodoc:
@@ -200,7 +241,7 @@
     end
 
     if OCI8.oracle_client_version >= ORAVER_9_0
-      class DateTimeViaOCITimestamp < OCI8::BindType::OCITimestamp
+      class DateTimeViaOCITimestamp < OCI8::BindType::OCITimestamp # :nodoc:
         include OCI8::BindType::Util
 
         def set(val) # :nodoc:
@@ -212,7 +253,7 @@
         end
       end
 
-      class TimeViaOCITimestamp < OCI8::BindType::OCITimestamp
+      class TimeViaOCITimestamp < OCI8::BindType::OCITimestamp # :nodoc:
         include OCI8::BindType::Util
 
         def set(val) # :nodoc:
@@ -225,88 +266,70 @@
       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.
+    # This is a helper class to select or bind Oracle data types such as
+    # <tt>DATE</tt>, <tt>TIMESTAMP</tt>, <tt>TIMESTAMP WITH TIME ZONE</tt>
+    # and <tt>TIMESTAMP WITH LOCAL TIME ZONE</tt>. The retrieved value
+    # is a \DateTime.
     #
-    # == Select
+    # === How to select \DataTime values.
     #
-    # 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.
+    # <tt>DATE</tt>, <tt>TIMESTAMP</tt>, <tt>TIMESTAMP WITH TIME ZONE</tt>
+    # and <tt>TIMESTAMP WITH LOCAL TIME ZONE</tt> are selected as a \Time
+    # by default. You change the behaviour by explicitly calling
+    # OCI8::Cursor#define as follows:
     #
-    # You can change the session time zone by executing the following SQL.
+    #   cursor = conn.parse("SELECT hiredate FROM emp")
+    #   cursor.define(1, nil, DateTime)
+    #   cursor.exec()
     #
-    #   ALTER SESSION SET TIME_ZONE='-05:00'
+    # Otherwise, you can change the default mapping for all queries.
     #
-    # == Bind
+    #   # Changes the mapping for DATE
+    #   OCI8::BindType::Mapping[OCI8::SQLT_DAT] = OCI8::BindType::DateTime
+    #   
+    #   # Changes the mapping for TIMESTAMP
+    #   OCI8::BindType::Mapping[OCI8::SQLT_TIMESTAMP] = OCI8::BindType::DateTime
+    #   
+    #   # Changes the mapping for TIMESTAMP WITH TIME ZONE
+    #   OCI8::BindType::Mapping[OCI8::SQLT_TIMESTAMP_TZ] = OCI8::BindType::DateTime
+    #   
+    #   # Changes the mapping for TIMESTAMP WITH LOCAL TIME ZONE
+    #   OCI8::BindType::Mapping[OCI8::SQLT_TIMESTAMP_LTZ] = OCI8::BindType::DateTime
     #
-    # To bind a DateTime[http://www.ruby-doc.org/core/classes/DateTime.html]
-    # value implicitly:
+    # === Note for default time zone
     #
-    #   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 retrieved value's time zone is determined by the session time zone
+    # if its data type is <tt>DATE</tt>, <tt>TIMESTAMP</tt> or <tt>TIMESTAMP
+    # WITH LOCAL TIME ZONE</tt>.
     #
-    # The bind variable <code>:2</code> is bound as <tt>TIMESTAMP WITH TIME ZONE</tt> on Oracle.
+    # The session time zone is same with local machine's by default.
+    # It is changed by the following SQL.
     #
-    # To bind explicitly:
+    #   ALTER SESSION SET TIME_ZONE='-05:00'
     #
-    #   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
+    # === Note for Oracle 8.x client
     #
-    # 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.
+    # Timestamp data types and session time zone are new features in
+    # Oracle 9i. This class is available only to fetch or bind <tt>DATE</tt>
+    # when using Oracle 8.x client.
     #
-    # 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 retrieved value's time zone is determined not by the session
+    # time zone, but by the OCI8::BindType.default_timezone
+    # The time zone can be changed as follows:
     #
-    # The output value of the bind varible is always a
-    # DateTime[http://www.ruby-doc.org/core/classes/DateTime.html].
+    #  OCI8::BindType.default_timezone = :local
+    #  # or
+    #  OCI8::BindType.default_timezone = :utc
     #
-    #   cursor = conn.exec("BEGIN :ts := current_timestamp; END")
-    #   cursor.bind_param(:ts, nil, DateTime)
-    #   cursor.exec
-    #   cursor[:ts] # => a DateTime.
-    #   cursor.close
+    # If you are in the regions where daylight saving time is adopted,
+    # you should use OCI8::BindType::Time.
     #
     class DateTime
       if OCI8.oracle_client_version >= ORAVER_9_0
-        def self.create(con, val, param, max_array_size)
+        def self.create(con, val, param, max_array_size) # :nodoc:
           if true # TODO: check Oracle server version
             DateTimeViaOCITimestamp.new(con, val, param, max_array_size)
           else
@@ -314,15 +337,78 @@
           end
         end
       else
-        def self.create(con, val, param, max_array_size)
+        def self.create(con, val, param, max_array_size) # :nodoc:
           DateTimeViaOCIDate.new(con, val, param, max_array_size)
         end
       end
     end
 
+    #--
+    # OCI8::BindType::Time
+    #++
+    # This is a helper class to select or bind Oracle data types such as
+    # <tt>DATE</tt>, <tt>TIMESTAMP</tt>, <tt>TIMESTAMP WITH TIME ZONE</tt>
+    # and <tt>TIMESTAMP WITH LOCAL TIME ZONE</tt>. The retrieved value
+    # is a \Time.
+    #
+    # === How to select \Time values.
+    #
+    # <tt>DATE</tt>, <tt>TIMESTAMP</tt>, <tt>TIMESTAMP WITH TIME ZONE</tt>
+    # and <tt>TIMESTAMP WITH LOCAL TIME ZONE</tt> are selected as a \Time
+    # by default. If the default behaviour is changed, you can select it
+    # as a \Time by explicitly calling OCI8::Cursor#define as follows:
+    #
+    #   cursor = conn.parse("SELECT hiredate FROM emp")
+    #   cursor.define(1, nil, Time)
+    #   cursor.exec()
+    #
+    # === Note for ruby prior to 1.9.2
+    #
+    # If the retrieved value cannot be represented by \Time, it become
+    # a \DateTime. The fallback is done only when the ruby is before 1.9.2
+    # and one of the following conditions are met.
+    # - The timezone part is neither local nor utc.
+    # - The time is out of the time_t[http://en.wikipedia.org/wiki/Time_t].
+    #
+    # If the retrieved value has the precision of fractional second more
+    # than 6, the fractional second is truncated to microsecond, which
+    # is the precision of standard \Time class.
+    #
+    # To avoid this fractional second truncation:
+    # - Upgrade to ruby 1.9.2, whose \Time precision is nanosecond.
+    # - Otherwise, change the defalt mapping to use \DateTime as follows.
+    #    OCI8::BindType::Mapping[OCI8::SQLT_TIMESTAMP] = OCI8::BindType::DateTime
+    #    OCI8::BindType::Mapping[OCI8::SQLT_TIMESTAMP_TZ] = OCI8::BindType::DateTime
+    #    OCI8::BindType::Mapping[OCI8::SQLT_TIMESTAMP_LTZ] = OCI8::BindType::DateTime
+    #
+    # === Note for default time zone
+    #
+    # The retrieved value's time zone is determined by the session time zone
+    # if its data type is <tt>DATE</tt>, <tt>TIMESTAMP</tt> or <tt>TIMESTAMP
+    # WITH LOCAL TIME ZONE</tt>.
+    #
+    # The session time zone is same with local machine's by default.
+    # It is changed by the following SQL.
+    #
+    #   ALTER SESSION SET TIME_ZONE='-05:00'
+    #
+    # === Note for Oracle 8.x client
+    #
+    # Timestamp data types and session time zone are new features in
+    # Oracle 9i. This class is available only to fetch or bind <tt>DATE</tt>
+    # when using Oracle 8.x client.
+    #
+    # The retrieved value's time zone is determined not by the session
+    # time zone, but by the OCI8::BindType.default_timezone
+    # The time zone can be changed as follows:
+    #
+    #  OCI8::BindType.default_timezone = :local
+    #  # or
+    #  OCI8::BindType.default_timezone = :utc
+    #
     class Time
       if OCI8.oracle_client_version >= ORAVER_9_0
-        def self.create(con, val, param, max_array_size)
+        def self.create(con, val, param, max_array_size) # :nodoc:
           if true # TODO: check Oracle server version
             TimeViaOCITimestamp.new(con, val, param, max_array_size)
           else
@@ -330,7 +416,7 @@
           end
         end
       else
-        def self.create(con, val, param, max_array_size)
+        def self.create(con, val, param, max_array_size) # :nodoc:
           TimeViaOCIDate.new(con, val, param, max_array_size)
         end
       end

Added: trunk/ruby-oci8/spec/fetch_datetime8_spec.rb
===================================================================
--- trunk/ruby-oci8/spec/fetch_datetime8_spec.rb	                        (rev 0)
+++ trunk/ruby-oci8/spec/fetch_datetime8_spec.rb	2009-09-22 10:46:31 UTC (rev 359)
@@ -0,0 +1,221 @@
+#
+# Specs for OCI8::Cursor when fetching date, timestamp, timestamp with
+# time zone and tiemstamp with local time zone as DateTime by Oracle 8.x API
+#
+require File.join(File.dirname(__FILE__), 'spec_helper.rb')
+
+module FetchDateTime8Helper
+  def before_all
+    @conn = get_oracle_connection()
+    @default_timezone = @conn.select_one("select sessiontimezone from dual")[0]
+    OCI8::BindType::Mapping[:date] = OCI8::BindType::DateTimeViaOCIDate
+    OCI8::BindType::Mapping[:timestamp] = OCI8::BindType::DateTimeViaOCIDate
+    OCI8::BindType::Mapping[:timestamp_tz] = OCI8::BindType::DateTimeViaOCIDate
+    OCI8::BindType::Mapping[:timestamp_ltz] = OCI8::BindType::DateTimeViaOCIDate
+  end
+
+  def after_all
+    OCI8::BindType::Mapping[:date] = OCI8::BindType::Time
+    OCI8::BindType::Mapping[:timestamp] = OCI8::BindType::Time
+    OCI8::BindType::Mapping[:timestamp_tz] = OCI8::BindType::Time
+    OCI8::BindType::Mapping[:timestamp_ltz] = OCI8::BindType::Time
+    @conn.logoff
+  end
+
+  @@local_timezone = DateTime.now.zone
+
+  def local_timezone
+    @@local_timezone
+  end
+end
+
+describe OCI8::Cursor, "when fetching a date as DateTime by Oracle 8.x API" do
+  include FetchDateTime8Helper
+
+  before :all do
+    before_all
+  end
+
+  after :all do
+    after_all
+  end
+
+  def fetch_a_date(str, fmt = 'yyyy-mm-dd hh24:mi:ss')
+    cursor = @conn.exec("select to_date(:1, :2) from dual", str, fmt)
+    begin
+      cursor.column_metadata[0].data_type.should eql(:date)
+      cursor.fetch[0]
+    ensure
+      cursor.close
+    end
+  end
+
+  it "should get a DateTime whose time zone is OCI8::BindType.default_timezone" do
+    begin
+      [:utc, :local].each do |tz|
+        OCI8::BindType.default_timezone = tz
+        result = fetch_a_date('2009-10-11 12:13:14')
+        expected_val = DateTime.parse("2009-10-11T12:13:14#{tz == :utc ? '+00:00' : local_timezone}")
+        result.should eql(expected_val)
+        result.offset.to_r.should eql(expected_val.offset.to_r)
+      end
+    ensure
+      OCI8::BindType.default_timezone = :local
+    end
+  end
+end
+
+describe OCI8::Cursor, "when fetching a timestamp as DateTime by Oracle 8.x API" do
+  include FetchDateTime8Helper
+
+  before :all do
+    before_all
+  end
+
+  after :all do
+    after_all
+  end
+
+  def fetch_a_timestamp(str, fmt = 'yyyy-mm-dd hh24:mi:ss')
+    cursor = @conn.exec("select to_timestamp(:1, :2) from dual", str, fmt)
+    begin
+      cursor.column_metadata[0].data_type.should eql(:timestamp)
+      cursor.fetch[0]
+    ensure
+      cursor.close
+    end
+  end
+
+  it "should get a DateTime whose time zone is OCI8::BindType.default_timezone" do
+    begin
+      [:utc, :local].each do |tz|
+        OCI8::BindType.default_timezone = tz
+        result = fetch_a_timestamp('2009-10-11 12:13:14')
+        expected_val = DateTime.parse("2009-10-11T12:13:14#{tz == :utc ? '+00:00' : local_timezone}")
+        result.should eql(expected_val)
+        result.offset.to_r.should eql(expected_val.offset.to_r)
+      end
+    ensure
+      OCI8::BindType.default_timezone = :local
+    end
+  end
+
+  it "should get a DateTime whose fractional seconds are truncated" do
+    begin
+      ['2009-10-11 12:13:14',
+       '2009-10-11 12:13:14.000000001',
+       '2009-10-11 12:13:14.999999999'].each do |time_str|
+        result = fetch_a_timestamp(time_str, 'yyyy-mm-dd hh24:mi:ss.ff9')
+        expected_val = DateTime.parse("2009-10-11 12:13:14#{local_timezone}")
+        result.should eql(expected_val)
+        result.offset.to_r.should eql(expected_val.offset.to_r)
+      end
+    ensure
+      OCI8::BindType.default_timezone = :local
+    end
+  end
+end
+
+describe OCI8::Cursor, "when fetching a timestamp with time zone as DateTime by Oracle 8.0 API" do
+  include FetchDateTime8Helper
+
+  before :all do
+    before_all
+  end
+
+  after :all do
+    after_all
+  end
+
+  # fetch a timestamp with time zone
+  def fetch_a_timestamp_tz(str, fmt = 'yyyy-mm-dd hh24:mi:ss tzh:tzm')
+    cursor = @conn.exec("select to_timestamp_tz(:1, :2) from dual", str, fmt)
+    begin
+      cursor.column_metadata[0].data_type.should eql(:timestamp_tz)
+      cursor.fetch[0]
+    ensure
+      cursor.close
+    end
+  end
+
+  it "should get a DateTime whose time zone is OCI8::BindType.default_timezone" do
+    begin
+      [:utc, :local].each do |tz|
+        OCI8::BindType.default_timezone = tz
+        result = fetch_a_timestamp_tz('2009-10-11 12:13:14 -07:00')
+        expected_val = DateTime.parse("2009-10-11T12:13:14#{tz == :utc ? '+00:00' : local_timezone}")
+        result.should eql(expected_val)
+        result.offset.to_r.should eql(expected_val.offset.to_r)
+      end
+    ensure
+      OCI8::BindType.default_timezone = :local
+    end
+  end
+
+  it "should get a DateTime whose fractional seconds are truncated" do
+    begin
+      ['2009-10-11 12:13:14',
+       '2009-10-11 12:13:14.000000001',
+       '2009-10-11 12:13:14.999999999'].each do |time_str|
+        result = fetch_a_timestamp_tz(time_str, 'yyyy-mm-dd hh24:mi:ss.ff9')
+        expected_val = DateTime.parse("2009-10-11T12:13:14#{local_timezone}")
+        result.should eql(expected_val)
+        result.offset.to_r.should eql(expected_val.offset.to_r)
+      end
+    ensure
+      OCI8::BindType.default_timezone = :local
+    end
+  end
+end
+
+describe OCI8::Cursor, "when fetching a timestamp with local time zone as DateTime by Oracle 8.x API" do
+  include FetchDateTime8Helper
+
+  before :all do
+    before_all
+  end
+
+  after :all do
+    after_all
+  end
+
+  # fetch a timestamp with local time zone
+  def fetch_a_timestamp_ltz(str, fmt = 'yyyy-mm-dd hh24:mi:ss tzh:tzm')
+    cursor = @conn.exec("select cast(to_timestamp_tz(:1, :2) as timestamp(9) with local time zone) from dual", str, fmt)
+    begin
+      cursor.column_metadata[0].data_type.should eql(:timestamp_ltz)
+      timestamp = cursor.fetch[0]
+    ensure
+      cursor.close
+    end
+  end
+
+  it "should get a DateTime whose time zone is OCI8::BindType.default_timezone" do
+    begin
+      [:utc, :local].each do |tz|
+        OCI8::BindType.default_timezone = tz
+        result = fetch_a_timestamp_ltz('2009-10-11 12:13:14')
+        expected_val = DateTime.parse("2009-10-11T12:13:14#{tz == :utc ? '+00:00' : local_timezone}")
+        result.should eql(expected_val)
+        result.offset.to_r.should eql(expected_val.offset.to_r)
+      end
+    ensure
+      OCI8::BindType.default_timezone = :local
+    end
+  end
+
+  it "should get a DateTime whose fractional seconds are truncated" do
+    begin
+      ['2009-10-11 12:13:14',
+       '2009-10-11 12:13:14.000000001',
+       '2009-10-11 12:13:14.999999999'].each do |time_str|
+        result = fetch_a_timestamp_ltz(time_str, 'yyyy-mm-dd hh24:mi:ss.ff9')
+        expected_val = DateTime.parse("2009-10-11T12:13:14#{local_timezone}")
+        result.should eql(expected_val)
+        result.offset.to_r.should eql(expected_val.offset.to_r)
+      end
+    ensure
+      OCI8::BindType.default_timezone = :local
+    end
+  end
+end

Added: trunk/ruby-oci8/spec/fetch_datetime_spec.rb
===================================================================
--- trunk/ruby-oci8/spec/fetch_datetime_spec.rb	                        (rev 0)
+++ trunk/ruby-oci8/spec/fetch_datetime_spec.rb	2009-09-22 10:46:31 UTC (rev 359)
@@ -0,0 +1,195 @@
+#
+# Specs for OCI8::Cursor when fetching date, timestamp, timestamp with
+# time zone and tiemstamp with local time zone as DateTime
+#
+require File.join(File.dirname(__FILE__), 'spec_helper.rb')
+
+module FetchDateTimeHelper
+  def before_all
+    @conn = get_oracle_connection()
+    @default_timezone = @conn.select_one("select sessiontimezone from dual")[0]
+    OCI8::BindType::Mapping[:date] = OCI8::BindType::DateTime
+    OCI8::BindType::Mapping[:timestamp] = OCI8::BindType::DateTime
+    OCI8::BindType::Mapping[:timestamp_tz] = OCI8::BindType::DateTime
+    OCI8::BindType::Mapping[:timestamp_ltz] = OCI8::BindType::DateTime
+  end
+
+  def after_all
+    OCI8::BindType::Mapping[:date] = OCI8::BindType::Time
+    OCI8::BindType::Mapping[:timestamp] = OCI8::BindType::Time
+    OCI8::BindType::Mapping[:timestamp_tz] = OCI8::BindType::Time
+    OCI8::BindType::Mapping[:timestamp_ltz] = OCI8::BindType::Time
+    @conn.logoff
+  end
+
+end
+
+describe OCI8::Cursor, "when fetching a date as DateTime" do
+  include FetchDateTimeHelper
+
+  before :all do
+    before_all
+  end
+
+  after :all do
+    after_all
+  end
+
+  def fetch_a_date(str, fmt = 'yyyy-mm-dd hh24:mi:ss')
+    cursor = @conn.exec("select to_date(:1, :2) from dual", str, fmt)
+    begin
+      cursor.column_metadata[0].data_type.should eql(:date)
+      cursor.fetch[0]
+    ensure
+      cursor.close
+    end
+  end
+
+  it "should get a DateTime of local time if the session time zone is local" do
+    result = fetch_a_date('2009-10-11 12:13:14')
+    expected_val = DateTime.parse("2009-10-11T12:13:14#{@default_timezone}")
+    result.should eql(expected_val)
+    result.offset.should eql(expected_val.offset)
+  end
+
+  it "should get a DateTime of any session time zone" do
+    begin
+      ['+09:00', '+00:00', '-05:00'].each do |timezone|
+        @conn.exec("alter session set time_zone = '#{timezone}'")
+        result = fetch_a_date('2009-10-11 12:13:14')
+        expected_val = DateTime.parse("2009-10-11T12:13:14#{timezone}")
+        result.should eql(expected_val)
+        result.offset.should eql(expected_val.offset)
+      end
+    ensure
+      @conn.exec("alter session set time_zone = '#{@default_timezone}'")
+    end
+  end
+end
+
+describe OCI8::Cursor, "when fetching a timestamp as DateTime" do
+  include FetchDateTimeHelper
+
+  before :all do
+    before_all
+  end
+
+  after :all do
+    after_all
+  end
+
+  def fetch_a_timestamp(str, fmt = 'yyyy-mm-dd hh24:mi:ss.ff9')
+    cursor = @conn.exec("select to_timestamp(:1, :2) from dual", str, fmt)
+    begin
+      cursor.column_metadata[0].data_type.should eql(:timestamp)
+      cursor.fetch[0]
+    ensure
+      cursor.close
+    end
+  end
+
+  it "should get a DateTime of local time if the session time zone is local" do
+    result = fetch_a_timestamp('2009-10-11 12:13:14.123456789')
+    expected_val = DateTime.parse("2009-10-11T12:13:14.123456789#{@default_timezone}")
+    result.should eql(expected_val)
+    result.offset.should eql(expected_val.offset)
+  end
+
+  it "should get a DateTime of any session time zone" do
+    begin
+      ['+09:00', '+00:00', '-05:00'].each do |timezone|
+        @conn.exec("alter session set time_zone = '#{timezone}'")
+        result = fetch_a_timestamp('2009-10-11 12:13:14.999999999')
+        expected_val = DateTime.parse("2009-10-11T12:13:14.999999999#{timezone}")
+        result.should eql(expected_val)
+        result.offset.should eql(expected_val.offset)
+      end
+    ensure
+      @conn.exec("alter session set time_zone = '#{@default_timezone}'")
+    end
+  end
+end
+
+describe OCI8::Cursor, "when fetching a timestamp with time zone as DateTime" do
+  include FetchDateTimeHelper
+
+  before :all do
+    before_all
+  end
+
+  after :all do
+    after_all
+  end
+
+  # fetch a timestamp with time zone
+  def fetch_a_timestamp_tz(str, fmt = 'yyyy-mm-dd hh24:mi:ss.ff9 tzh:tzm')
+    cursor = @conn.exec("select to_timestamp_tz(:1, :2) from dual", str, fmt)
+    begin
+      cursor.column_metadata[0].data_type.should eql(:timestamp_tz)
+      cursor.fetch[0]
+    ensure
+      cursor.close
+    end
+  end
+
+  it "should get a DateTime of local time if the time zone is local" do
+    result = fetch_a_timestamp_tz("2009-10-11 12:13:14.123456789")
+    expected_val = DateTime.parse("2009-10-11T12:13:14.123456789#{@default_timezone}")
+    result.should eql(expected_val)
+    result.offset.should eql(expected_val.offset)
+  end
+
+  it "should get a DateTime of any session time zone" do
+    ['+09:00', '+00:00', '-05:00'].each do |timezone|
+      result = fetch_a_timestamp_tz("2009-10-11 12:13:14.999999999 #{timezone}")
+      expected_val = DateTime.parse("2009-10-11T12:13:14.999999999#{timezone}")
+      result.should eql(expected_val)
+      result.offset.should eql(expected_val.offset)
+    end
+  end
+end
+
+describe OCI8::Cursor, "when fetching a timestamp with local time zone as DateTime" do
+  include FetchDateTimeHelper
+
+  before :all do
+    before_all
+  end
+
+  after :all do
+    after_all
+  end
+
+  # fetch a timestamp with local time zone
+  def fetch_a_timestamp_ltz(str, fmt = 'yyyy-mm-dd hh24:mi:ss.ff9 tzh:tzm')
+    cursor = @conn.exec("select cast(to_timestamp_tz(:1, :2) as timestamp(9) with local time zone) from dual", str, fmt)
+    begin
+      cursor.column_metadata[0].data_type.should eql(:timestamp_ltz)
+      timestamp = cursor.fetch[0]
+    ensure
+      cursor.close
+    end
+  end
+
+  it "should get a DateTime of local time if the time zone is local" do
+    result = fetch_a_timestamp_ltz("2009-10-11 12:13:14.123456789")
+    expected_val = DateTime.parse("2009-10-11T12:13:14.123456789#{@default_timezone}")
+    result.should eql(expected_val)
+    result.offset.should eql(expected_val.offset)
+  end
+
+  it "should get a DateTime of any session time zone" do
+    begin
+      ['+09:00', '+00:00', '-05:00'].each do |timezone|
+        @conn.exec("alter session set time_zone = '#{timezone}'")
+        result = fetch_a_timestamp_ltz("2009-10-11 12:13:14.999999999 #{timezone}")
+        expected_val = DateTime.parse("2009-10-11T12:13:14.999999999#{timezone}")
+        result.should eql(expected_val)
+        result.offset.should eql(expected_val.offset)
+      end
+    ensure
+      @conn.exec("alter session set time_zone = '#{@default_timezone}'")
+    end
+  end
+
+end

Added: trunk/ruby-oci8/spec/fetch_time8_spec.rb
===================================================================
--- trunk/ruby-oci8/spec/fetch_time8_spec.rb	                        (rev 0)
+++ trunk/ruby-oci8/spec/fetch_time8_spec.rb	2009-09-22 10:46:31 UTC (rev 359)
@@ -0,0 +1,264 @@
+#
+# Specs for OCI8::Cursor when fetching date, timestamp, timestamp with
+# time zone and tiemstamp with local time zone as Time by Oracle 8.x API
+#
+require File.join(File.dirname(__FILE__), 'spec_helper.rb')
+
+module FetchTime8Helper
+  def before_all
+    @conn = get_oracle_connection()
+    @default_timezone = @conn.select_one("select sessiontimezone from dual")[0]
+    OCI8::BindType::Mapping[:date] = OCI8::BindType::TimeViaOCIDate
+    OCI8::BindType::Mapping[:timestamp] = OCI8::BindType::TimeViaOCIDate
+    OCI8::BindType::Mapping[:timestamp_tz] = OCI8::BindType::TimeViaOCIDate
+    OCI8::BindType::Mapping[:timestamp_ltz] = OCI8::BindType::TimeViaOCIDate
+  end
+
+  def after_all
+    OCI8::BindType::Mapping[:date] = OCI8::BindType::Time
+    OCI8::BindType::Mapping[:timestamp] = OCI8::BindType::Time
+    OCI8::BindType::Mapping[:timestamp_tz] = OCI8::BindType::Time
+    OCI8::BindType::Mapping[:timestamp_ltz] = OCI8::BindType::Time
+    @conn.logoff
+  end
+
+end
+
+describe OCI8::Cursor, "when fetching a date as Time by Oracle 8.x API" do
+  include FetchTime8Helper
+
+  before :all do
+    before_all
+  end
+
+  after :all do
+    after_all
+  end
+
+  def fetch_a_date(str, fmt = 'yyyy-mm-dd hh24:mi:ss')
+    cursor = @conn.exec("select to_date(:1, :2) from dual", str, fmt)
+    begin
+      cursor.column_metadata[0].data_type.should eql(:date)
+      cursor.fetch[0]
+    ensure
+      cursor.close
+    end
+  end
+
+  it "should get a Time whose time zone is OCI8::BindType.default_timezone" do
+    begin
+      [:utc, :local].each do |tz|
+        OCI8::BindType.default_timezone = tz
+        result = fetch_a_date('2009-10-11 12:13:14')
+        expected_val = Time.send(tz, 2009, 10, 11, 12, 13, 14)
+        result.should eql(expected_val)
+        result.utc_offset.should eql(expected_val.utc_offset)
+      end
+    ensure
+      OCI8::BindType.default_timezone = :local
+    end
+  end
+
+  it "should get a Time if the year is in the valid year range, otherwise a DateTime" do
+    ['1888', '1971', '1972', '2038', '2039', '9999', '-4711'].each do |year|
+      timestamp = fetch_a_date("#{year}-01-01", 'syyyy-mm-dd')
+      begin
+        Time.local(year)
+        timestamp.should eql(Time.local(year))
+      rescue ArgumentError
+        timestamp.should eql(DateTime.parse("#{year}-01-01T00:00#{DateTime.now.zone}"))
+      end
+    end
+  end
+end
+
+describe OCI8::Cursor, "when fetching a timestamp as Time by Oracle 8.x API" do
+  include FetchTime8Helper
+
+  before :all do
+    before_all
+  end
+
+  after :all do
+    after_all
+  end
+
+  def fetch_a_timestamp(str, fmt = 'yyyy-mm-dd hh24:mi:ss')
+    cursor = @conn.exec("select to_timestamp(:1, :2) from dual", str, fmt)
+    begin
+      cursor.column_metadata[0].data_type.should eql(:timestamp)
+      cursor.fetch[0]
+    ensure
+      cursor.close
+    end
+  end
+
+  it "should get a Time whose time zone is OCI8::BindType.default_timezone" do
+    begin
+      [:utc, :local].each do |tz|
+        OCI8::BindType.default_timezone = tz
+        result = fetch_a_timestamp('2009-10-11 12:13:14')
+        expected_val = Time.send(tz, 2009, 10, 11, 12, 13, 14)
+        result.should eql(expected_val)
+        result.utc_offset.should eql(expected_val.utc_offset)
+      end
+    ensure
+      OCI8::BindType.default_timezone = :local
+    end
+  end
+
+  it "should get a Time if the year is in the valid year range, otherwise a DateTime" do
+    ['1888', '1971', '1972', '2038', '2039', '9999', '-4711'].each do |year|
+      timestamp = fetch_a_timestamp("#{year}-01-01", 'syyyy-mm-dd')
+      begin
+        Time.local(year)
+        timestamp.should eql(Time.local(year))
+      rescue ArgumentError
+        timestamp.should eql(DateTime.parse("#{year}-01-01T00:00#{DateTime.now.zone}"))
+      end
+    end
+  end
+
+  it "should get a Time whose fractional seconds are truncated" do
+    begin
+      ['2009-10-11 12:13:14',
+       '2009-10-11 12:13:14.000000001',
+       '2009-10-11 12:13:14.999999999'].each do |time_str|
+        result = fetch_a_timestamp(time_str, 'yyyy-mm-dd hh24:mi:ss.ff9')
+        expected_val = Time.local(2009, 10, 11, 12, 13, 14)
+        result.should eql(expected_val)
+        result.utc_offset.should eql(expected_val.utc_offset)
+      end
+    ensure
+      OCI8::BindType.default_timezone = :local
+    end
+  end
+end
+
+describe OCI8::Cursor, "when fetching a timestamp with time zone as Time by Oracle 8.0 API" do
+  include FetchTime8Helper
+
+  before :all do
+    before_all
+  end
+
+  after :all do
+    after_all
+  end
+
+  # fetch a timestamp with time zone
+  def fetch_a_timestamp_tz(str, fmt = 'yyyy-mm-dd hh24:mi:ss tzh:tzm')
+    cursor = @conn.exec("select to_timestamp_tz(:1, :2) from dual", str, fmt)
+    begin
+      cursor.column_metadata[0].data_type.should eql(:timestamp_tz)
+      cursor.fetch[0]
+    ensure
+      cursor.close
+    end
+  end
+
+  it "should get a Time whose time zone is OCI8::BindType.default_timezone" do
+    begin
+      [:utc, :local].each do |tz|
+        OCI8::BindType.default_timezone = tz
+        result = fetch_a_timestamp_tz('2009-10-11 12:13:14 -07:00')
+        expected_val = Time.send(tz, 2009, 10, 11, 12, 13, 14)
+        result.should eql(expected_val)
+        result.utc_offset.should eql(expected_val.utc_offset)
+      end
+    ensure
+      OCI8::BindType.default_timezone = :local
+    end
+  end
+
+  it "should get a Time if the year is in the valid year range, otherwise a DateTime" do
+    ['1888', '1971', '1972', '2038', '2039', '9999', '-4711'].each do |year|
+      timestamp = fetch_a_timestamp_tz("#{year}-01-01", 'syyyy-mm-dd')
+      begin
+        Time.local(year)
+        timestamp.should eql(Time.local(year))
+      rescue ArgumentError
+        timestamp.should eql(DateTime.parse("#{year}-01-01T00:00#{DateTime.now.zone}"))
+      end
+    end
+  end
+
+  it "should get a Time whose fractional seconds are truncated" do
+    begin
+      ['2009-10-11 12:13:14',
+       '2009-10-11 12:13:14.000000001',
+       '2009-10-11 12:13:14.999999999'].each do |time_str|
+        result = fetch_a_timestamp_tz(time_str, 'yyyy-mm-dd hh24:mi:ss.ff9')
+        expected_val = Time.local(2009, 10, 11, 12, 13, 14)
+        result.should eql(expected_val)
+        result.utc_offset.should eql(expected_val.utc_offset)
+      end
+    ensure
+      OCI8::BindType.default_timezone = :local
+    end
+  end
+end
+
+describe OCI8::Cursor, "when fetching a timestamp with local time zone as Time by Oracle 8.x API" do
+  include FetchTime8Helper
+
+  before :all do
+    before_all
+  end
+
+  after :all do
+    after_all
+  end
+
+  # fetch a timestamp with local time zone
+  def fetch_a_timestamp_ltz(str, fmt = 'yyyy-mm-dd hh24:mi:ss tzh:tzm')
+    cursor = @conn.exec("select cast(to_timestamp_tz(:1, :2) as timestamp(9) with local time zone) from dual", str, fmt)
+    begin
+      cursor.column_metadata[0].data_type.should eql(:timestamp_ltz)
+      timestamp = cursor.fetch[0]
+    ensure
+      cursor.close
+    end
+  end
+
+  it "should get a Time whose time zone is OCI8::BindType.default_timezone" do
+    begin
+      [:utc, :local].each do |tz|
+        OCI8::BindType.default_timezone = tz
+        result = fetch_a_timestamp_ltz('2009-10-11 12:13:14')
+        expected_val = Time.send(tz, 2009, 10, 11, 12, 13, 14)
+        result.should eql(expected_val)
+        result.utc_offset.should eql(expected_val.utc_offset)
+      end
+    ensure
+      OCI8::BindType.default_timezone = :local
+    end
+  end
+
+  it "should get a Time if the year is in the valid year range, otherwise a DateTime" do
+    ['1888', '1971', '1972', '2038', '2039', '9999', '-4711'].each do |year|
+      timestamp = fetch_a_timestamp_ltz("#{year}-01-01", 'syyyy-mm-dd')
+      begin
+        Time.local(year)
+        timestamp.should eql(Time.local(year))
+      rescue ArgumentError
+        timestamp.should eql(DateTime.parse("#{year}-01-01T00:00#{DateTime.now.zone}"))
+      end
+    end
+  end
+
+  it "should get a Time whose fractional seconds are truncated" do
+    begin
+      ['2009-10-11 12:13:14',
+       '2009-10-11 12:13:14.000000001',
+       '2009-10-11 12:13:14.999999999'].each do |time_str|
+        result = fetch_a_timestamp_ltz(time_str, 'yyyy-mm-dd hh24:mi:ss.ff9')
+        expected_val = Time.local(2009, 10, 11, 12, 13, 14)
+        result.should eql(expected_val)
+        result.utc_offset.should eql(expected_val.utc_offset)
+      end
+    ensure
+      OCI8::BindType.default_timezone = :local
+    end
+  end
+end

Added: trunk/ruby-oci8/spec/fetch_time_spec.rb
===================================================================
--- trunk/ruby-oci8/spec/fetch_time_spec.rb	                        (rev 0)
+++ trunk/ruby-oci8/spec/fetch_time_spec.rb	2009-09-22 10:46:31 UTC (rev 359)
@@ -0,0 +1,357 @@
+#
+# Specs for OCI8::Cursor when fetching date, timestamp, timestamp with
+# time zone and tiemstamp with local time zone as Time
+#
+require File.join(File.dirname(__FILE__), 'spec_helper.rb')
+
+module FetchTimeHelper
+  def before_all
+    @conn = get_oracle_connection()
+    @default_timezone = @conn.select_one("select sessiontimezone from dual")[0]
+  end
+
+  def after_all
+    @conn.logoff
+  end
+end
+
+
+describe OCI8::Cursor, "when fetching a date as Time" do
+  include FetchTimeHelper
+
+  before :all do
+    before_all
+  end
+
+  after :all do
+    after_all
+  end
+
+  def fetch_a_date(str, fmt = 'yyyy-mm-dd hh24:mi:ss')
+    cursor = @conn.exec("select to_date(:1, :2) from dual", str, fmt)
+    begin
+      cursor.column_metadata[0].data_type.should eql(:date)
+      cursor.fetch[0]
+    ensure
+      cursor.close
+    end
+  end
+
+  it "should get a Time if the session time zone is local" do
+    result = fetch_a_date('2009-10-11 12:13:14')
+    result.should eql(Time.local(2009, 10, 11, 12, 13, 14))
+    result.utc_offset.should eql(Time.now.utc_offset)
+  end
+
+  it "should get a Time if the session time zone is UTC" do
+    begin
+      @conn.exec("alter session set time_zone = '00:00'")
+      result = fetch_a_date('2009-10-11 12:13:14')
+      result.should eql(Time.utc(2009, 10, 11, 12, 13, 14))
+      result.utc_offset.should eql(0)
+    ensure
+      @conn.exec("alter session set time_zone = '#{@default_timezone}'")
+    end
+  end
+
+  it "should get a DateTime if the session time zone is neither local nor UTC prior to ruby 1.9.2" do
+    if RUBY_VERSION < '1.9.2'
+      begin
+        ['+09:00', '-05:00', '-07:00'].each do |timezone|
+          next if timezone == @default_timezone
+          @conn.exec("alter session set time_zone = '#{timezone}'")
+          result = fetch_a_date('2009-10-11 12:13:14')
+          result.should eql(DateTime.parse("2009-10-11T12:13:14#{timezone}"))
+          result.zone.should eql(timezone)
+        end
+      ensure
+        @conn.exec("alter session set time_zone = '#{@default_timezone}'")
+      end
+    end
+  end
+
+  it "should get a Time in any session time zone after ruby 1.9.2" do
+    if RUBY_VERSION >= '1.9.2'
+      begin
+        timezone = ['+09:00', '-05:00'].detect {|tz| tz != @default_timezone}
+        @conn.exec("alter session set time_zone = '#{timezone}'")
+        result = fetch_a_date('2009-10-11 12:13:14')
+        result.should eql(Time.new(2009, 10, 11, 12, 13, 14, timezone))
+        result.utc_offset.should eql(Time.new.getlocal(timezone).utc_offset)
+      ensure
+        @conn.exec("alter session set time_zone = '#{@default_timezone}'")
+      end
+    end
+  end
+
+  it "should get a Time if the year is in the valid year range, otherwise a DateTime" do
+    ['1888', '1971', '1972', '2038', '2039', '9999', '-4711', '-1', '0001', '0138', '0139'].each do |year|
+      result = fetch_a_date("#{year}-01-01", 'syyyy-mm-dd')
+      expected_val = nil
+      if RUBY_VERSION >= '1.9.2'
+        expected_val = Time.new(year, 1, 1, 0, 0, 0, @default_timezone)
+      else
+        begin
+          expected_val = Time.local(year)
+        rescue ArgumentError
+        end
+        if expected_val.nil? ||
+            expected_val.year != year.to_i ||
+            expected_val.strftime('%z') != @default_timezone.gsub(/:/, '') then
+          expected_val = DateTime.parse("#{year}-01-01T00:00#{@default_timezone}")
+        end
+      end
+      result.should eql(expected_val)
+    end
+  end
+end
+
+describe OCI8::Cursor, "when fetching a timestamp as Time" do
+  include FetchTimeHelper
+
+  before :all do
+    before_all
+  end
+
+  after :all do
+    after_all
+  end
+
+  def fetch_a_timestamp(str, fmt = 'yyyy-mm-dd hh24:mi:ss')
+    cursor = @conn.exec("select to_timestamp(:1, :2) from dual", str, fmt)
+    begin
+      cursor.column_metadata[0].data_type.should eql(:timestamp)
+      cursor.fetch[0]
+    ensure
+      cursor.close
+    end
+  end
+
+  it "should get a Time whose precision is microsecond if the ruby is 1.8" do
+    result = fetch_a_timestamp('2009-10-11 12:13:14.123456789', 'yyyy-mm-dd hh24:mi:ss.ff9')
+    result.usec.should eql(123456)
+    result = fetch_a_timestamp('2009-10-11 12:13:59.999999999', 'yyyy-mm-dd hh24:mi:ss.ff9')
+    result.usec.should eql(999999)
+  end
+
+  it "should get a Time whose precision is nanosecond if the ruby is 1.9" do
+    next unless Time.public_method_defined? :nsec
+    result = fetch_a_timestamp('2009-10-11 12:13:14.123456789', 'yyyy-mm-dd hh24:mi:ss.ff9')
+    result.nsec.should eql(123456789)
+    result = fetch_a_timestamp('2009-10-11 12:13:59.999999999', 'yyyy-mm-dd hh24:mi:ss.ff9')
+    result.nsec.should eql(999999999)
+  end
+
+  it "should get a Time if the session time zone is local" do
+    result = fetch_a_timestamp('2009-10-11 12:13:14')
+    result.should eql(Time.local(2009, 10, 11, 12, 13, 14))
+    result.utc_offset.should eql(Time.now.utc_offset)
+  end
+
+  it "should get a Time if the session time zone is UTC" do
+    begin
+      @conn.exec("alter session set time_zone = '00:00'")
+      result = fetch_a_timestamp('2009-10-11 12:13:14')
+      result.should eql(Time.utc(2009, 10, 11, 12, 13, 14))
+      result.utc_offset.should eql(0)
+    ensure
+      @conn.exec("alter session set time_zone = '#{@default_timezone}'")
+    end
+  end
+
+  it "should get a DateTime if the session time zone is neither local nor UTC prior to ruby 1.9.2" do
+    if RUBY_VERSION < '1.9.2'
+      begin
+        timezone = ['+09:00', '-05:00'].detect {|tz| tz != @default_timezone}
+        @conn.exec("alter session set time_zone = '#{timezone}'")
+        result = fetch_a_timestamp('2009-10-11 12:13:14')
+        result.should eql(DateTime.parse("2009-10-11T12:13:14#{timezone}"))
+        result.zone.should eql(timezone)
+      ensure
+        @conn.exec("alter session set time_zone = '#{@default_timezone}'")
+      end
+    end
+  end
+
+  it "should get a Time in any session time zone after ruby 1.9.2" do
+    if RUBY_VERSION >= '1.9.2'
+      begin
+        timezone = ['+09:00', '-05:00'].detect {|tz| tz != @default_timezone}
+        @conn.exec("alter session set time_zone = '#{timezone}'")
+        result = fetch_a_timestamp('2009-10-11 12:13:14')
+        result.should eql(Time.new(2009, 10, 11, 12, 13, 14, timezone))
+        result.utc_offset.should eql(Time.new.getlocal(timezone).utc_offset)
+      ensure
+        @conn.exec("alter session set time_zone = '#{@default_timezone}'")
+      end
+    end
+  end
+
+  it "should get a Time if the year is in the valid year range, otherwise a DateTime" do
+    ['1888', '1971', '1972', '2038', '2039', '9999', '-4711', '-1', '0001', '0138', '0139'].each do |year|
+      result = fetch_a_timestamp("#{year}-01-01", 'syyyy-mm-dd')
+      expected_val = nil
+      if RUBY_VERSION >= '1.9.2'
+        expected_val = Time.new(year, 1, 1, 0, 0, 0, @default_timezone)
+      else
+        begin
+          expected_val = Time.local(year)
+        rescue ArgumentError
+        end
+        if expected_val.nil? ||
+            expected_val.year != year.to_i ||
+            expected_val.strftime('%z') != @default_timezone.gsub(/:/, '') then
+          expected_val = DateTime.parse("#{year}-01-01T00:00#{@default_timezone}")
+        end
+      end
+      result.should eql(expected_val)
+    end
+  end
+end
+
+describe OCI8::Cursor, "when fetching a timestamp with time zone as Time" do
+  include FetchTimeHelper
+
+  before :all do
+    before_all
+  end
+
+  after :all do
+    after_all
+  end
+
+  # fetch a timestamp with time zone
+  def fetch_a_timestamp_tz(str, fmt = 'yyyy-mm-dd hh24:mi:ss tzh:tzm')
+    cursor = @conn.exec("select to_timestamp_tz(:1, :2) from dual", str, fmt)
+    begin
+      cursor.column_metadata[0].data_type.should eql(:timestamp_tz)
+      cursor.fetch[0]
+    ensure
+      cursor.close
+    end
+  end
+
+  it "should get a Time whose precision is microsecond if the ruby is 1.8" do
+    result = fetch_a_timestamp_tz("2009-10-11 12:13:14.123456789 #{DateTime.now.zone}", 'yyyy-mm-dd hh24:mi:ss.ff9 tzh:tzm')
+    result.should be_a_kind_of(Time)
+    result.usec.should eql(123456)
+  end
+
+  it "should get a Time whose precision is nanosecond if the ruby is 1.9" do
+    next unless Time.public_method_defined? :nsec
+    result = fetch_a_timestamp_tz("2009-10-11 12:13:14.123456789 #{DateTime.now.zone}", 'yyyy-mm-dd hh24:mi:ss.ff9 tzh:tzm')
+    result.should be_a_kind_of(Time)
+    result.nsec.should eql(123456789)
+  end
+
+  it "should get a Time if the time zone is local" do
+    result = fetch_a_timestamp_tz("2009-10-11 12:13:14 #{DateTime.now.zone}")
+    result.should eql(Time.local(2009, 10, 11, 12, 13, 14))
+    result.utc_offset.should eql(Time.now.utc_offset)
+  end
+
+  it "should get a Time if the time zone is UTC" do
+    result = fetch_a_timestamp_tz("2009-10-11 12:13:14 00:00")
+    result.should eql(Time.utc(2009, 10, 11, 12, 13, 14))
+    result.utc_offset.should eql(0)
+  end
+
+  it "should get a DateTime if the time zone is neither local nor UTC prior to ruby 1.9.2" do
+    if RUBY_VERSION < '1.9.2'
+      timezone = ['+09:00', '-05:00'].detect {|tz| tz != @default_timezone}
+      result = fetch_a_timestamp_tz("2009-10-11 12:13:14 #{timezone}")
+      result.should eql(DateTime.parse("2009-10-11T12:13:14#{timezone}"))
+      result.zone.should eql(timezone)
+    end
+  end
+
+  it "should get a Time in any time zone after ruby 1.9.2" do
+    if RUBY_VERSION >= '1.9.2'
+      timezone = ['+09:00', '-05:00'].detect {|tz| tz != DateTime.now.zone}
+      result = fetch_a_timestamp_tz("2009-10-11 12:13:14 #{timezone}")
+      result.should eql(Time.new(2009, 10, 11, 12, 13, 14, timezone))
+      result.utc_offset.should eql(Time.new.getlocal(timezone).utc_offset)
+    end
+  end
+end
+
+describe OCI8::Cursor, "when fetching a timestamp with local time zone as Time" do
+  include FetchTimeHelper
+
+  before :all do
+    before_all
+  end
+
+  after :all do
+    after_all
+  end
+
+  # fetch a timestamp with local time zone
+  def fetch_a_timestamp_ltz(str, fmt = 'yyyy-mm-dd hh24:mi:ss tzh:tzm')
+    cursor = @conn.exec("select cast(to_timestamp_tz(:1, :2) as timestamp(9) with local time zone) from dual", str, fmt)
+    begin
+      cursor.column_metadata[0].data_type.should eql(:timestamp_ltz)
+      timestamp = cursor.fetch[0]
+    ensure
+      cursor.close
+    end
+  end
+
+  it "should get a Time whose precision is microsecond if the ruby is 1.8" do
+    result = fetch_a_timestamp_ltz('2009-10-11 12:13:14.123456789 00:00', 'yyyy-mm-dd hh24:mi:ss.ff9 tzh:tzm')
+    result.should be_a_kind_of(Time)
+    result.usec.should eql(123456)
+  end
+
+  it "should get a Time whose precision is nanosecond if the ruby is 1.9" do
+    next unless Time.public_method_defined? :nsec
+    result = fetch_a_timestamp_ltz('2009-10-11 12:13:14.123456789 00:00', 'yyyy-mm-dd hh24:mi:ss.ff9 tzh:tzm')
+    result.should be_a_kind_of(Time)
+    result.nsec.should eql(123456789)
+  end
+
+  it "should get a Time if the session time zone is local" do
+    result = fetch_a_timestamp_ltz('2009-10-11 12:13:14 00:00')
+    result.should eql(Time.utc(2009, 10, 11, 12, 13, 14))
+    result.utc_offset.should eql(Time.now.utc_offset)
+  end
+
+  it "should get a Time if the session time zone is UTC" do
+    begin
+      @conn.exec("alter session set time_zone = '00:00'")
+      result = fetch_a_timestamp_ltz('2009-10-11 12:13:14 00:00')
+      result.should eql(Time.utc(2009, 10, 11, 12, 13, 14))
+      result.utc_offset.should eql(0)
+    ensure
+      @conn.exec("alter session set time_zone = '#{@default_timezone}'")
+    end
+  end
+
+  it "should get a DateTime if the session time zone is neither local nor UTC prior to ruby 1.9.2" do
+    if RUBY_VERSION < '1.9.2'
+      timezone = ['+09:00', '-05:00'].detect {|tz| tz != @default_timezone}
+      begin
+        @conn.exec("alter session set time_zone = '#{timezone}'")
+        result = fetch_a_timestamp_ltz('2009-10-11 12:13:14 00:00')
+        result.should eql(DateTime.parse("2009-10-11T12:13:14+00:00"))
+        result.zone.should eql(timezone)
+      ensure
+        @conn.exec("alter session set time_zone = '#{@default_timezone}'")
+      end
+    end
+  end
+
+  it "should get a Time in any session time zone after ruby 1.9.2" do
+    if RUBY_VERSION >= '1.9.2'
+      timezone = ['+09:00', '-05:00'].detect {|tz| tz != @default_timezone}
+      begin
+        @conn.exec("alter session set time_zone = '#{timezone}'")
+        result = fetch_a_timestamp_ltz('2009-10-11 12:13:14 00:00')
+        result.should eql(Time.new(2009, 10, 11, 12, 13, 14, '+00:00'))
+        result.utc_offset.should eql(Time.new.getlocal(timezone).utc_offset)
+      ensure
+        @conn.exec("alter session set time_zone = '#{@default_timezone}'")
+      end
+    end
+  end
+end

Added: trunk/ruby-oci8/spec/spec_helper.rb
===================================================================
--- trunk/ruby-oci8/spec/spec_helper.rb	                        (rev 0)
+++ trunk/ruby-oci8/spec/spec_helper.rb	2009-09-22 10:46:31 UTC (rev 359)
@@ -0,0 +1,60 @@
+#
+# Here is user configurations
+#
+$dbuser = "ruby"
+$dbpass = "oci8"
+$dbname = nil
+
+
+############################################################
+## Don't modify below.
+############################################################
+
+unless defined? OCI8
+  $LOAD_PATH << File.join(File.dirname(__FILE__), '../lib')
+  $LOAD_PATH << File.join(File.dirname(__FILE__), '../ext/oci8')
+  require 'oci8'
+end
+
+#
+# setup $oracle_server_version
+#
+conn = OCI8.new($dbuser, $dbpass, $dbname)
+begin
+  conn.exec('select value from database_compatible_level') do |row|
+    $oracle_server_version = OCI8::OracleVersion.new(row[0])
+  end
+rescue OCIError
+  raise if $!.code != 942 # ORA-00942: table or view does not exist
+  $oracle_server_version = OCI8::ORAVER_8_0
+end
+conn.logoff
+if $oracle_server_version < OCI8.oracle_client_version
+  $oracle_version = $oracle_server_version
+else
+  $oracle_version = OCI8.oracle_client_version
+end
+
+#
+# define get_oracle_connection()
+#
+def get_oracle_connection()
+  OCI8.new($dbuser, $dbpass, $dbname)
+rescue OCIError
+  raise if $!.code != 12516 && $!.code != 12520
+  # sleep a few second and try again if
+  # the error code is ORA-12516 or ORA-12520.
+  #
+  # ORA-12516 - TNS:listener could not find available handler with
+  #             matching protocol stack
+  # ORA-12520 - TNS:listener could not find available handler for
+  #             requested type of server
+  #
+  # Thanks to Christopher Jones.
+  #
+  # Ref: The Underground PHP and Oracle Manual (page 175 in vesion 1.4)
+  #      http://www.oracle.com/technology/tech/php/pdf/underground-php-oracle-manual.pdf
+  #
+  sleep(5)
+  OCI8.new($dbuser, $dbpass, $dbname)
+end




More information about the ruby-oci8-commit mailing list