Reputation: 51
Why does Rails' Date#strptime
parse "13/08" as August 15 or August 14 before the year 200?
Date.strptime('13/08/99', '%d/%m/%Y') #=> Thu, 15 Aug 0099
Date.strptime('13/08/100', '%d/%m/%Y') #=> Fri, 14 Aug 0100
Date.strptime('13/08/199', '%d/%m/%Y') #=> Tue, 14 Aug 0199
Date.strptime('13/08/200', '%d/%m/%Y') #=> Wed, 13 Aug 0200
Upvotes: 4
Views: 383
Reputation: 54313
To sum up :
If you don't use timecop gem, Date#strptime seems to work fine for year < 200.
If you use timecop, Date#strptime is overwritten and uses Time#to_date, which seems to return wrong values for year < 200.
Easy solutions, either :
Harder solution : understand what's wrong with the implementation of Time#to_date (see Stefan's explanation.)
[0] pry(main)> Time.local(99,8,13).to_date
=> #<Date: 0099-08-15 ((1757444j,0s,0n),+0s,2299161j)>
[1] pry(main)> Date.strptime('13/08/99', '%d/%m/%Y')
=> #<Date: 0099-08-13 ((1757442j,0s,0n),+0s,2299161j)>
[2] pry(main)> require 'timecop'
=> true
[3] pry(main)> Date.strptime('13/08/99', '%d/%m/%Y')
=> #<Date: 0099-08-15 ((1757444j,0s,0n),+0s,2299161j)>
[4] pry(main)> Date.strptime_without_mock_date('13/08/99', '%d/%m/%Y')
=> #<Date: 0099-08-13 ((1757442j,0s,0n),+0s,2299161j)>
[5] pry(main)> time = Time.strptime('13/08/99', '%d/%m/%Y')
=> 0099-08-13 00:00:00 +0053
[6] pry(main)> Date.new(time.year,time.month,time.day)
=> #<Date: 0099-08-13 ((1757442j,0s,0n),+0s,2299161j)>
Upvotes: 4
Reputation: 114268
Using github.com/daveallie/where_is:
Where.is(Date, :strptime) #=> ["/home/deployer/.rvm/gems/ruby-2.3.1/gems/timecop-0.7.0/lib/timecop/time_extensions.rb", 46]
which leads to timecop/time_extensions.rb#L46:
def strptime_with_mock_date(str, fmt) Time.strptime(str, fmt).to_date end
That's correct, Timecop turns:
Date.strptime('13/08/99', '%d/%m/%Y')
#=> #<Date: 0099-08-13 ((1757442j,0s,0n),+0s,2299161j)>
into:
Time.strptime('13/08/99', '%d/%m/%Y').to_date
#=> #<Date: 0099-08-15 ((1757444j,0s,0n),+0s,2299161j)>
So it's not
Date
, that's buggy, it'sTime
?
It's not exactly buggy, just different. Time#to_date
interprets the values as proleptic Gregorian calendar dates:
Date.new(99, 8, 13, Date::GREGORIAN).new_start
#=> #<Date: 0099-08-15 ((1757444j,0s,0n),+0s,2299161j)>
versus:
Date.new(99, 8, 13, Date::ITALY).new_start
#=> #<Date: 0099-08-13 ((1757442j,0s,0n),+0s,2299161j)>
Here's a summary from Wikipedia:
Julian range | Proleptic Gregorian range | Gregorian ahead by:
-------------------------|---------------------------|--------------------
03/03/4 - 01/03/100 | 01/03/4 - 28/02/100 | −2 days
02/03/100 - 29/02/200 | 01/03/100 - 28/02/200 | −1 days
01/03/200 - 28/02/300 | 01/03/200 - 28/02/300 | 0 days
29/02/300 - 27/02/500 | 01/03/300 - 28/02/500 | 1 day
28/02/500 - 26/02/600 | 01/03/500 - 28/02/600 | 2 days
27/02/600 - 25/02/700 | 01/03/600 - 28/02/700 | 3 days
26/02/700 - 24/02/900 | 01/03/700 - 28/02/900 | 4 days
25/02/900 - 23/02/1000 | 01/03/900 - 28/02/1000 | 5 days
24/02/1000 - 22/02/1100 | 01/03/1000 - 28/02/1100 | 6 days
23/02/1100 - 21/02/1300 | 01/03/1100 - 28/02/1300 | 7 days
22/02/1300 - 20/02/1400 | 01/03/1300 - 28/02/1400 | 8 days
21/02/1400 - 19/02/1500 | 01/03/1400 - 28/02/1500 | 9 days
20/02/1500 - 04/10/1582 | 01/03/1500 - 14/10/1582 | 10 days
Upvotes: 2