joestelmach.com

Portable Time Zones In Rails Applications

It seems like such a simple concept: I live in New Jersey; therefore I would like all of the dated material on my site to be displayed and managed in the Eastern Time zone. I'm sure some of you are wondering why I'm wasting my time writing about such a simple concept. Set the server time zone to US/Eastern and all should be well, right? Not so fast. Suppose your server is in California and you don't have the option of changing the time zone.

This is the situation I encountered recently while putting this site together to be hosted by Dreamhost. After thinking way too long about how to solve this problem, I'd like to discuss my solution for maintaining flexible, time zone independent dates in Rails applications.

The Problem

Let's take a look at what we're trying to accomplish:

That's it. Seems simple enough right?

What most people will probably do at first

OK, the server is on Pacific Time, so all dates generated on the system clock will be in Pacific Time. The Pacific Time zone is exactly 3 hours behind the Eastern Time zone, so the problem can be fixed by simply adding 3 hours to all the dates being generated by the System clock. Your SQL will change to: mysql>date_add(sysdate(), interval 3 hour);
and, more appropriately for a Rails application, Your Ruby code will change to:
time_in_eastern = Time.now() + (60 * 60 * 3)

So we're close to coming up with a solution: our users will give us all times in the Eastern Time zone, and all system clock generated times will be converted to the Eastern Time zone by adding 3 hours. In researching message boards and articles on the subject, it seems like this is probably the most popular solution (not necessarily only for Rails apps, but in general.) However, we should consider the following drawbacks to this solution:

  1. strftime("%Z") will still display the Pacific Time zone by default for any dates retrieved with ActiveRecord.
  2. Your application will be hard-coded with a 3 hour interval difference. What if webspace becomes ridiculously cheap in Russia and you'd like to move your application? (Hey, music is, so why not web space?)
  3. If you decide at a later time that you would like to work with your dates in some other time zone, say Central time, you will need to change the time interval in your code to 2 instead of 3, and subtract an hour from all the dates that have been stored already. Not exactly portable.

Where does this leave us?

OK, so strftime("%Z") is displaying Pacific Time, even though we are sure that all of our times are stored as Eastern Time. Believe it or not, this can be fixed with a simple configuration of your application. Whooaa! A configuration...In a Rails app? But that's impossible!

The Rails team prides itself on 'convention over configuration,' and for the most part, following the conventions will lead to predictable results. However, in this situation, the convention is for ActiveRecord to default to using whatever time zone your server is in when retrieving dates. This is a problem for us, since we would like to work with our dates in some other time zone. The solution requires us to add an entry to the bottom of the environment.rb file (under the /public directory):

ActiveRecord::Base.default_timezone = :est
This will tell ActiveRecord to retrieve all dates as Eastern Time Zone dates, regardless of the server's actual time zone.

Now all of the bases are covered, and we have come up with a first run solution to the problem. If this solution works for you, and you are happy with keeping track of all these conversions, then go for it. I personally would like a better solution. A solution that would allow me to move my application, or my database, to a server living in a different time zone. I would also like the freedom to change my mind as to what time zone my application prefers to work in (think about selling your application to a customer. Shouldn't they choose the time zone?)


Enter UTC Time

UTC, or Universal Time Code, is a modern day synonym for Greenwich Mean Time (they are not exactly the same, but for our purposes here, they can be thought of as the same.) If we store all of our dates in UTC, then we have the freedom of moving our application and our database to servers in any time zone. Additionally, we can use an environment variable to tell our application what time zone to use when generating timestamps off of the system clock. The Ruby Time API is written to transparently handle all of the conversions between our local time zone dates, and the UTC dates we will be storing in the database.

Let's Implement

First we'll edit our environment.rb file to inform ActiveRecord that our dates are stored in UTC:

ActiveRecord::Base.default_timezone = :utc
Now, we'll add an additional entry to the environment.rb file to set up an environment variable informing rails that the local time zone should be Eastern Time:
ENV['TZ'] = 'US/Eastern'
And thats it! Our application is now configured for portable time zones. As a best practice, I would recommend appending '_UTC' to the end of the database column name responsible for storing your dates. This will make it obvious to anyone viewing the table that the dates are stored in UTC.



Common Usage of The Time API

Our app is now configured and ready to roll, so lets look at how we can easily work with our new portable dates with the use of the Ruby Time library. For the following code snippets, let's assume we have a 'Blog' model with a column 'date_added_utc'


Aren't We Forgetting Something?

When discussing the problems with time zones, we said that mysql>sysdate() generated times in the wrong time zone. We haven't said a word about it since. I personally feel that you shouldn't ever have to use this in your Rails apps, since you would be straying from the ORM-driven path provided by ActiveRecord. However, I realize that some developers might wish to use straight SQL in their applications, so I'm here to help. If you would like to generate current UTC timestamps in sql, you can use the convert_tz construct (If your using MySql, this is only available in versions 4.1.3 and greater.) The syntax is as follows:

mysql>convert_tz(systime(), 'PST', 'UTC')
This will return the conversion of the current system time from Pacific Time to UTC time. Remember, we are once again at the mercy of the location of the database server. In order to make this a bit more maintainable, we could use a sepaarate environment variable to specify the time zone of the database server so it won't be hard-coded in our application. But again, my advice is to not use database-generated timestamps in favor of working directly in Ruby. If you disagree, then this should give you some direction as to how to use them practically to keep your application maintainable.

Comments
blog comments powered by Disqus