Skip to content

Commit

Permalink
Allow other "dates of calendar reform" in Date#strptime
Browse files Browse the repository at this point in the history
Timecop is incompatible with the newest patch version of Psych (4.0.5)
because of this change:

    https://github.com/ruby/psych/compare/v4.0.4..v4.0.5#diff-6a459e056cadf37665f54005bd2dde09d9ba8e66c9807eb0dc67145f9b841771L66-R66

Timecop only allows strptime to be called with the default of
Date::ITALY as the fourth argument. This fourth argument is the "day
of calendar reform" for leap years.  Dates pre-reform are Julian
calendar dates, which have leap years every 4 years. Dates after
reform are Gregorian calendar dates, which have more complicated leap
year logic (every 4 years, except if the year is divisible by 100 and
not by 400). https://en.wikipedia.org/wiki/Gregorian_calendar

Psych starts calling strptime with Date::GREGORIAN as of 4.0.5 (I'm
not sure why, but it's a legal use of the standard Ruby API and so it
seems like Timecop shouldn't break it).

This PR updates strptime_without_mock_date to allow passing in other
values for the fourth argument by passing them along to date
initialization.
  • Loading branch information
scpike authored and joshuacronemeyer committed Nov 29, 2022
1 parent 864bcf1 commit 9cccff1
Show file tree
Hide file tree
Showing 2 changed files with 20 additions and 13 deletions.
21 changes: 8 additions & 13 deletions lib/timecop/time_extensions.rb
Original file line number Diff line number Diff line change
Expand Up @@ -45,36 +45,31 @@ def today_with_mock_date
alias_method :strptime_without_mock_date, :strptime

def strptime_with_mock_date(str = '-4712-01-01', fmt = '%F', start = Date::ITALY)
unless start == Date::ITALY
raise ArgumentError, "Timecop's #{self}::#{__method__} only " +
"supports Date::ITALY for the start argument."
end

#If date is not valid the following line raises
Date.strptime_without_mock_date(str, fmt)
Date.strptime_without_mock_date(str, fmt, start)

d = Date._strptime(str, fmt)
now = Time.now.to_date
year = d[:year] || now.year
mon = d[:mon] || now.mon
if d.keys == [:year]
Date.new(year)
Date.new(year, 1, 1, start)
elsif d[:mday]
Date.new(year, mon, d[:mday])
Date.new(year, mon, d[:mday], start)
elsif d[:wday]
Date.new(year, mon, now.mday) + (d[:wday] - now.wday)
Date.new(year, mon, now.mday, start) + (d[:wday] - now.wday)
elsif d[:yday]
Date.new(year).next_day(d[:yday] - 1)
Date.new(year, 1, 1, start).next_day(d[:yday] - 1)
elsif d[:cwyear] && d[:cweek]
if d[:cwday]
Date.commercial(d[:cwyear], d[:cweek], d[:cwday])
Date.commercial(d[:cwyear], d[:cweek], d[:cwday], start)
else
Date.commercial(d[:cwyear], d[:cweek])
Date.commercial(d[:cwyear], d[:cweek], 1, start)
end
elsif d[:seconds]
Time.at(d[:seconds]).to_date
else
Date.new(year, mon)
Date.new(year, mon, 1, start)
end
end

Expand Down
12 changes: 12 additions & 0 deletions test/date_strptime_scenarios.rb
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,18 @@ def test_date_strptime_with_invalid_date
assert_raises(ArgumentError) { Date.strptime('', '%Y-%m-%d') }
end

def test_date_strptime_with_gregorian
assert_equal Date.strptime('1999-04-01', '%Y-%m-%d', Date::GREGORIAN), Date.new(1999, 4, 1)
end

def test_date_strptime_with_gregorian_non_leap
assert(!Date.strptime('1000-04-01', '%Y-%m-%d', Date::GREGORIAN).leap?)
end

def test_date_strptime_with_julian_leap
assert(Date.strptime('1000-04-01', '%Y-%m-%d', Date::JULIAN).leap?)
end

def test_ancient_strptime
ancient = Date.strptime('11-01-08', '%Y-%m-%d').strftime
assert_equal '0011-01-08', ancient # Failed before fix to strptime_with_mock_date
Expand Down

0 comments on commit 9cccff1

Please sign in to comment.