Ruby / Time objects only go back to 1901-12-14
From WhyNotWiki
Contents |
[edit] Problem
I encountered this when I tried to do something like this to calculate a person's age:
def age
(Time.now - birthdate.to_time).to_i / (365*24*60*60)
end
(Using to_time from ActiveSupport)
Of course, this works fine for people with normal birthdays. But for anyone with a birthday in 1900 or earlier, it throws a fatal error:
ArgumentError: time out of range
from /usr/lib/ruby/gems/1.8/gems/activesupport-1.4.2/lib/active_support/core_ext/date/conversions.rb:29:in `local'
from /usr/lib/ruby/gems/1.8/gems/activesupport-1.4.2/lib/active_support/core_ext/date/conversions.rb:29:in `send'
from /usr/lib/ruby/gems/1.8/gems/activesupport-1.4.2/lib/active_support/core_ext/date/conversions.rb:29:in `to_time'
from ...
I should have known better than to be dealing with things in the precision of seconds when I'm only interested in years (or fractional numbers of years at most).
[edit] Solution/workaround
As several posts [1] out there point out, the easiest solution is usually just to use a Date object instead of a Time object.
In my case, I simply changed it to this.
def age
(Date.today - birthdate).to_i / 365
end
Much simpler. (But most importantly of all, it works, even for years < 1901!)
[edit] Details of problem / Tracking down the problem
irb -> Date.new(1901,12,14).to_time
=> Sat Dec 14 00:00:00 -0800 1901
irb -> Date.new(1901,12,13).to_time
ArgumentError: time out of range
from /usr/lib/ruby/gems/1.8/gems/activesupport-1.4.2/lib/active_support/core_ext/date/conversions.rb:29:in `local'
from /usr/lib/ruby/gems/1.8/gems/activesupport-1.4.2/lib/active_support/core_ext/date/conversions.rb:29:in `send'
from /usr/lib/ruby/gems/1.8/gems/activesupport-1.4.2/lib/active_support/core_ext/date/conversions.rb:29:in `to_time'
from (irb):23
/usr/lib/ruby/gems/1.8/gems/activesupport-1.4.2/lib/active_support/core_ext/date/conversions.rb
def to_time(form = :local)
if respond_to?(:hour)
::Time.send(form, year, month, day, hour, min, sec)
else
::Time.send(form, year, month, day)
end
end
(Since it looks like ActiveSupport's Date#to_time simply calls Time.local, I will just call that directly in the rest of my examples...)
irb -> Time.local(1901,12,14)
=> Sat Dec 14 00:00:00 -0800 1901
irb -> Time.local(1901,12,13)
ArgumentError: time out of range
from (irb):27:in `local'
from (irb):27
Now to see if Date can do any better...
irb -> (Date.today - Date.new(1900,1,1)).to_i / 365
=> 107
# Hey, look at that! It works for the year 1900. But that's not all... it works for years even earlier than that:
irb -> (Date.today - Date.new(1600,1,1)).to_i / 365
=> 407
irb -> (Date.today - Date.new(1,1,1)).to_i / 365
=> 2007
irb -> (Date.today - Date.new(0,1,1)).to_i / 365
=> 2009
irb -> (Date.today - Date.new(-1,1,1)).to_i / 365
=> 2010
irb -> (Date.today - Date.new(2000,1,1)).to_i / 365
=> 7
irb -> Date.today.to_s
=> "2007-08-30"
irb -> (Date.today - Date.new(2000,12,1)).to_i / 365
=> 6
[edit] Seems to work on some architectures/implementations
[mistitled] (http://mentalized.net/journal/2006/12/19/one_reason_to_upgrade_ruby_to_185/).
$ ruby -v ruby 1.8.5 (2006-08-25) [i486-linux] $ irb irb(main):002:0> Time.mktime(1901, 12, 13) => Fri Dec 13 21:45:52 +0100 1901
On further digging this appears to specific to architecture/hardware rather than version of Ruby:$ ruby -v ruby 1.8.5 (2006-08-25) [i386-freebsd5] $ irb > Time.mktime(1901,12,13) ArgumentError: time out of range from (irb):2:in `mktime' from (irb):2 from :0
