From a19888e939b2cccf6c433103533b4f8942706357 Mon Sep 17 00:00:00 2001 From: Brian Lopez Date: Fri, 20 Aug 2010 12:07:27 -0700 Subject: [PATCH] Make sure we switch over to the DateTime class for DATETIME/TIMESTAMP columns that are out of the supported range for 32bit platforms --- ext/mysql2/result.c | 34 ++++++++++++++++++++++------------ lib/mysql2.rb | 1 + lib/mysql2/client.rb | 5 +++++ spec/mysql2/result_spec.rb | 9 +++++++-- 4 files changed, 35 insertions(+), 14 deletions(-) diff --git a/ext/mysql2/result.c b/ext/mysql2/result.c index 9df4131..e237b1b 100644 --- a/ext/mysql2/result.c +++ b/ext/mysql2/result.c @@ -10,7 +10,7 @@ VALUE opt_decimal_zero, opt_float_zero, opt_time_year, opt_time_month; extern VALUE mMysql2, cMysql2Client, cMysql2Error; static VALUE intern_encoding_from_charset; static ID intern_new, intern_utc, intern_local, intern_encoding_from_charset_code, - intern_localtime; + intern_localtime, intern_local_offset, intern_civil; static ID sym_symbolize_keys, sym_as, sym_array, sym_database_timezone, sym_application_timezone, sym_local, sym_utc, sym_cast_booleans; static ID intern_merge; @@ -190,12 +190,20 @@ static VALUE rb_mysql_result_fetch_row(VALUE self, ID db_timezone, ID app_timezo rb_raise(cMysql2Error, "Invalid date: %s", row[i]); val = Qnil; } else { - val = rb_funcall(rb_cTime, db_timezone, 6, INT2NUM(year), INT2NUM(month), INT2NUM(day), INT2NUM(hour), INT2NUM(min), INT2NUM(sec)); - if (!NIL_P(app_timezone)) { - if (app_timezone == intern_local) { - val = rb_funcall(val, intern_localtime, 0); - } else { // utc - val = rb_funcall(val, intern_utc, 0); + if (year < 1902 || year+month+day > 2058) { // use DateTime instead + VALUE offset = INT2NUM(0); + if (db_timezone == intern_local) { + offset = rb_funcall(cMysql2Client, rb_intern("local_offset"), 0); + } + val = rb_funcall(cDateTime, intern_civil, 7, INT2NUM(year), INT2NUM(month), INT2NUM(day), INT2NUM(hour), INT2NUM(min), INT2NUM(sec), offset); + } else { + val = rb_funcall(rb_cTime, db_timezone, 6, INT2NUM(year), INT2NUM(month), INT2NUM(day), INT2NUM(hour), INT2NUM(min), INT2NUM(sec)); + if (!NIL_P(app_timezone)) { + if (app_timezone == intern_local) { + val = rb_funcall(val, intern_localtime, 0); + } else { // utc + val = rb_funcall(val, intern_utc, 0); + } } } } @@ -420,11 +428,13 @@ void init_mysql2_result() { intern_encoding_from_charset = rb_intern("encoding_from_charset"); intern_encoding_from_charset_code = rb_intern("encoding_from_charset_code"); - intern_new = rb_intern("new"); - intern_utc = rb_intern("utc"); - intern_local = rb_intern("local"); - intern_merge = rb_intern("merge"); - intern_localtime = rb_intern("localtime"); + intern_new = rb_intern("new"); + intern_utc = rb_intern("utc"); + intern_local = rb_intern("local"); + intern_merge = rb_intern("merge"); + intern_localtime = rb_intern("localtime"); + intern_local_offset = rb_intern("local_offset"); + intern_civil = rb_intern("civil"); sym_symbolize_keys = ID2SYM(rb_intern("symbolize_keys")); sym_as = ID2SYM(rb_intern("as")); diff --git a/lib/mysql2.rb b/lib/mysql2.rb index 5446d2f..12bc28f 100644 --- a/lib/mysql2.rb +++ b/lib/mysql2.rb @@ -1,6 +1,7 @@ # encoding: UTF-8 require 'date' require 'bigdecimal' +require 'rational' unless RUBY_VERSION >= '1.9.2' require 'mysql2/error' require 'mysql2/mysql2' diff --git a/lib/mysql2/client.rb b/lib/mysql2/client.rb index cbb6a60..732508d 100644 --- a/lib/mysql2/client.rb +++ b/lib/mysql2/client.rb @@ -224,5 +224,10 @@ module Mysql2 end end end + + private + def self.local_offset + ::Time.local(2010).utc_offset.to_r / 86400 + end end end diff --git a/spec/mysql2/result_spec.rb b/spec/mysql2/result_spec.rb index fd230cb..ee3820d 100644 --- a/spec/mysql2/result_spec.rb +++ b/spec/mysql2/result_spec.rb @@ -143,12 +143,17 @@ describe Mysql2::Result do @test_result['double_test'].should eql(10.3) end - it "should return Time for a DATETIME value" do + it "should return Time for a DATETIME value when within the supported range" do @test_result['date_time_test'].class.should eql(Time) @test_result['date_time_test'].strftime("%F %T").should eql('2010-04-04 11:44:00') end - it "should return Time for a TIMESTAMP value" do + it "should return DateTime for a DATETIME value when outside the supported range" do + r = @client.query("SELECT CAST('1901-1-1 01:01:01' AS DATETIME) as test") + r.first['test'].class.should eql(DateTime) + end + + it "should return Time for a TIMESTAMP value when within the supported range" do @test_result['timestamp_test'].class.should eql(Time) @test_result['timestamp_test'].strftime("%F %T").should eql('2010-04-04 11:44:00') end