Timezones in Rails 2.1
It isn’t particularly surprising that timezone support is a pretty important component of web applications, as the web really is a global medium. If you are building an application that has to deal with times, such as sending out calendar reminders or even something as simple as displaying a timestamp correctly, you may need to be wary of the fact that different users live in different timezones. Sound easy? Then read on…
A quick background lesson: Obviously, not every part of the world is in daylight at the same time, when it is the middle of the day in Australia, it’s the middle of the night in the United States. To make sure midday means the same thing in Perth as it does in Atlanta, the world is broken up in to timezones revolving around Coordinated Universal Time (UTC) that runs through Greenwich, UK (which is why UTC is also called Greenwich Mean Time or GMT), ranging from -12 GMT to +14 GMT.
Since every timezone is defined in respect to this imaginary zone time, it would make sense to use that as a base in our timezone coding adventure. In Rails, you can tell ActiveRecord to save all dates as UTC by setting the following value in your /config/environment.rb:
config.time_zone = 'UTC'
The most obvious and simple way to calculate localtime, would be to add the offset to the current time in GMT – and if that is all you needed to do, I could conclude this article right now, but alas, it’s not always that easy because of daylight savings.
The theory behind daylight savings time (DST) is to shift the localtime forward an amount of time (usually an hour), making the sun seem to rise and set later, meaning you have more time to do stuff after work. Unfortunately, it just means you stay at work longer trying to work out all this timezone stuff. It wouldn’t be so bad if every country adopted DST on the same day, but the reality is that not all countries have it, each one adopts it at different times (in fact, it’s not uncommon for regions WITHIN a country to adopt it at different times), and not all countries advance it by a standard hour.
So what are we to do? Thankfully, there are a set of libraries called the zoneinfo database (or Olson database) that basically documents each and every DST transition for each country. The gem tzinfo is the Ruby version of this library, and before Rails 2.1, you would need to work with that library (maybe with the help of a plugin) to make timezones work, but now it is baked into Rails 2.1 and above.
The first step would be to store your users timezone information. The simplest way to do this is to store their timezone string against their user details, which I like to do using a grouped select tag:
<select name="user[timezone]" id="user_timezone"> <%= option_groups_from_collection_for_select TZInfo::Country.all.sort, "zones", "name", "identifier", "friendly_identifier", @user ? @user.timezone : nil %> </select>
Which will save entries like “Australia/Melbourne” or “America/Los Angeles”. Next, you just need to tell Rails what timezone each request is in, which is now extremely simple using the Time.zone= method. Call this method in a before_filter in application.rb and it will set the timezone for each request:
def timezone_manager Time.zone = current_user.timezone if current_user && current_user.timezone end
Nice and easy! Every time you user or print out a timezone, it will be in the users local zone.
If you want to find out about more of the stuff you can do with the Rails 2.1 timezone support, check out the fantastic tutorial at: http://mad.ly/2008/04/09/rails-21-time-zone-support-an-overview/