Rails, Moment.js And Time Zones
Here are a few quick tips for the time zone aware Rails developer that finds themselves deep into JavaScript date objects. First, use the Moment.js JavaScrpt date library! Moment.js has a very rich API for parsing and working with times, very similiar to ActiveSupport's extensions. However, it does not have a solid way of moving times across zones. Especially if those zones may or may not observer daylight savings time (DST).
Many JavaScript time zone libraries require a huge set of geographic data to both identify zones and their observance of DST. These data files can add a significant overhead to JavaScript. But wouldn't it be great if there was a simple way of leveraging your Rails model's time zone settings? There is, but first we need to serialize an ActiveSupport::TimeZone
object in JSON. Easy, just define an #as_json
method like the one below. I suggest adding this to an initializer in your Rails config/initializers/active_support.rb
directory. The key attribute here is the utc_total_offset
. This will be a number in minutes that properly observes DST.
module ActiveSupport
class TimeZone
def as_json(options=nil)
{ :name => name,
:identifier => tzinfo.identifier,
:friendly_identifier => tzinfo.friendly_identifier,
:utc_offset => utc_offset,
:utc_total_offset => tzinfo.current_period.utc_total_offset }
end
end
end
Now assuming you have serialized a models time zone attribute using the full ActiveSupport::TimeZone
object, we can easily use this information client side via a quick extension to Moment.js' prototype. Here is a moment.js.coffee
file I have required in my Rails applications.
moment.fn.forTimeZone = (timeZone) ->
currentOffset = (this.zone() * 60) * -1
adjustedOfffset = if currentOffset > timeZone.utc_total_offset
timeZone.utc_total_offset - currentOffset
else
currentOffset - timeZone.utc_total_offset
this.clone().add 'seconds', adjustedOfffset
So we let Ruby do all the hard work of telling us what time zones are observing DST without all the bloat to our JavaScript for parsing zone identifiers. Some sample output.
eastern = {
friendly_identifier: "America - New York",
identifier: "America/New_York",
name: "Eastern Time (US & Canada)",
utc_offset: -18000,
utc_total_offset: -14400
}
noonPacific = 1344279600000 // "2012-08-06T12:00:00-07:00"
format = 'MMMM Do, YYYY \\at h:mma'
moment(noonPacific).format(format)
// "August 6th, 2012 at 12:00pm"
moment(noonPacific).forTimeZone(eastern).format(format)
// "August 6th, 2012 at 3:00pm"