Ruby / Time objects only go back to 1901-12-14

From WhyNotWiki

Jump to: navigation, search

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/). Retrieved on 2007-05-11 11:18.

$ 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
Personal tools