From: andyw@... Date: 2016-01-15T14:13:00+00:00 Subject: [ruby-core:72878] [Ruby trunk - Bug #11994] [Open] Incorrect result for yday in vtm_add_offset with negative UTC offset on last day of the year Issue #11994 has been reported by Andrew White. ---------------------------------------- Bug #11994: Incorrect result for yday in vtm_add_offset with negative UTC offset on last day of the year https://bugs.ruby-lang.org/issues/11994 * Author: Andrew White * Status: Open * Priority: Normal * Assignee: * ruby -v: ruby 2.4.0dev (2016-01-15 trunk 53543) [x86_64-darwin14] * Backport: 2.0.0: UNKNOWN, 2.1: UNKNOWN, 2.2: UNKNOWN, 2.3: UNKNOWN ---------------------------------------- When you have a UTC time value that's the first day of the year and then convert it to a time value with a negative UTC offset so that it's the 31st December then the year day is calculated incorrectly, e.g: ~~~ >> t = Time.utc(2015, 1, 1, 1, 0, 0).getlocal("-05:00") => 2014-12-31 20:00:00 -0500 >> t.yday => 364 >> t.strftime('%j') => "364" ~~~ I had a root around and it seems as though the problem is in `vtm_add_offset` in time.c at line 2032: ~~~ if (day) { if (day < 0) { if (vtm->mon == 1 && vtm->mday == 1) { vtm->mday = 31; vtm->mon = 12; /* December */ vtm->year = sub(vtm->year, INT2FIX(1)); vtm->yday = leap_year_v_p(vtm->year) ? 365 : 364; } ~~~ As far as I can tell the code appears to be making the assumption that year day is zero indexed but it's hard to determine the exact intent. I created a failing test case: ~~~ def test_strftime_yearday_on_last_day_of_year t = Time.utc(2015, 12, 31, 0, 0, 0) assert_equal("365", t.strftime("%j")) t = Time.utc(2016, 12, 31, 0, 0, 0) assert_equal("366", t.strftime("%j")) t = Time.utc(2015, 12, 30, 20, 0, 0).getlocal("+05:00") assert_equal("365", t.strftime("%j")) t = Time.utc(2016, 12, 30, 20, 0, 0).getlocal("+05:00") assert_equal("366", t.strftime("%j")) t = Time.utc(2016, 1, 1, 1, 0, 0).getlocal("-05:00") assert_equal("365", t.strftime("%j")) t = Time.utc(2017, 1, 1, 1, 0, 0).getlocal("-05:00") assert_equal("366", t.strftime("%j")) end ~~~ When I changed the ternary to the following: ~~~ vtm->yday = leap_year_v_p(vtm->year) ? 366 : 365; ~~~ then the test passes and no other tests fail. The original report came via the Rails bug tracker: https://github.com/rails/rails/issues/23033 There are a couple of other places in time.c with similar ternaries (in gmtime_with_leapsecond) but they appear to only affect 4.4BSD so I was unable to test them. -- https://bugs.ruby-lang.org/ Unsubscribe: