You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

407 lines
14 KiB

# TZInfo - Ruby Time Zone Library
[![RubyGems](https://img.shields.io/gem/v/tzinfo)](https://rubygems.org/gems/tzinfo) [![Travis CI Build](https://img.shields.io/travis/com/tzinfo/tzinfo?logo=travis)](https://travis-ci.com/github/tzinfo/tzinfo) [![AppVeyor Build](https://img.shields.io/appveyor/build/philr/tzinfo?logo=appveyor)](https://ci.appveyor.com/project/philr/tzinfo)
[TZInfo](https://tzinfo.github.io) 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](https://www.iana.org/time-zones)
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](https://tzinfo.github.io/datasourcenotfound) 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
to `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](http://www.iana.org/time-zones). The
[Theory and pragmatics of the tz code and data](https://data.iana.org/time-zones/theory.html)
document gives details of how the data is organized and managed.
## Example Usage
To use TZInfo, it must first be required with:
```ruby
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:
```ruby
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`:
```ruby
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`:
```ruby
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`:
```ruby
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:
```ruby
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:
```ruby
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:
```ruby
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:
```ruby
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:
```ruby
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:
```ruby
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:
```ruby
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):
```ruby
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:
```ruby
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):
```ruby
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:
```ruby
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:
```ruby
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:
```ruby
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](https://www.iso.org/iso-3166-country-codes.html) 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`:
```ruby
TZInfo::Country.all_codes
# => ["AD", "AE", ..., "ZW"]
```
A `TZInfo::Country` instance representing an individual time zone can be
obtained with `TZInfo::Country.get`:
```ruby
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:
```ruby
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:
```ruby
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:
```ruby
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](https://data.iana.org/time-zones/theory.html#naming)).
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.
```ruby
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:
```ruby
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](https://www.rubydoc.info/gems/tzinfo/).
## License
TZInfo is released under the MIT license, see LICENSE for details.
## Source Code
Source code for TZInfo is available on
[GitHub](https://github.com/tzinfo/tzinfo).
## Issue Tracker
Please post any bugs, issues, feature requests or questions about TZInfo to the
[GitHub issue tracker](https://github.com/tzinfo/tzinfo/issues).
Issues with the underlying time zone data should be raised on the
[Time Zone Database Discussion mailing list](https://mm.icann.org/mailman/listinfo/tz).