I am sitting here in Singapore, where it is currently 1pm on 6th March (my laptop's system time is also set to this time).
I'm working for a client with their Rails app setup in Palo Alto, where it is currently 9pm on 5th March.
Their application is processing some data from London, where it is currently 5am on 6th March.
So... how do we handle time (and time-like) variables in this app? This is a long article, so you can also skip straight to the summary.
For this article, we are concerned with 3 different "times":
Location | Label | Current Time |
---|---|---|
Singapore | My time / System time | 1pm, 6th March |
London | Selected time | 5am, 6th March |
Palo Alto | Application time | 9pm, 5th March |
Some notes to remember:
In this example, London time is in the GMT timezone which makes it easier since it is also UTC (+00:00 offset). However this is not always the case.
Although both of these are used interchangeably in casual language, they are technically different.
Here's what happens:
Command | Returns | Notes |
---|---|---|
Time.now | Time 2015-03-06 13:00:00 +0800 | System timezone (SG) |
Time.now.in_time_zone Time.current | ActiveSupport::TimeWithZone Thu, 05 Mar 2015 21:00:00 PST -08:00 | Application timezone (CA) |
Time.now.in_time_zone(nil) | Time 2015-03-06 13:00:00 +0800 | System timezone Careful! A nil timezone defaults to system time and returns a Time object. Time.now.in_time_zone and Time.now.in_time_zone(nil) are different! |
Time.now.in_time_zone("London") | ActiveSupport::TimeWithZone Fri, 06 Mar 2015 05:00:00 GMT +00:00 | Selected timezone (London) |
Time.now.in_time_zone("UTC") | ActiveSupport::TimeWithZone Fri, 06 Mar 2015 05:00:00 UTC +00:00 | Selected timezone (UTC) |
Time.now.utc | Time 2015-03-06 05:00:00 UTC | Selected timezone (UTC) Careful!Time.now.in_time_zone("UTC") and Time.now.utc are equivalent but different classes. |
DateTime.now | DateTime Fri, 06 Mar 2015 13:00:00 +0800 | System timezone (SG) |
DateTime.now.in_time_zone | ActiveSupport::TimeWithZone Thu, 05 Mar 2015 21:00:00 PST -08:00 | Application timezone (CA) |
DateTime.now.in_time_zone("UTC") | ActiveSupport::TimeWithZone Fri, 06 Mar 2015 05:00:00 UTC +00:00 | Selected timezone (UTC) |
DateTime.now.utc | DateTime Fri, 06 Mar 2015 05:00:00 +0000 | Selected timezone (UTC) Careful!DateTime.now.in_time_zone("UTC") and DateTime.now.utc are equivalent but different classes. |
You can format any Time, DateTime, or ActiveSupport::TimeWithZone object into a string using a list of format characters.
Variable | Command | Returns |
---|---|---|
Time | t = Time.now t.strftime("%Y-%m-%d %H:%M") | "2015-03-06 13:00" (System time, 1pm in SG) |
ActiveSupport::TimeWithZone | t = Time.now.in_time_zone("London") t.strftime("%Y-%m-%d %H:%M") | "2015-03-06 05:00" (Selected time, 5am in London)) |
DateTime | t = DateTime.now t.strftime("%Y-%m-%d %H:%M") | "2015-03-06 13:00" (System time, 1pm in SG) |
Assuming these variables:
Command | Returns | Notes |
---|---|---|
Time.strptime(t, format) Time.parse(sample_time) | Time 2015-03-06 13:00:00 +0800 | Has system timezone info, but not converted. |
Time.zone.parse(t) | ActiveSupport::TimeWithZone Fri, 06 Mar 2015 13:00:00 PST -08:00 | Has application timezone info, but not converted. |
DateTime.strptime(t, format) DateTime.parse(sample_time) | DateTime Fri, 06 Mar 2015 13:00:00 +0000 | Careful! The numbers show system time (ie 1pm in SG), but this DateTime object is actually in UTC time. |
t.in_time_zone | ActiveSupport::TimeWithZone Fri, 06 Mar 2015 13:00:00 PST -08:00 | Careful! The numbers show system time (ie 1pm in SG), but this ActiveSupport::TimeWithZone object is actually in application time (ie CA). |
t.in_time_zone(nil) | Time Fri, 06 Mar 2015 13:00:00 +0800 | Careful! The numbers show system time (ie 1pm in SG), but this Time object is actually in system time (ie SG). |
t.in_time_zone("London") | ActiveSupport::TimeWithZone Fri, 06 Mar 2015 13:00:00 GMT +00:00 | Careful! The numbers show system time (ie 1pm in SG), but this ActiveSupport::TimeWithZone object is actually in selected time (ie London). |
Assuming these variables:
Command | Returns | Notes |
---|---|---|
Time.strptime(t, format) Time.parse(sample_time) | Time 2015-11-06 13:00:00 +0800 | Has system timezone info, but not converted. Careful! If you don’t include timezone format in your format variable, the timezone does not get parsed. |
Time.zone.parse(t) | ActiveSupport::TimeWithZone Fri, 06 Mar 2015 05:00:00 PST -08:00 | Has application timezone info, and also converted. |
DateTime.strptime(t, format) | DateTime Fri, 06 Mar 2015 13:00:00 +0000 | Has selected timezone info, but not converted. Careful! If you don’t include timezone format in your format variable, the timezone does not get parsed. |
DateTime.parse(t) | DateTime Fri, 06 Mar 2015 13:00:00 +0000 | Has selected timezone info, and also converted. |
t.in_time_zone | ActiveSupport::TimeWithZone Fri, 06 Mar 2015 05:00:00 PST -08:00 | Has application timezone info, and also converted. |
Command | Returns | Notes |
---|---|---|
2.hours.ago | ActiveSupport::TimeWithZone Thu, 05 Mar 2015 19:00:00 PST | Application timezone |
Date.current | Date Thu, 05 Mar 2015 | Application timezone |
Date.today | Date Fri, 06 Mar 2015 | System timezone |
There's also the use_zone method:
Time.now # => System time, e.g. 2015-03-06 13:00:00 +0800
Time.zone.now # => Application time, e.g. Thu, 05 Mar 2015 21:00:00 PST -08:00
Time.use_zone("London") do
p Time.now # => still System time, e.g. 2015-03-06 13:00:00 +0800
p Time.zone.now # => London time, e.g. Fri, 06 Mar 2015 05:00:00 GMT +00:00
end
After this long list of different commands, here's what I keep in mind: