David Eisinger, Web Developer, September 10, 2008
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 < ?',
Time.now])
=> []
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 Time.now is based on the system clock, running four hours behind. The solution? ActiveSupport’s TimeWithZone:
>> Goal.find(:all, :conditions => ['created_at < ?',
Time.zone.now])
=> [#<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 Time.zone.now and Time.zone.local. To discard the time element, use beginning_of_day.
BONUS TIP
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 = Time.zone.now
=> 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 + 1.day
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.
Recent Comments
:D
thats exactly what i have been looking for, though i do not need it so badly since memoize arrived…