Timezones: From Rails to EmberJS and Back Again

The Later calendar is a prominent feature of the Later web app. It is the first thing that users are directed to when they log in, and it is the place where all social media scheduling happens in the web app. At Later, we have users from all around the world, and in many cases they are teams of users with each person being in a different part of the world. Given this, how does a user plan out a posting schedule for Instagram, for example, that’s to be posted by another person on their team who’s located halfway around the world?

Fortunately, since pretty much the beginning of Later, we had been asking for users’ timezones when they created their accounts. Those were simpler days, and much of our web app was Rails-based, with some snippets of BackboneJS and later EmberJS on the front end.

At the time, the calendar library we were using had no support for timezones, so users’ timezones were just being stored but not used in any way. Then came some major changes, with consolidation of the front end to being a pretty robust Ember app, with the back end in Rails. The calendar library got updated to v2, and with the update came timezone support. As well, we had a lot of users requesting support for being able to see the calendar in a different timezone, or changing their default timezone on the web app.

So, a few things had to be covered by our implementation of timezone support:

  • A user should be able to change their default timezone that the calendar loads in. They should also be able to change the timezone they had set upon account creation.
  • A user should be able to select a different timezone on their calendar to see what their scheduled posts would look like in another timezone. Along with this, they should be able to schedule their posts for the timezone that they selected.

Timezones are hard 😕

One major thing that anyone who incorporates timezone support into their app learns is that timezones are hard. Like, really hard. Even though we treat timezones as a solely view-level concept, there were still several problems around timezones that we needed to solve.

Thankfully, the fact that timezones are difficult to implement in code has been acknowledged for a very long time, and some very smart people have written libraries to help deal with timezones.

In JavaScript, the go-to library for timezones is Moment Timezone. It’s pretty easy to implement, and it’s well documented.

However, if you have an Ember front end that uses Moment Timezone and a Rails back end which uses ActiveSupport’s timezones, you will soon experience another problem: there is a fairly significant discrepancy between the number of timezones in these packages.

When you load Moment Timezone with all timezone data, you end up with 588 different timezones. On the other hand, the ActiveSupport::TimeZone class limits the number to 146 “meaningful” zones. You can check out this gist to see the mapping.

So what do we have?

  • User objects in Rails, some of which have a timezone set to the Rails-friendly name, such as “Pacific Time (US & Canada)”, while others have no timezone set.
  • A front-end that has 588 loaded timezones, with no knowledge of how these zones map to their Rails counterparts.

So what do we do?

The solution was to simplify everyone’s lives a little and stick with the subset of 146 “meaningful” timezones from Rails. (And honestly, the drop-down list of 588 options looked like something out of a front-end nightmare).

Setup

We created an endpoint in Rails which simply returns all of the Rails timezone objects as JSON. Each returned object only requires three properties: the timezone name in Rails (e.g. "Pacific Time (US & Canada)"), the timezone identifier (e.g. "America/Los_Angeles"), and the timezone GMT offset (e.g. "-08:00").

ActiveSupport::TimeZone.all.map{ |zone| {name: zone.name, identifier: zone.tzinfo.identifier, offset: zone.formatted_offset} }

On the Ember side, there is a service which grabs all of the timezones from that Rails endpoint and stores them as an array of Ember objects.

var self = this;
Ember.$.getJSON('timezones.json').then(function(response) {
  self.set('timezones', response.timezones.map(function(zone) { return TimeZone.create(zone); }));
});

This service is also responsible for tracking the “current” timezone the Ember app should use.

if (!Ember.isEmpty(this.get('currentUser.timeZone'))){
  this.set('currentTimeZone', {name: this.get('currentUser.timeZone'), identifier: this.get('currentUser.timeZoneIdentifier')});
} else {
  var timeZone = moment.tz.guess();
  this.set('currentTimeZone', {name: timeZone, identifier: timeZone});
}

(When the current user does not have a timezone set, we use Moment Timezone’s guess() function to determine the user’s timezone. This function returns the timezone represented by its identifier – e.g. "America/Vancouver".)

The timezone list and the currently set timezone can now be accessed throughout the Ember app, by injecting the service into whichever route/controller/component needs to access this information.

Use

Updating a user’s default timezone

A user can update their default timezone in their account settings. We decided to implement a basic select drop-down list that represents timezones the same way that Rails does by default, where each timezone looks something like: “(GMT-08:00) Pacific Time (US & Canada)”. This is where we use that GMT offset string sent down from Rails for every timezone:

<option value={{timezone.name}}>(GMT{{timezone.offset}}) {{timezone.name}}</option>

For Rails to correctly update a user’s timezone, it must receive the Rails-friendly string representation of that timezone. For example, if I wanted to update my timezone to "America/New_York", I’d have to tell Rails that I am updating the timezone to "Eastern Time (US & Canada)".

Updating the timezone on the calendar

The Later calendar now displays the currentTimeZone’s Rails-derived name on the calendar. When a user clicks on this name, a modal appears that lets the user select a different timezone that they wish to view the calendar in. When a different timezone is selected on this modal, the currentTimeZone property is updated on the service, and the calendar must be re-rendered.

var name = this.get('timeZoneToSet.name');
var identifier = this.get('timeZoneToSet.identifier');
this.set('currentTimeZone', { name: name, identifier: identifier });

And that’s pretty much it! 😀 Once the decision was made to limit ourselves to the timezones defined by Rails, timezone support became a much simpler task, and then it was just a question of how to get the timezones into Ember and then back to Rails.