Protip: TimeWithZone, All The Time

If you’ve ever tried to retrieve a list of ActiveRecord objects based on their timestamps, you’ve probably been bitten by the quirky time support in Rails:

>> Goal.create(:description => "Run a mile") => #<Goal id: 1, description: "Run a mile", created_at: "2008-09-09 19:32:57", updated_at: "2008-09-09 19:32:57"> >> Goal.find(:all, :conditions => ['created_at < ?',]) => [] 

Huh? Checking the logs, we see that the two commands above correspond to the following queries:

INSERT INTO "goals" ("updated_at", "description", "created_at") VALUES('2008-09-09 19:32:57', 'Run a mile', '2008-09-09 19:32:57') SELECT * FROM "goals" WHERE created_at < '2008-09-09 15:33:17' 

Rails stores created_at relative to Coordinated Universal Time, while is based on the system clock, running four hours behind. The solution? ActiveSupport’s TimeWithZone:

>> Goal.find(:all, :conditions => ['created_at < ?',]) => [#<Goal id: 1, description: "Run a mile", created_at: "2008-09-09 19:32:57", updated_at: "2008-09-09 19:32:57">] 

Rule of thumb: always use TimeWithZone in your Rails projects. Date, Time and DateTime simply don’t play well with ActiveRecord. Instantiate it with and To discard the time element, use beginning_of_day.


Since it’s a subclass of Time, interpolating a range of TimeWithZone objects fills in every second between the two times — not so useful if you need a date for every day in a month:

>> t = => Tue, 09 Sep 2008 14:26:45 EDT -04:00 >> (t..(t + 1.month)).to_a.size [9 minutes later] => 2592001 

Fortunately, the desired behavior is just a monkeypatch away:

class ActiveSupport::TimeWithZone def succ self + end end >> (t..(t + 1.month)).to_a.size => 31 

For more information about time zones in Rails, Geoff Buesing and Ryan Daigle have good, up-to-date posts.

David Eisinger

David is Viget's managing development director. From our Durham, NC, office, he builds high-quality, forward-thinking software for PUMA, the World Wildlife Fund,, and many others.

More articles by David