How to Deal with Timezones the ActiveSupport Way
Writing software aware of different timezones can be a daunting task. Luckily, Ruby on Rails’ ActiveSupport library has some very nice built in features that can prove invaluable when facing time related issues.
ActiveSupport in Ruby on Rails 4.0+ has a built in list of all supported timezones on the
This list of timezones can be used when parsing strings into
Time objects and converting an existing
Time object from one timezone to another.One interesting detail about this list of timezones is the lack of daylight savings time qualifiers. Keeping the timezones agnostic of daylight savings helps simplify their use. A developer does not need to worry about using one timezone object over another due to the time of the year.
ActiveSupport library adds some functionality to built in Ruby classes. One of those additions is the ability to set and retrieve the
zone attribute on the
Time class. After a
zone is set, it can be used when parsing strings into
Time.zone = 'Pacific Time (US & Canada)' Time.zone.parse('2016-04-01 10:00:00') #=> Fri, 01 Apr 2016 10:00:00 PDT -07:00
zone attribute persists for the rest of the Ruby runtime, potentially causing unexpected behaviour at a later time. Luckily,
ActiveSupport solves this problem with the
use_zone method. This method accepts the same set of strings as its single argument and expects a block. Within the passed in block is the only place
Time.zone is affected, eliminating the possibility of a
zone sticking around longer than intended:
Time.zone.name #=> "UTC" Time.use_zone('Pacific Time (US & Canada)') do Time.zone.name # => "Pacific Time (US & Canada)" Time.zone.parse('2016-04-01 10:00:00') #=> Fri, 01 Apr 2016 10:00:00 PDT -07:00 end Time.zone.name #=> "UTC"
zone setting can be very helpful if a set of users are processed, each with their own respective time zone.
When dealing with
DateTime objects, converting each from one timezone to another can be tedious. The
in_time_zone method removes some complexity from those operations. Used alone, the
in_time_zone can transform an existing object into an instance of
now = Time.now # => 2016-04-04 03:55:24 +0000 now.in_time_zone('Hawaii') # => Sun, 03 Apr 2016 17:55:24 HST -10:00
In any sane system, timestamps in a database are always stored in UTC, aka Zulu, time. When exposing these timestamps to users, the
in_time_zone method can quickly switch the timestamp to a user’s local time. To see a more creative use of
in_time_zone, we can assume that an application must solve a scheduling problem. An example application wants to send its users an email at the exact same time relative to a user’s timezone. Regardless of where a user lives, they should receive an email at 10:30 AM local time.
A naive approach might not yield the intended result:
user.time_zone # => Hawaii send_time = Time.new(2016, 04, 01, 10, 30) # => 2016-04-01 10:30:00 +0000 send_time.in_time_zone(user.time_zone) # => Fri, 01 Apr 2016 00:30:00 HST -10:00
Whoops, that is not right at all. This code initialized a
Time object for the “correct” send time but then
in_time_zone not only moved the timezone of that object, it also modified the actual time. This would result in all users getting an email at the same exact moment but at an inconvenient time relative to where they live. Potential solutions to this problem might include modifying the timezone part of an outputted string (the "+0000" at the end) and re-parsing it, but that solution can be prone to error and hard to understand. Since the
in_time_zone method works on the
Date class, the simplest approach would be to initialize a new
DateTime object in a user’s timezone and add 10.5 hours to it:
user.time_zone # => Hawaii beginning_of_day = Date.today.in_time_zone(user.time_zone) # => Sun, 03 Apr 2016 00:00:00 HST -10:00 beginning_of_day += (10.5).hours # => Sun, 03 Apr 2016 10:30:00 HST -10:00 sender = ImportantEmailSender.new(user) sender.send_at!(beginning_of_day)
Great! An email will be sent at 10:30 AM Hawaii time and we successfully avoided any time string munging or other strange object casting. A user in London can also receive an email at exactly 10:30 AM their local time and this code need not grow in complexity.