Bug #11994
closedIncorrect result for yday in vtm_add_offset with negative UTC offset on last day of the year
Description
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.