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: <mailto:ruby-core-request / ruby-lang.org?subject=unsubscribe>
<http://lists.ruby-lang.org/cgi-bin/mailman/options/ruby-core>