diff --git a/ext/date/date_core.c b/ext/date/date_core.c index 0d69633..7a9e01f 100644 --- a/ext/date/date_core.c +++ b/ext/date/date_core.c @@ -2561,7 +2561,12 @@ valid_nth_kday_p(VALUE y, int m, int n, int k, double sg, } #endif +#ifndef HAVE_RB_CATEGORY_WARN +#define rb_category_warn(category, fmt) rb_warn(fmt) +#endif + VALUE date_zone_to_diff(VALUE); +VALUE date_zone_to_diff_frac(VALUE, int *, long *); static int offset_to_sec(VALUE vof, int *rof) @@ -2633,13 +2638,21 @@ offset_to_sec(VALUE vof, int *rof) } case T_STRING: { - VALUE vs = date_zone_to_diff(vof); - long n; - - if (!FIXNUM_P(vs)) - return 0; - n = FIX2LONG(vs); - if (n < -DAY_IN_SECONDS || n > DAY_IN_SECONDS) + int frac, ok = 0; + long whole, n = 0; + VALUE vs = date_zone_to_diff_frac(vof, &frac, &whole); + + if (FIXNUM_P(vs)) { + n = FIX2LONG(vs); + if (n >= -DAY_IN_SECONDS && n <= DAY_IN_SECONDS) + ok = 1; + } + /* warn only where dropping the fraction would give an offset + * different from the one this returns today */ + if (frac && (ok ? n : 0) != whole) + rb_category_warn(RB_WARN_CATEGORY_DEPRECATED, "fraction of hour in a zone offset is deprecated" + " and will be ignored in a future version"); + if (!ok) return 0; *rof = (int)n; return 1; @@ -4678,10 +4691,6 @@ get_limit(VALUE opt) return 128; } -#ifndef HAVE_RB_CATEGORY_WARN -#define rb_category_warn(category, fmt) rb_warn(fmt) -#endif - static VALUE check_limit(VALUE str, VALUE opt) { diff --git a/ext/date/date_parse.c b/ext/date/date_parse.c index 37748a1..d7433d4 100644 --- a/ext/date/date_parse.c +++ b/ext/date/date_parse.c @@ -7,6 +7,10 @@ #include "ruby/re.h" #include +#ifndef HAVE_RB_CATEGORY_WARN +#define rb_category_warn(category, fmt) rb_warn(fmt) +#endif + #undef strncasecmp #define strncasecmp STRNCASECMP @@ -413,12 +417,15 @@ shrink_space(char *d, const char *s, long l) } VALUE -date_zone_to_diff(VALUE str) +date_zone_to_diff_frac(VALUE str, int *fracp, long *whole) { VALUE offset = Qnil; long l = RSTRING_LEN(str); const char *s = RSTRING_PTR(str); + *fracp = 0; + *whole = 0; + { int dst = 0; int w; @@ -533,6 +540,8 @@ date_zone_to_diff(VALUE str) offset = rb_rational_num(offset); } } + *fracp = 1; + *whole = hour * 3600; goto ok; } else if (l > 2) { @@ -558,6 +567,19 @@ date_zone_to_diff(VALUE str) return offset; } +VALUE +date_zone_to_diff(VALUE str) +{ + int frac; + long whole; + VALUE offset = date_zone_to_diff_frac(str, &frac, &whole); + + if (frac) + rb_category_warn(RB_WARN_CATEGORY_DEPRECATED, "fraction of hour in a zone offset is deprecated" + " and will be ignored in a future version"); + return offset; +} + static int day_num(VALUE s) { diff --git a/test/date/test_date_new.rb b/test/date/test_date_new.rb index b839c52..4a0f9f1 100644 --- a/test/date/test_date_new.rb +++ b/test/date/test_date_new.rb @@ -213,6 +213,41 @@ def test_civil__offset assert_equal(0, d.offset) end + def test_civil__fractional_zone_offset_deprecated + with_deprecation_warnings do + # A fraction that makes the offset non-integral is already dropped whole, + # so ignoring the fraction will not change these. + ['+00.123', '-00.123'].each do |z| + assert_warning(/\A(?!.*fraction of hour).*invalid offset is ignored/m) do + assert_equal(0, DateTime.civil(2001,2,3, 0,0,0, z).offset) + end + end + + # Here the fraction reaches the result, or dropping it would give an + # offset different from the 0 returned today. + assert_warning(/fraction of hour/) do + assert_equal(Rational(1800, 86400), DateTime.civil(2001,2,3, 0,0,0, '+00.5').offset) + end + assert_warning(/fraction of hour/) do + assert_equal(0, DateTime.civil(2001,2,3, 0,0,0, '+01.123').offset) + end + + # A zero fraction and the colon forms change nothing. + assert_warning('') do + assert_equal(Rational(3600, 86400), DateTime.civil(2001,2,3, 0,0,0, '+01.0').offset) + assert_equal(Rational(1800, 86400), DateTime.civil(2001,2,3, 0,0,0, '+00:30').offset) + end + end + end + + def with_deprecation_warnings + deprecated = Warning[:deprecated] if Warning.respond_to?(:[]) + Warning[:deprecated] = true if Warning.respond_to?(:[]=) + yield + ensure + Warning[:deprecated] = deprecated if Warning.respond_to?(:[]=) + end + def test_civil__reform d = Date.jd(Date::ENGLAND, Date::ENGLAND) dt = DateTime.jd(Date::ENGLAND, 0,0,0,0, Date::ENGLAND) diff --git a/test/date/test_date_parse.rb b/test/date/test_date_parse.rb index 8308f25..8994fa9 100644 --- a/test/date/test_date_parse.rb +++ b/test/date/test_date_parse.rb @@ -5,6 +5,78 @@ class TestDateParse < Test::Unit::TestCase + def test__parse_fractional_zone_offset_deprecated + with_deprecation_warnings do + # The fractional hour is honoured today and will be ignored, which + # changes :offset, or at least :zone for a zero fraction. + assert_warning(/fraction of hour/) do + assert_equal(Rational(2214, 5), Date._parse('2001-02-03T00:00:00+00.123')[:offset]) + end + assert_warning(/fraction of hour/) do + assert_equal(1800, Date._parse('2001-02-03T00:00:00+00,5')[:offset]) + end + assert_warning(/fraction of hour/) do + h = Date._parse('2001-02-03T00:00:00+01.0') + assert_equal(['+01.0', 3600], h.values_at(:zone, :offset)) + end + [ + ['19990523235521.123[-9.50]', [1999,5,23,23,55,21,'-9.50',-(9*3600+30*60)]], + ['19990523235521.123[+9.50]', [1999,5,23,23,55,21,'+9.50',+(9*3600+30*60)]], + ['2000-01-31 13:20:00-5.5', [2000,1,31,13,20,0,'-5.5',-5*3600-30*60]], + ['2000-01-31 13:20:00-5,5', [2000,1,31,13,20,0,'-5,5',-5*3600-30*60]], + ['2000-01-31 13:20:00+3.5', [2000,1,31,13,20,0,'+3.5',3*3600+30*60]], + ['2000-01-31 13:20:00+3,5', [2000,1,31,13,20,0,'+3,5',3*3600+30*60]], + ].each do |str, expected| + assert_warning(/fraction of hour/) do + h = Date._parse(str) + assert_equal(expected, h.values_at(:year,:mon,:mday,:hour,:min,:sec,:zone,:offset), str) + end + end + # The colon forms have no fractional hour at all. + assert_warning('') do + assert_equal(1800, Date._parse('2001-02-03T00:00:00+00:30')[:offset]) + assert_equal(86399, Date._parse('2001-02-03T00:00:00+23:59:59')[:offset]) + end + end + end + + def test__strptime_fractional_zone_offset_deprecated + with_deprecation_warnings do + assert_warning(/fraction of hour/) do + assert_equal(1800, Date._strptime('+00.5', '%z')[:offset]) + end + assert_warning(/fraction of hour/) do + assert_equal(Rational(2214, 5), Date._strptime('+00,123', '%z')[:offset]) + end + assert_warning(/fraction of hour/) do + assert_equal(['+01.0', 3600], Date._strptime('+01.0', '%z').values_at(:zone, :offset)) + end + assert_warning(/fraction of hour/) do + h = Date._strptime('Thu Jul 29 16:39:41 GMT-3.5 1999', '%a %b %d %H:%M:%S %Z %Y') + assert_equal(['GMT-3.5', -3*3600-30*60], h.values_at(:zone, :offset)) + end + assert_warning(/fraction of hour/) do + h = Date._strptime('Thu Jul 29 16:39:41 GMT-3,5 1999', '%a %b %d %H:%M:%S %Z %Y') + assert_equal(['GMT-3,5', -3*3600-30*60], h.values_at(:zone, :offset)) + end + assert_warning(/fraction of hour/) do + assert_equal(3600, Date._strptime('+0.9999999999999999999999', '%Z')[:offset]) + end + assert_warning('') do + assert_equal(1800, Date._strptime('+00:30', '%z')[:offset]) + assert_equal(-3600, Date._strptime('-0100', '%z')[:offset]) + end + end + end + + def with_deprecation_warnings + deprecated = Warning[:deprecated] if Warning.respond_to?(:[]) + Warning[:deprecated] = true if Warning.respond_to?(:[]=) + yield + ensure + Warning[:deprecated] = deprecated if Warning.respond_to?(:[]=) + end + def test__parse [ # ctime(3), asctime(3) @@ -134,8 +206,6 @@ def test__parse [['19990523235521.123[+9]',false],[1999,5,23,23,55,21,'+9',+(9*3600),nil], __LINE__], [['19990523235521.123[9]',false],[1999,5,23,23,55,21,'9',+(9*3600),nil], __LINE__], [['19990523235521.123[9 ]',false],[1999,5,23,23,55,21,'9 ',+(9*3600),nil], __LINE__], - [['19990523235521.123[-9.50]',false],[1999,5,23,23,55,21,'-9.50',-(9*3600+30*60),nil], __LINE__], - [['19990523235521.123[+9.50]',false],[1999,5,23,23,55,21,'+9.50',+(9*3600+30*60),nil], __LINE__], [['19990523235521.123[-5:EST]',false],[1999,5,23,23,55,21,'EST',-5*3600,nil], __LINE__], [['19990523235521.123[+9:JST]',false],[1999,5,23,23,55,21,'JST',9*3600,nil], __LINE__], [['19990523235521.123[+12:XXX YYY ZZZ]',false],[1999,5,23,23,55,21,'XXX YYY ZZZ',12*3600,nil], __LINE__], @@ -166,10 +236,6 @@ def test__parse [['2000-01-31 13:20:00-5',false],[2000,1,31,13,20,0,'-5',-5*3600,nil], __LINE__], # [-+]\d+.\d+ - [['2000-01-31 13:20:00-5.5',false],[2000,1,31,13,20,0,'-5.5',-5*3600-30*60,nil], __LINE__], - [['2000-01-31 13:20:00-5,5',false],[2000,1,31,13,20,0,'-5,5',-5*3600-30*60,nil], __LINE__], - [['2000-01-31 13:20:00+3.5',false],[2000,1,31,13,20,0,'+3.5',3*3600+30*60,nil], __LINE__], - [['2000-01-31 13:20:00+3,5',false],[2000,1,31,13,20,0,'+3,5',3*3600+30*60,nil], __LINE__], # mil [['2000-01-31 13:20:00 Z',false],[2000,1,31,13,20,0,'Z',0*3600,nil], __LINE__], diff --git a/test/date/test_date_strptime.rb b/test/date/test_date_strptime.rb index f68f749..688a91e 100644 --- a/test/date/test_date_strptime.rb +++ b/test/date/test_date_strptime.rb @@ -142,8 +142,6 @@ def test__strptime__3 [['Thu Jul 29 16:39:41 GMT-09 1999', '%a %b %d %H:%M:%S %Z %Y'], [1999,7,29,16,39,41,'GMT-09',-9*3600,4], __LINE__], [['Thu Jul 29 16:39:41 GMT-09:08 1999', '%a %b %d %H:%M:%S %Z %Y'], [1999,7,29,16,39,41,'GMT-09:08',-9*3600-8*60,4], __LINE__], [['Thu Jul 29 16:39:41 GMT-09:08:07 1999', '%a %b %d %H:%M:%S %Z %Y'], [1999,7,29,16,39,41,'GMT-09:08:07',-9*3600-8*60-7,4], __LINE__], - [['Thu Jul 29 16:39:41 GMT-3.5 1999', '%a %b %d %H:%M:%S %Z %Y'], [1999,7,29,16,39,41,'GMT-3.5',-3*3600-30*60,4], __LINE__], - [['Thu Jul 29 16:39:41 GMT-3,5 1999', '%a %b %d %H:%M:%S %Z %Y'], [1999,7,29,16,39,41,'GMT-3,5',-3*3600-30*60,4], __LINE__], [['Thu Jul 29 16:39:41 Mountain Daylight Time 1999', '%a %b %d %H:%M:%S %Z %Y'], [1999,7,29,16,39,41,'Mountain Daylight Time',-6*3600,4], __LINE__], [['Thu Jul 29 16:39:41 E. Australia Standard Time 1999', '%a %b %d %H:%M:%S %Z %Y'], [1999,7,29,16,39,41,'E. Australia Standard Time',10*3600,4], __LINE__], @@ -182,7 +180,6 @@ def test__strptime__3 [['E. Australia Standard Time', '%Z'], [nil,nil,nil,nil,nil,nil,'E. Australia Standard Time',10*3600,nil], __LINE__], # out of range - [['+0.9999999999999999999999', '%Z'], [nil,nil,nil,nil,nil,nil,'+0.9999999999999999999999',+1*3600,nil], __LINE__], [['+9999999999999999999999.0', '%Z'], [nil,nil,nil,nil,nil,nil,'+9999999999999999999999.0',nil,nil], __LINE__], ].each do |x, y| h = Date._strptime(*x)