Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Ruby Time#to_date() method returns wrong date

Tags:

ruby

We're using Ruby 1.9.3, and I found what appears to be a strange Ruby error with Time#to_date

Time.new(1).to_date returns 3 January 0001 when it should return 1 January 0001.

I discovered this problem accidentally. It seems if I call .to_datetime.to_date, the result would be correct.

I also found some other related weirdness. See the irb console output below. (Note I'm using irb, not rails console to ensure that it's only Ruby I'm using, not any add-on stuff of Rails.)

>> require "Time"
=> true
>> Time.new(1).to_date
=> #<Date: 0001-01-03 ((1721426j,0s,0n),+0s,2299161j)>
>> Time.new(1).to_datetime
=> #<DateTime: 0001-01-01T00:00:00+01:00 ((1721423j,82800s,0n),+3600s,2299161j)>
>> Time.new(1).to_datetime.to_date
=> #<Date: 0001-01-01 ((1721424j,0s,0n),+0s,2299161j)>
>> Time.new(100).to_date
=> #<Date: 0100-01-03 ((1757585j,0s,0n),+0s,2299161j)>
>> Time.new(1969).to_date
=> #<Date: 1969-01-01 ((2440223j,0s,0n),+0s,2299161j)>
>> Time.new(500).to_date
=> #<Date: 0499-12-31 ((1903682j,0s,0n),+0s,2299161j)>
>> Time.new(1000).to_date
=> #<Date: 0999-12-27 ((2086303j,0s,0n),+0s,2299161j)>
>> Time.new(2014).to_date
=> #<Date: 2014-01-01 ((2456659j,0s,0n),+0s,2299161j)>
>> Time.new(1,1,1,10,0,0, "+00:00").to_date
=> #<Date: 0001-01-03 ((1721426j,0s,0n),+0s,2299161j)>
>> DateTime.new(1,1,1,10,0,0, "+00:00").to_date
=> #<Date: 0001-01-01 ((1721424j,0s,0n),+0s,2299161j)>

Could someone explain why this happens? Is this related to Unix time somehow?

It seems DateTime is more reliable, at least with Ruby 1.9.3.

like image 637
Zack Xu Avatar asked Mar 20 '14 14:03

Zack Xu


People also ask

What is Ruby time?

Advertisements. The Time class represents dates and times in Ruby. It is a thin layer over the system date and time functionality provided by the operating system. This class may be unable on your system to represent dates before 1970 or after 2038.


1 Answers

It's because of how leap year is implemented in the ruby Time.to_date method.

Note that the cutoff for your strange behavior happens between 1583 and 1582:

>    Time.new(1583).to_date
=> #<Date: 1583-01-01 ((2299239j,0s,0n),+0s,2299161j)>
>    Time.new(1582).to_date
=> #<Date: 1581-12-22 ((2298874j,0s,0n),+0s,2299161j)>

Where 1583's date is correct but 1582's is incorrect. If you look at the source for Time.to_date, you can see that it always considers a date to be a Gregorian calendar date - i.e. it includes leap year. Also note that the switch to the Gregorian calendar was in 1582, explaining the cutoff for the behavior above. (For those really interested in the source code, note that the call to decode_year is passing -1 as the style parameter, which always makes all calls to that function use a Gregorian calendar date.)

So that means any dates before 1583 will be incorrect when displaying them with this method.

like image 168
mralexlau Avatar answered Oct 22 '22 03:10

mralexlau