TZInfo - Ruby Time Zone Library
TZInfo is a Ruby library that provides access to time zone data and allows times to be converted using time zone rules.
Data Sources
TZInfo requires a source of time zone data. There are two options:
- A zoneinfo directory containing timezone definition files. These files are
generated from the IANA Time Zone Database
using the
zic
utility. Most Unix-like systems include a zoneinfo directory. - The TZInfo::Data library (the tzinfo-data gem). TZInfo::Data contains a set of Ruby modules that are also generated from the IANA Time Zone Database.
By default, TZInfo will attempt to use TZInfo::Data. If TZInfo::Data is not
available (i.e. if require 'tzinfo/data'
fails), then TZInfo will search for a
zoneinfo directory instead (by checking the directories specified by
TZInfo::DataSources::ZoneinfoDataSource.search_path
in turn).
If no data source can be found, a TZInfo::DataSourceNotFound
exception will be
raised when TZInfo is used. Further information is available
in the wiki to help resolve
TZInfo::DataSourceNotFound
errors.
The default data source selection can be overridden by calling
TZInfo::DataSource.set
.
Custom data sources can also be used. See the TZInfo::DataSource.set
documentation for further details.
Installation
The TZInfo gem can be installed by running gem install tzinfo
or by adding
gem 'tzinfo'
to your Gemfile
and running bundle install
.
To use the Ruby modules as the data source, TZInfo::Data will also need to be
installed by running gem install tzinfo-data
or by adding gem 'tzinfo-data'
to your Gemfile
.
IANA Time Zone Database
The data returned and used by TZInfo is sourced from the IANA Time Zone Database. The Theory and pragmatics of the tz code and data document gives details of how the data is organized and managed.
Example Usage
To use TZInfo, it must first be required with:
require 'tzinfo'
The TZInfo::Timezone
class provides access to time zone data and methods for
converting times.
The all_identifiers
method returns a list of valid time zone identifiers:
identifiers = TZInfo::Timezone.all_identifiers
# => ["Africa/Abidjan", "Africa/Accra", ..., "Zulu"]
A TZInfo::Timezone
instance representing an individual time zone can be
obtained with TZInfo::Timezone.get
:
tz = TZInfo::Timezone.get('America/New_York')
# => #<TZInfo::DataTimezone: America/New_York>
A time can be converted to the local time of the time zone with to_local
:
tz.to_local(Time.utc(2018, 2, 1, 12, 30, 0))
# => 2018-02-01 07:30:00 -0500
tz.to_local(Time.utc(2018, 7, 1, 12, 30, 0))
# => 2018-07-01 08:30:00 -0400
tz.to_local(Time.new(2018, 7, 1, 13, 30, 0, '+01:00'))
# => 2018-07-01 08:30:00 -0400
Local times with the appropriate offset for the time zone can be constructed
with local_time
:
tz.local_time(2018, 2, 1, 7, 30, 0)
# => 2018-02-01 07:30:00 -0500
tz.local_time(2018, 7, 1, 8, 30, 0)
# => 2018-07-01 08:30:00 -0400
Local times can be converted to UTC by using local_time
and calling utc
on
the result:
tz.local_time(2018, 2, 1, 7, 30, 0).utc
# => 2018-02-01 12:30:00 UTC
tz.local_time(2018, 7, 1, 8, 30, 0).utc
# => 2018-07-01 12:30:00 UTC
The local_to_utc
method can also be used to convert a time object to UTC. The
offset of the time is ignored - it is treated as if it were a local time for the
time zone:
tz.local_to_utc(Time.utc(2018, 2, 1, 7, 30, 0))
# => 2018-02-01 12:30:00 UTC
tz.local_to_utc(Time.new(2018, 2, 1, 7, 30, 0, '+01:00'))
# => 2018-02-01 12:30:00 UTC
Information about the time zone can be obtained from returned local times:
local_time = tz.to_local(Time.utc(2018, 2, 1, 12, 30, 0))
local_time.utc_offset # => -18000
local_time.dst? # => false
local_time.zone # => "EST"
local_time = tz.to_local(Time.utc(2018, 7, 1, 12, 30, 0))
local_time.utc_offset # => -14400
local_time.dst? # => true
local_time.zone # => "EDT"
Time zone information can be included when formatting times with strftime
using the %z
and %Z
directives:
tz.to_local(Time.utc(2018, 2, 1, 12, 30, 0)).strftime('%Y-%m-%d %H:%M:%S %z %Z')
# => "2018-02-01 07:30:00 -0500 EST"
tz.to_local(Time.utc(2018, 7, 1, 12, 30, 0)).strftime('%Y-%m-%d %H:%M:%S %z %Z')
# => "2018-07-01 08:30:00 -0400 EDT"
The period_for
method can be used to obtain information about the observed
time zone information at a particular time as a TZInfo::TimezonePeriod
object:
period = tz.period_for(Time.utc(2018, 7, 1, 12, 30, 0))
period.base_utc_offset # => -18000
period.std_offset # => 3600
period.observed_utc_offset # => -14400
period.abbreviation # => "EDT"
period.dst? # => true
period.local_starts_at.to_time # => 2018-03-11 03:00:00 -0400
period.local_ends_at.to_time # => 2018-11-04 02:00:00 -0400
A list of transitions between periods where different rules are observed can be
obtained with the transitions_up_to
method. The result is returned as an
Array
of TZInfo::TimezoneTransition
objects:
transitions = tz.transitions_up_to(Time.utc(2019, 1, 1), Time.utc(2017, 1, 1))
transitions.map do |t|
[t.local_end_at.to_time, t.offset.observed_utc_offset, t.offset.abbreviation]
end
# => [[2017-03-12 02:00:00 -0500, -14400, "EDT"],
# [2017-11-05 02:00:00 -0400, -18000, "EST"],
# [2018-03-11 02:00:00 -0500, -14400, "EDT"],
# [2018-11-04 02:00:00 -0400, -18000, "EST"]]
A list of the unique offsets used by a time zone can be obtained with the
offsets_up_to
method. The result is returned as an Array
of
TZInfo::TimezoneOffset
objects:
offsets = tz.offsets_up_to(Time.utc(2019, 1, 1))
offsets.map {|o| [o.observed_utc_offset, o.abbreviation] }
# => [[-17762, "LMT"],
# [-18000, "EST"],
# [-14400, "EDT"],
# [-14400, "EWT"],
# [-14400, "EPT"]]
All TZInfo::Timezone
methods that accept a time as a parameter can be used
with either instances of Time
, DateTime
or TZInfo::Timestamp
. Arbitrary
Time
-like objects that respond to both to_i
and subsec
and optionally
utc_offset
will be treated as if they are instances of Time
.
TZInfo::Timezone
methods that both accept and return times will return an
object with a type matching that of the parameter (actually a
TZInfo::TimeWithOffset
, TZInfo::DateTimeWithOffset
or
TZInfo::TimestampWithOffset
subclass when returning a local time):
tz.to_local(Time.utc(2018, 7, 1, 12, 30, 0))
# => 2018-07-01 08:30:00 -0400
tz.to_local(DateTime.new(2018, 7, 1, 12, 30, 0))
# => #<TZInfo::DateTimeWithOffset: 2018-07-01T08:30:00-04:00 ((2458301j,45000s,0n),-14400s,2299161j)>
tz.to_local(TZInfo::Timestamp.create(2018, 7, 1, 12, 30, 0, 0, :utc))
# => #<TZInfo::TimestampWithOffset: @value=1530448200, @sub_second=0, @utc_offset=-14400, @utc=false>
In addition to local_time
, which returns Time
instances, the
local_datetime
and local_timestamp
methods can be used to construct local
DateTime
and TZInfo::Timestamp
instances with the appropriate offset:
tz.local_time(2018, 2, 1, 7, 30, 0)
# => 2018-02-01 07:30:00 -0500
tz.local_datetime(2018, 2, 1, 7, 30, 0)
# => #<TZInfo::DateTimeWithOffset: 2018-02-01T07:30:00-05:00 ((2458151j,45000s,0n),-18000s,2299161j)>
tz.local_timestamp(2018, 2, 1, 7, 30, 0)
# => #<TZInfo::TimestampWithOffset: @value=1517488200, @sub_second=0, @utc_offset=-18000, @utc=false>
The local_to_utc
, local_time
, local_datetime
and local_timestamp
methods
may raise a TZInfo::PeriodNotFound
or a TZInfo::AmbiguousTime
exception.
TZInfo::PeriodNotFound
signals that there is no equivalent UTC time (for
example, during the transition from standard time to daylight savings time when
the clocks are moved forward and an hour is skipped). TZInfo::AmbiguousTime
signals that there is more than one equivalent UTC time (for example, during the
transition from daylight savings time to standard time where the clocks are
moved back and an hour is repeated):
tz.local_time(2018, 3, 11, 2, 30, 0, 0)
# raises TZInfo::PeriodNotFound (2018-03-11 02:30:00 is an invalid local time.)
tz.local_time(2018, 11, 4, 1, 30, 0, 0)
# raises TZInfo::AmbiguousTime (2018-11-04 01:30:00 is an ambiguous local time.)
TZInfo::PeriodNotFound
exceptions can only be resolved by adjusting the time,
for example, by advancing an hour:
tz.local_time(2018, 3, 11, 3, 30, 0, 0)
# => 2018-03-11 03:30:00 -0400
TZInfo::AmbiguousTime
exceptions can be resolved by setting the dst
parameter and/or specifying a block to choose one of the interpretations:
tz.local_time(2018, 11, 4, 1, 30, 0, 0, true)
# => 2018-11-04 01:30:00 -0400
tz.local_time(2018, 11, 4, 1, 30, 0, 0, false)
# => 2018-11-04 01:30:00 -0500
tz.local_time(2018, 11, 4, 1, 30, 0, 0) {|p| p.first }
# => 2018-11-04 01:30:00 -0400
tz.local_time(2018, 11, 4, 1, 30, 0, 0) {|p| p.last }
# => 2018-11-04 01:30:00 -0500
The default value of the dst
parameter can also be set globally:
TZInfo::Timezone.default_dst = true
tz.local_time(2018, 11, 4, 1, 30, 0, 0)
# => 2018-11-04 01:30:00 -0400
TZInfo::Timezone.default_dst = false
tz.local_time(2018, 11, 4, 1, 30, 0, 0)
# => 2018-11-04 01:30:00 -0500
TZInfo also provides information about
ISO 3166-1 countries and
their associated time zones via the TZInfo::Country
class.
A list of valid ISO 3166-1 (alpha-2) country codes can be obtained by calling
TZInfo::Country.all_codes
:
TZInfo::Country.all_codes
# => ["AD", "AE", ..., "ZW"]
A TZInfo::Country
instance representing an individual time zone can be
obtained with TZInfo::Country.get
:
c = TZInfo::Country.get('US')
# => #<TZInfo::Country: US>
c.name
# => "United States"
The zone_identifiers
method returns a list of the time zone identifiers used
in a country:
c.zone_identifiers
# => ["America/New_York", "America/Detroit", ..., "Pacific/Honolulu"]
The zone_info
method returns further information about the time zones used in
a country as an Array
of TZInfo::CountryTimezone
instances:
zi = c.zone_info.first
zi.identifier # => "America/New_York"
zi.latitude.to_f.round(5) # => 40.71417
zi.longitude.to_f.round(5) # => -74.00639
zi.description # => "Eastern (most areas)"
The zones
method returns an Array
of TZInfo::Timezone
instances for a
country. A TZInfo::Timezone
instance can be obtained from a
TZInfo::CountryTimezone
using the timezone
method:
zi.timezone.to_local(Time.utc(2018, 2, 1, 12, 30, 0))
# => 2018-02-01 07:30:00 -0500
For further detail, please refer to the API documentation for the
TZInfo::Timezone
and TZInfo::Country
classes.
Time Zone Selection
The Time Zone Database maintainers recommend that time zone identifiers are not made visible to end-users (see Names of timezones).
Instead of displaying a list of time zone identifiers, time zones can be
selected by the user's country. Call TZInfo::Country.all
to obtain a list of
TZInfo::Country
objects, each with a unique code
and a name
that can be
used for display purposes.
Most countries have a single time zone. When choosing such a country, the time zone can be inferred and selected automatically.
croatia = TZInfo::Country.get('HR')
# => #<TZInfo::Country: HR>
croatia.zone_info.length
# => 1
croatia.zone_info[0].identifier
# => "Europe/Belgrade"
Some countries have multiple time zones. The zone_info
method can be used
to obtain a list of user-friendly descriptions of the available options:
australia = TZInfo::Country.get('AU')
# => #<TZInfo::Country: AU>
australia.zone_info.length
# => 13
australia.zone_info.map {|i| [i.identifier, i.description] }
# => [["Australia/Lord_Howe", "Lord Howe Island"],
# ["Antarctica/Macquarie", "Macquarie Island"],
# ...
# ["Australia/Eucla", "Western Australia (Eucla)"]]
Please note that country information available through TZInfo is intended as an aid to help users select a time zone data appropriate for their practical needs. It is not intended to take or endorse any position on legal or territorial claims.
Compatibility
TZInfo v2.0.0 requires a minimum of Ruby MRI 1.9.3 or JRuby 1.7 (in 1.9 mode or later).
Thread-Safety
The TZInfo::Country
and TZInfo::Timezone
classes are thread-safe. It is safe
to use class and instance methods of TZInfo::Country
and TZInfo::Timezone
in
concurrently executing threads. Instances of both classes can be shared across
thread boundaries.
Documentation
API documentation for TZInfo is available on RubyDoc.info.
License
TZInfo is released under the MIT license, see LICENSE for details.
Source Code
Source code for TZInfo is available on GitHub.
Issue Tracker
Please post any bugs, issues, feature requests or questions about TZInfo to the GitHub issue tracker.
Issues with the underlying time zone data should be raised on the Time Zone Database Discussion mailing list.