Codementor Events

Localizing time in a Traditional Rails app with Moment.js

Published Jun 25, 2015Last updated Mar 21, 2017
Localizing time in a Traditional Rails app with Moment.js

In Ruby, a moment in time is typically represented as a Time object. In a Twitter-like
app, for example, the time of each tweet would be coded as
a Time. Internally, objects of this type represent moments in time as the number of
seconds since the UNIX Epoch
(January 1, 1970 at midnight, UTC).

The use of Coordinated Universal Time (UTC)
gives us a standard to measure time against. But, of course, there's a
user-experience challenge we need to deal with. I've used Rails to create Woofr, a skeletal Twitter clone, to illustrate the point. You can play along at home by cloning the repo and following the installation instructions in the README.

Let's look at the timeline of randomly-generated tweets... er, barks... as displayed when we git-checkout
our first commit:

Controller

def index
  @barks = Bark.includes(:user).order('barked_at DESC').all
end

View

<% @barks.each do |b| %>
  <li>
    <div class="bark-username">@<%= b.user.username %></div>
    <div class="bark-body"><%= b.body %></div>
    <time class="bark-barked-at"
      datetime="<%= b.barked_at.to_datetime %>"
      >barked at <span class="timestring"><%= b.barked_at %></span></time>
  </li>
<% end %>

enter image description here

Our poor user is going to have to mentally convert UTC to their local time in order to make sense of the dates of each bark, assuming that they are even aware of what "UTC" means.

What we really need to do is localize these times for the user. While it's possible to do this within Rails, there's a better solution. The browser is already aware of the user's timezone! Browser-side time localization libraries work most naturally with a browser-centric app, but it's still easy to retrofit one onto our traditional Rails app to convert UTC times to local times.

Disclaimer: for clarity, the code in these examples is the bare minimum necessary to demonstrate these techniques; there is no error handling.

Localizing Time with Moment.js

To do this, we include Moment.js in our project via the momentjs-rails gem, declare a class on each string we want to convert, and use Moment to replace each string. Check out this commit on GitHub for full details.

$('.timestring').each(function() {
  this.textContent = moment(this.textContent).format('lll');
});

enter image description here

Tada! Compare that with the original version above.

The lll format specifies a human-readable form of the date at level-3 verbosity using short words, hence the three lowercase "L".

Standardizing User Input to UTC

The flip side is that we need to convert user input (presumably local time) into UTC for storage. Let's use Moment again for that. For the sake of the example, let's assume that a user can specify the time of a bark (at this commit):

enter image description here

When we submit this form, the time is stored in the database as if the user had entered it in UTC: Wed, 03 Feb 2016 12:00:00 UTC +00:00

We need to convert according to our assumption that the user has entered a local time. With this commit, we'll add a hidden field as the "real" bark time, and use a handler on the submit event of our form to grab the user-supplied value, convert it, and set the UTC time on the hidden field.

View

Barked at
<%= datetime_field_tag :raw_barked_at %>
<%= f.hidden_field :barked_at %>

JavaScript

$('.bark-form').submit(function() {
  var barkMoment = moment($('#raw_barked_at').val());

  if (barkMoment.isValid()) {
    $('#bark_barked_at').val(barkMoment.toISOString());
  }
});

This time the database has: Wed, 03 Feb 2016 17:00:00 UTC +00:00

Bingo!

Bonus: Local Language and Format

Moment.js also has optional support for fully localizing times with languages and formats; all you need to do is require each Moment.js locale that you want to make available.

Summary

With a library like Moment.js, it's easy to convert system times to something the user will fully understand, and also to store user-supplied times in a uniform way.

Discover and read more posts from Yitz Schaffer
get started