From 7524d7c41f6efc3365bfbd9f038cee92005847d6 Mon Sep 17 00:00:00 2001 From: sjh9714 Date: Wed, 19 Aug 2026 10:07:40 +0900 Subject: [PATCH] Range-check the integral Rational offset Every branch of offset_to_sec range-checks the resulting number of seconds except one path through the Rational branch: when the day fraction is an integral Rational, n is assigned inside the if arm and reaches *rof without passing the guard that sits in the else arm. DateTime.new(2024, 1, 1, 0, 0, 0, Rational(2, 1)) therefore produced a 48-hour offset, while the equivalent Integer 2 is rejected and falls back to +00:00. Rational(49710, 1) is 4_294_944_000 seconds, over INT_MAX, so the (int) narrowing turned a large positive offset into a negative one. Move the check below the if/else so it covers both arms. That also bounds n before the narrowing. Rational(1, 1) is exactly DAY_IN_SECONDS and the guard is inclusive, so in-range values are unaffected. --- ext/date/date_core.c | 4 ++-- test/date/test_date_new.rb | 15 +++++++++++++++ 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/ext/date/date_core.c b/ext/date/date_core.c index 72d697c8..88f8ef41 100644 --- a/ext/date/date_core.c +++ b/ext/date/date_core.c @@ -2621,9 +2621,9 @@ offset_to_sec(VALUE vof, int *rof) if (!FIXNUM_P(vn)) return 0; n = FIX2LONG(vn); - if (n < -DAY_IN_SECONDS || n > DAY_IN_SECONDS) - return 0; } + if (n < -DAY_IN_SECONDS || n > DAY_IN_SECONDS) + return 0; *rof = (int)n; return 1; } diff --git a/test/date/test_date_new.rb b/test/date/test_date_new.rb index eddeeff8..8973eefb 100644 --- a/test/date/test_date_new.rb +++ b/test/date/test_date_new.rb @@ -192,6 +192,21 @@ def test_civil__ex end end + def test_civil__offset + d = DateTime.civil(2001,2,3, 0,0,0, Rational(1, 1)) + assert_equal(1.to_r, d.offset) + d = DateTime.civil(2001,2,3, 0,0,0, Rational(-1, 1)) + assert_equal(-1.to_r, d.offset) + + # An out-of-range offset is ignored, as it is for the equivalent Integer. + d = DateTime.civil(2001,2,3, 0,0,0, 2) + assert_equal(0, d.offset) + d = DateTime.civil(2001,2,3, 0,0,0, Rational(2, 1)) + assert_equal(0, d.offset) + d = DateTime.civil(2001,2,3, 0,0,0, Rational(49710, 1)) + assert_equal(0, d.offset) + end + def test_civil__reform d = Date.jd(Date::ENGLAND, Date::ENGLAND) dt = DateTime.jd(Date::ENGLAND, 0,0,0,0, Date::ENGLAND)