From 9cccff177cf91c78455b220682a83b7e640e5a38 Mon Sep 17 00:00:00 2001 From: Steve Pike Date: Thu, 8 Sep 2022 16:39:34 -0400 Subject: [PATCH] Allow other "dates of calendar reform" in Date#strptime 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. --- lib/timecop/time_extensions.rb | 21 ++++++++------------- test/date_strptime_scenarios.rb | 12 ++++++++++++ 2 files changed, 20 insertions(+), 13 deletions(-) diff --git a/lib/timecop/time_extensions.rb b/lib/timecop/time_extensions.rb index 553e0fa..bb52ec1 100644 --- a/lib/timecop/time_extensions.rb +++ b/lib/timecop/time_extensions.rb @@ -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 diff --git a/test/date_strptime_scenarios.rb b/test/date_strptime_scenarios.rb index 9f2229f..8899af7 100644 --- a/test/date_strptime_scenarios.rb +++ b/test/date_strptime_scenarios.rb @@ -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