TZInfo - Ruby Time Zone Library

RubyGems Tests

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:

  1. 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.
  2. 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 (using the search path specified by
TZInfo::ZoneinfoDataSource::DEFAULT_SEARCH_PATH).

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/Adibdjan", "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')
# => #

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))
# => #
tz.to_local(TZInfo::Timestamp.create(2018, 7, 1, 12, 30, 0, 0, :utc))
# => #

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)
# => #
tz.local_timestamp(2018, 2, 1, 7, 30, 0)
# => #

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')
# => #
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')
# => #
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')
# => #
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.