What's New in Edge Rails: Easier Timezones

Posted by ryan
at 7:59 PM on Thursday, January 24, 2008

The days of forcing time-zone support into your Rails apps with not one, but two plugins are over. It would appear that Rails now has its own way of dealing with timezones via a custom implementation (though it’s still based on the tzinfo gem).

Here’s the deal. Set the Time.zone variable to the local timezone. All further date manipulations will automatically reflect this local time while being saved to the database in UTC. Here’s what that will look like:

1
2
3
4
5
6
7
8
9
10
# Set the local time zone
Time.zone = "Pacific Time (US & Canada)"

# All times will now reflect the local time
article = Article.find(:first)
article.published_at #=> Wed, 30 Jan 2008 2:21:09 PST -08:00

# Setting new times in UTC will also be reflected in local time
article.published_at = Time.utc(2008, 1, 1, 0)
article.published_at  #=> Mon, 31 Dec 2007 16:00:00 PST -08:00

So how can we use this new timezone support in the real world – as in our Rails apps where you let users define their own timezone? We can do this using a before filter to set Time.zone, much in the same way you’re used to doing:

1
2
3
4
5
6
7
8
9
class ApplicationController < ActionController::Base

  before_filter :set_timezone

  def set_timezone
    # current_user.time_zone #=> 'London'
    Time.zone = current_user.time_zone
  end
end

Now your controller actions and views will automatically have their dates represented in the user’s timezone.

To set a default timezone for your app, do so in environment.rb:

1
2
3
Rails::Initializer.run do |config|
  config.time_zone = "Hawaii"
end

To get the current time in the currently set timezone you can use Time.zone.now:

1
2
# Instead of Time.now
Time.zone.now

At the end of the day you’ve got a timezone solution built into Rails that avoids needless dependencies and establishes a common practice for multi-timezone applications.

This article leave you wanting for more (it won’t offend me)? If so, check out Geoff Buesing’s incredibly detailed and thorough timezone writeup. It looks to be the first of a few tutorials by the guy who actually wrote this functionality.

tags: ruby, rubyonrails

Comments

Leave a response

  1. BenJanuary 24, 2008 @ 09:54 PM

    Couldn’t this be pushed deeper so that current_user.registered_at is a TimeWithZone? current_user.registered_at.in_current_time_zone would be nice. Hmm, or couldn’t current_user.registered_at.localtime be made to do the right thing as long as your set_timezone filter were in use?

  2. Geoff BuesingJanuary 24, 2008 @ 11:17 PM

    Ryan, thanks for the writeup. There are still a few more pieces to the TimeZone puzzle to be put in place (like ActiveRecord integration), but what’s there now should be useable. A couple notes:

    1.TimeWithZone is similar to the Duration class, in that, you should never need to create an instance directly—in the TWZ case, you’ve got the #in_time_zone, #in_current_time_zone, #change_time_zone and #change_time_zone_to_current methods on Time and DateTime instances that will handle that for you.

    So, for example, you can do this:

    current_user.registered_at.in_current_time_zone

    ... and the result will automatically be wrapped in a TimeWithZone

    2. The current time in Time.zone can be accessed via Time.zone.now

    3. Time.zone= will do a lookup via TimeZone[] with a string argument, so no need to use Time.get_zone in your around filter

  3. PratikJanuary 26, 2008 @ 03:02 PM

    You don’t really need around_filter there. A simple before_filter ( no need to reset to previous value ) will work as Time.zone= is a threadsafe method.

  4. PaulJanuary 29, 2008 @ 02:35 PM

    @Pratik Thanks for sharing that info.

  5. BillyFebruary 08, 2008 @ 12:41 PM

    @Geoff: This is fantastic, but I have to agree with Ben and disagree with you a little bit – there are instances in which it would be great to be able to store a TimeWithZone object and I hope you guys will consider implementing that. Most DBs have some form of time zone support and actually store the date internally as UTC with an offset.

    I’ve got a small-ish rant about how useful it would be over here: http://www.zetetic.net/articles/2008/02/08/usage-of-timewithzone

  6. HashamMarch 03, 2008 @ 06:41 AM

    That will definitely save lot of custom coding on my part in future applications

  7. RickyMarch 31, 2008 @ 05:46 AM

    Looking from a higher level, it’s interesting that something so simple and obvious as time and date has so many facets and complexity to it. There’s a book out called ‘Calendrical Calculations’ that addresses a lot of this.

    It’s also interesting that it isn’t covered much in CS/SE classes. Or at all.

    I think it’s an example of something that’s harder to code than to deal with in ‘Real life’. In real-life we take a table-driven approach, we read the calendar. In coding we calculate it. In real life I never need to know which cities have a thirty-minute timezone difference, instead of a whole hour. When coding, we do.

  8. Adam GreeneApril 02, 2008 @ 11:04 AM

    hey guys, I assume there will be a way to bypass local-time conversion when accessing certain fields? Perhaps something like: article[:published_at] or article.utc_published_at ?

    Just curious if anyone knows. thanks ryan for the writeup on this feature. fantastic!!! Adam

  9. Geoff BuesingApril 10, 2008 @ 09:29 AM

    Adam: you can bypass the local time conversion in a couple ways—

    1. just call #utc on the attribute: article.published_at.utc

    2. you can turn off time conversions for that attribute, via skip_time_zone_conversion_for_attributes:

    class Task < ActiveRecord::Base self.skip_time_zone_conversion_for_attributes = :alert_at, created_at end

  10. Geoff BuesingApril 10, 2008 @ 09:32 AM

    More info on the new time zone features in the upcoming Rails 2.1 release, here:

    http://mad.ly/2008/04/09/rails-21-time-zone-support-an-overview/