index.md (2403B)
1 --- 2 title: "Protip: TimeWithZone, All The Time" 3 date: 2008-09-10T00:00:00+00:00 4 draft: false 5 canonical_url: https://www.viget.com/articles/protip-timewithzone-all-the-time/ 6 --- 7 8 If you've ever tried to retrieve a list of ActiveRecord objects based on 9 their timestamps, you've probably been bitten by the quirky time support 10 in Rails: 11 12 ``` 13 >> Goal.create(:description => "Run a mile") 14 => #<Goal id: 1, description: "Run a mile", created_at: "2008-09-09 19:32:57", updated_at: "2008-09-09 19:32:57"> 15 >> Goal.find(:all, :conditions => ['created_at < ?', Time.now]) 16 => [] 17 ``` 18 19 Huh? Checking the logs, we see that the two commands above correspond to 20 the following queries: 21 22 ```sql 23 INSERT INTO "goals" ("updated_at", "description", "created_at") VALUES('2008-09-09 19:32:57', 'Run a mile', '2008-09-09 19:32:57') 24 SELECT * FROM "goals" WHERE created_at < '2008-09-09 15:33:17' 25 ``` 26 27 Rails stores `created_at` relative to [Coordinated Universal 28 Time](https://en.wikipedia.org/wiki/Coordinated_Universal_Time), while 29 `Time.now` is based on the system clock, running four hours behind. The 30 solution? ActiveSupport's 31 [TimeWithZone](http://caboo.se/doc/classes/ActiveSupport/TimeWithZone.html): 32 33 ``` 34 >> Goal.find(:all, :conditions => ['created_at < ?', Time.zone.now]) 35 => [#<Goal id: 1, description: "Run a mile", created_at: "2008-09-09 19:32:57", updated_at: "2008-09-09 19:32:57">] 36 ``` 37 38 **Rule of thumb:** always use TimeWithZone in your Rails projects. Date, 39 Time and DateTime simply don't play well with ActiveRecord. Instantiate 40 it with `Time.zone.now` and `Time.zone.local`. To discard the time 41 element, use `beginning_of_day`. 42 43 ## BONUS TIP 44 45 Since it's a subclass of Time, interpolating a range of TimeWithZone 46 objects fills in every second between the two times --- not so useful if 47 you need a date for every day in a month: 48 49 50 ``` 51 >> t = Time.zone.now 52 => Tue, 09 Sep 2008 14:26:45 EDT -04:00 53 >> (t..(t + 1.month)).to_a.size [9 minutes later] 54 => 2592001 55 ``` 56 57 Fortunately, the desired behavior is just a monkeypatch away: 58 59 ```ruby 60 class ActiveSupport::TimeWithZone 61 def succ 62 self + 1.day 63 end 64 end 65 66 >> (t..(t + 1.month)).to_a.size 67 => 31 68 ``` 69 70 For more information about time zones in Rails, [Geoff 71 Buesing](http://mad.ly/2008/04/09/rails-21-time-zone-support-an-overview/) 72 and [Ryan 73 Daigle](http://ryandaigle.com/articles/2008/1/25/what-s-new-in-edge-rails-easier-timezones) 74 have good, up-to-date posts.