Rails' ActiveSupport::TimeWithZone
objects have both a timezone code and offset, e.g. Thu, 28 Mar 2019 16:00:00 CET +01:00
. Ruby's stdlib TZInfo
also has time zones, but with different identifiers.
Unfortunately, not all timezone codes can be used to parse strings or to move time objects into another time zone.
Some timezone codes like CET
are supported by ActiveSupport extensions like String#in_time_zone
, while many codes will actually not work:
>> '2019-03-01 12:00'.in_time_zone('PST')
ArgumentError (Invalid Timezone: PST)
Here is why and what to do about it.
tl;dr: Use ActiveSupport's zone identifiers and convert them to zone codes where necessary (snippet at the end).
There are many countries in the world, and while some time zones are shared by multiple countries, it does not necessarily have to be that way forever.
Also, some use the same abbreviation for their time zones. For example, "BST" could mean
while "PST" could be
It's actually really good that you can not use identifiers like "BST" because chances are you'll end up in the wrong time zone.
This is why Rails actually uses a long list of time zone names with have names like "London" or "Pacific Time (US & Canada)". They are basically from the
IANA Time Zone Database
Show archive.org snapshot
that TZInfo
uses (like "Europe/London"
), but simplified.
You may eventually encounter DateTime.strptime
. It's a "better" Time.parse
where you can actually define the date format to parse, e.g.
>> DateTime.strptime('2019-03-01 12:00 PST', '%F %R %Z')
=> Fri, 01 Mar 2019 12:00:00 -0800
However, this will fail you in several ways:
time_zone_select
, you will be juggling zone names like "Berlin" which are actually unsupported.>> DateTime.strptime('2019-03-01 12:00 Berlin', '%F %R %Z')
=> Fri, 01 Mar 2019 12:00:00 +0000
This should not be UTC. And no, Europe/Berlin
won't work either. You could pass +01:00
but would still have no DST checks from Rails as described above.
If you are only interested in a time zone's offset, you might consider Time.zone_offset
and it might work sometimes:
>> Time.zone_offset('PST')
=> -28800
Ruby's Time
actually keeps a
list of custom identifiers
Show archive.org snapshot
.
Other identifiers will not work and simply return nil
.
>> Time.zone_offset('BST')
=> nil
>> Time.zone_offset('Berlin')
=> nil
I'm confident there are other Time
methods with similar pitfalls.
As mentioned earlier, ActiveSupport actually comes with a long list of time zones that should work well for you. Rails even has a time_zone_select
that makes selecting a time zone okay-ish for users.
However, identifiers are Strings like "Berlin"
, "Pacific Time (US & Canada)"
which can be confusing. If your application uses those strings to express time zones, "16:00 Berlin" can be misleading when combined with a location like "Munich".
So you could just read TimeWithZone#zone
, right?
>> '2019-03-28 16:00'.in_time_zone('Berlin').zone
=> "CET"
You can, except when you can not:
>> '2019-03-28 16:00'.in_time_zone('Almaty').zone
=> "+06"
Not every time zone has a pretty abbreviation. In such cases, you'll end up with a semi-broken offset identifier that humans won't understand.
It's not as bad as it seems.
TZInfo::Timezone.all
unless you want to do all the heavy lifting yourself.
String#in_time_zone
from ActiveSupport like above."+06"
, do something like this:def human_timezone(time_string, timezone)
time = time_string.in_time_zone(timezone)
if time.zone.match?(/^\w/)
time.zone
else
time.formatted_offset
end
end
Examples:
>> human_timezone('2019-03-28 16:00', 'Pacific Time (US & Canada)')
=> "PDT"
>> human_timezone('2019-03-28 16:00', 'Berlin')
=> "CET"
>> human_timezone('2019-05-01 16:00', 'Almaty')
=> "+06:00"