davideisinger.com

My personal website
Log | Files | Refs | README

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.