Clientside Timezone Detection

On the server side of web apps, sometimes we need to know what timezone a user is in. Like, for example, when we want to present a list of upcoming appointments.

Rails makes handling timezone jankery a piece of cake. It stores times in UTC to the database and handles conversion to the current timezone that Time.zone is set to.

We can easily change the global timezone setting in config/application.rb by changing config.time_zone. But when we have different users in different timezones, we need to change the timezone not globally for the whole app, but on each request, for each user. We can do this in an around_filter in the controller, but I’ll explain that in a minute.

Back to the user—how do we know what timezone they’re in? We could ask them to tell us by presenting a dropdown with a bunch of timezones and they can choose from, but that’s a pain. What happens then if they travel? They have to change their timezone setting again. Not ideal.

Theres a better way to do this. The computer already has a timezone setting, and the browser (via Javascript) already knows what it is. We just need a way to send that info to the server when a request is made. If we can get the timezone, we can send it up with a cookie.

How to reliably get the current timezone with Javascript

The Javascript Date object has a getTimezoneOffset method which returns an integer timezone offset, but that doesn’t quite get us what we need. Daylight savings time complicates things, and we also need the timezone represented as a string to hand to Rails, like “America/Los Angeles”, not an integer offset. Luckily, we can use the excellent jsTimezoneDetect library. This lets us get the timezone like this:

var tz = jstz.determine();
tz.name(); // returns "America/Los Angeles"

Putting it all together

Now that we have a reliable way to determine the user’s current timezone, here’s how to drop it into a cookie using jQuery and jquery.cookie:

jQuery(function() {
  var tz = jstz.determine();
  $.cookie('timezone', tz.name(), { path: '/' });
});

Then in a Rails app, we can use around_filter in ApplicationController to set the timezone for this request only, and reset it after the request is done. I should mention that if we instead use a before_filter and don’t reset the timezone at the end of the request, the change would persist across multiple requests for all users, potentialy setting the wrong timezone for other users. That would be bad.

class ApplicationController
  # ...
  around_filter :with_timezone

  private

  def with_timezone
    timezone = Time.find_zone(cookies[:timezone])
    Time.use_zone(timezone) { yield }
  end
end

With all that in place, we now know which timezones our users are in, without even having to ask them.

Update 1/6/2015: Simplified the around_filter implementation with suggestion in comments. Thanks Felix!

comments powered by Disqus