tzinfo-2.0.4/0000755000004100000410000000000013773021353013062 5ustar www-datawww-datatzinfo-2.0.4/README.md0000644000004100000410000003317113773021353014346 0ustar www-datawww-data# 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') # => # ``` 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)) # => # 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: ```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) # => # 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): ```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') # => # 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') # => # 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') # => # 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). tzinfo-2.0.4/data.tar.gz.sig0000444000004100000410000000040013773021353015673 0ustar www-datawww-dataK(8mwE1E^(;ŮK HGznsZʈ~{o.oT4~oՓx9^,/(aAj`1O)Q' 0N}%5)ErY6 %EPj^j=*GMQiUf;#e+GL΋r+ ؾXKt^G4='9'[AoA5G!5ZA޵tzinfo-2.0.4/metadata.gz.sig0000444000004100000410000000040013773021353015755 0ustar www-datawww-dataNKr &/2/ѩE'%G/^ "I52BIQn])|S_3 =Gpc o= ` յ~ɜj}?k?>TlMV`š =c]ᵿo1&܉[}FUz9y%3}L<  ԢhN-Ƀ_k>WW2vogNtzinfo-2.0.4/checksums.yaml.gz.sig0000444000004100000410000000040013773021353017123 0ustar www-datawww-data"/ޗS{2Eq&[ABE/V%:[ʏ%D_ q!]O6*P1j}b5$0Ξc1 fvUd=(AGNȖJ4W{Dl1mN?$,=rFŘ29$ǒ'(u*~2}^aeۆW}w\wy=}`~%4*2roo.D+Otzinfo-2.0.4/LICENSE0000644000004100000410000000204413773021353014067 0ustar www-datawww-dataCopyright (c) 2005-2020 Philip Ross Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. tzinfo-2.0.4/tzinfo.gemspec0000644000004100000410000001244313773021353015744 0ustar www-datawww-data######################################################### # This file has been automatically generated by gem2tgz # ######################################################### # -*- encoding: utf-8 -*- # stub: tzinfo 2.0.4 ruby lib Gem::Specification.new do |s| s.name = "tzinfo".freeze s.version = "2.0.4" s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version= s.metadata = { "bug_tracker_uri" => "https://github.com/tzinfo/tzinfo/issues", "changelog_uri" => "https://github.com/tzinfo/tzinfo/blob/master/CHANGES.md", "documentation_uri" => "https://rubydoc.info/gems/tzinfo/2.0.4", "homepage_uri" => "https://tzinfo.github.io", "source_code_uri" => "https://github.com/tzinfo/tzinfo/tree/v2.0.4" } if s.respond_to? :metadata= s.require_paths = ["lib".freeze] s.authors = ["Philip Ross".freeze] s.cert_chain = ["-----BEGIN CERTIFICATE-----\nMIIDPDCCAiSgAwIBAgIBATANBgkqhkiG9w0BAQsFADAkMSIwIAYDVQQDDBlwaGls\nLnJvc3MvREM9Z21haWwvREM9Y29tMB4XDTE5MTIyNDE0NTU0N1oXDTM5MTIyNDE0\nNTU0N1owJDEiMCAGA1UEAwwZcGhpbC5yb3NzL0RDPWdtYWlsL0RDPWNvbTCCASIw\nDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJGcwfqn4ZsmPl0b1Lt9dCzExrE5\nEeP/CRQjBdGHkF+mSpi69XysxdwLdfg5SPr9LfxthUug4nNFd5fDCiXM8hYe9jQD\nTmkIQKNBh4fFpGngn9gyy+SumCXi6b5L6d/aMc59NAOM6LJ88TOdH1648dh5rq3C\nULq82n3gg4+u0HHGjRPuR/pnCFQCZbANYdX+UBWd0qkOJn/EreNKROmEeHr/xKuh\n2/GlKFKt9KLcW3hwBB4fHHVYUzRau7D1m9KbEERdg//qNDC4B7fD2BFJuPbM5S7J\n41VwDAh1O8B/Qpg0f+S83K4Kodw4MiPGsug55UkNtd3mGR/zZJ9WM03DSwkCAwEA\nAaN5MHcwCQYDVR0TBAIwADALBgNVHQ8EBAMCBLAwHQYDVR0OBBYEFA+Z8zvfzBuA\nesoHIfz7+jxfUOcfMB4GA1UdEQQXMBWBE3BoaWwucm9zc0BnbWFpbC5jb20wHgYD\nVR0SBBcwFYETcGhpbC5yb3NzQGdtYWlsLmNvbTANBgkqhkiG9w0BAQsFAAOCAQEA\nJ80xgZ3gGdQVA8N+8NJANU5HLuZIU9jOaAlziU9ImoTgPiOHKGZC4as1TwT4kBt1\nQcnu7YSANYRrxP5tpOHsWPF/MQYgerAFCZS5+PzOTudwZ+7OsMW4/EMHy6aCVHEd\nc7HzQRC4mSrDRpWxzyBnZ5nX5OAmIkKA8NgeKybT/4Ku6iFPPUQwlyxQaO+Wlxdo\nFqHwpjRyoiVSpe4RUTNK3d3qesWPYi7Lxn6k6ZZeEdvG6ya33AXktE3jmmF+jPR1\nJ3Zn/kSTjTekiaspyGbczC3PUaeJNxr+yCvR4sk71Xmk/GaKKGOHedJ1uj/LAXrA\nMR0mpl7b8zCg0PFC1J73uw==\n-----END CERTIFICATE-----\n".freeze] s.date = "2020-12-16" s.description = "TZInfo provides access to time zone data and allows times to be converted using time zone rules.".freeze s.email = "phil.ross@gmail.com".freeze s.extra_rdoc_files = ["CHANGES.md".freeze, "LICENSE".freeze, "README.md".freeze] s.files = [".yardopts".freeze, "CHANGES.md".freeze, "LICENSE".freeze, "README.md".freeze, "lib/tzinfo.rb".freeze, "lib/tzinfo/annual_rules.rb".freeze, "lib/tzinfo/country.rb".freeze, "lib/tzinfo/country_timezone.rb".freeze, "lib/tzinfo/data_source.rb".freeze, "lib/tzinfo/data_sources.rb".freeze, "lib/tzinfo/data_sources/constant_offset_data_timezone_info.rb".freeze, "lib/tzinfo/data_sources/country_info.rb".freeze, "lib/tzinfo/data_sources/data_timezone_info.rb".freeze, "lib/tzinfo/data_sources/linked_timezone_info.rb".freeze, "lib/tzinfo/data_sources/posix_time_zone_parser.rb".freeze, "lib/tzinfo/data_sources/ruby_data_source.rb".freeze, "lib/tzinfo/data_sources/timezone_info.rb".freeze, "lib/tzinfo/data_sources/transitions_data_timezone_info.rb".freeze, "lib/tzinfo/data_sources/zoneinfo_data_source.rb".freeze, "lib/tzinfo/data_sources/zoneinfo_reader.rb".freeze, "lib/tzinfo/data_timezone.rb".freeze, "lib/tzinfo/datetime_with_offset.rb".freeze, "lib/tzinfo/format1.rb".freeze, "lib/tzinfo/format1/country_definer.rb".freeze, "lib/tzinfo/format1/country_index_definition.rb".freeze, "lib/tzinfo/format1/timezone_definer.rb".freeze, "lib/tzinfo/format1/timezone_definition.rb".freeze, "lib/tzinfo/format1/timezone_index_definition.rb".freeze, "lib/tzinfo/format2.rb".freeze, "lib/tzinfo/format2/country_definer.rb".freeze, "lib/tzinfo/format2/country_index_definer.rb".freeze, "lib/tzinfo/format2/country_index_definition.rb".freeze, "lib/tzinfo/format2/timezone_definer.rb".freeze, "lib/tzinfo/format2/timezone_definition.rb".freeze, "lib/tzinfo/format2/timezone_index_definer.rb".freeze, "lib/tzinfo/format2/timezone_index_definition.rb".freeze, "lib/tzinfo/info_timezone.rb".freeze, "lib/tzinfo/linked_timezone.rb".freeze, "lib/tzinfo/offset_timezone_period.rb".freeze, "lib/tzinfo/string_deduper.rb".freeze, "lib/tzinfo/time_with_offset.rb".freeze, "lib/tzinfo/timestamp.rb".freeze, "lib/tzinfo/timestamp_with_offset.rb".freeze, "lib/tzinfo/timezone.rb".freeze, "lib/tzinfo/timezone_offset.rb".freeze, "lib/tzinfo/timezone_period.rb".freeze, "lib/tzinfo/timezone_proxy.rb".freeze, "lib/tzinfo/timezone_transition.rb".freeze, "lib/tzinfo/transition_rule.rb".freeze, "lib/tzinfo/transitions_timezone_period.rb".freeze, "lib/tzinfo/untaint_ext.rb".freeze, "lib/tzinfo/version.rb".freeze, "lib/tzinfo/with_offset.rb".freeze] s.homepage = "https://tzinfo.github.io".freeze s.licenses = ["MIT".freeze] s.rdoc_options = ["--title".freeze, "TZInfo".freeze, "--main".freeze, "README.md".freeze] s.required_ruby_version = Gem::Requirement.new(">= 1.9.3".freeze) s.rubygems_version = "2.5.2.1".freeze s.summary = "Time Zone Library".freeze if s.respond_to? :specification_version then s.specification_version = 4 if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then s.add_runtime_dependency(%q.freeze, ["~> 1.0"]) else s.add_dependency(%q.freeze, ["~> 1.0"]) end else s.add_dependency(%q.freeze, ["~> 1.0"]) end end tzinfo-2.0.4/lib/0000755000004100000410000000000013773021353013630 5ustar www-datawww-datatzinfo-2.0.4/lib/tzinfo/0000755000004100000410000000000013773021353015141 5ustar www-datawww-datatzinfo-2.0.4/lib/tzinfo/string_deduper.rb0000644000004100000410000000737113773021353020514 0ustar www-datawww-data# encoding: UTF-8 # frozen_string_literal: true require 'concurrent' module TZInfo # Maintains a pool of `String` instances. The {#dedupe} method will return # either a pooled copy of a given `String` or add the instance to the pool. # # @private class StringDeduper #:nodoc: class << self # @return [StringDeduper] a globally available singleton instance of # {StringDeduper}. This instance is safe for use in concurrently # executing threads. attr_reader :global end # Initializes a new {StringDeduper}. def initialize @strings = create_hash do |h, k| v = k.dup.freeze h[v] = v end end # @param string [String] the string to deduplicate. # @return [bool] `string` if it is frozen, otherwise a frozen, possibly # pre-existing copy of `string`. def dedupe(string) return string if string.frozen? @strings[string] end protected # Creates a `Hash` to store pooled `String` instances. # # @param block [Proc] Default value block to be passed to `Hash.new`. # @return [Hash] a `Hash` to store pooled `String` instances. def create_hash(&block) Hash.new(&block) end end private_constant :StringDeduper # A thread-safe version of {StringDeduper}. # # @private class ConcurrentStringDeduper < StringDeduper #:nodoc: protected def create_hash(&block) Concurrent::Map.new(&block) end end private_constant :ConcurrentStringDeduper string_unary_minus_does_dedupe = if '0'.respond_to?(:-@) # :nocov_no_string_-@: s1 = -('0'.dup) s2 = -('0'.dup) s1.object_id == s2.object_id # :nocov_no_string_-@: else # :nocov_string_-@: false # :nocov_string_-@: end if string_unary_minus_does_dedupe # :nocov_no_deduping_string_unary_minus: # An implementation of {StringDeduper} using the `String#-@` method where # that method performs deduplication (Ruby 2.5 and later). # # Note that this is slightly different to the plain {StringDeduper} # implementation. In this implementation, frozen literal strings are already # in the pool and are candidates for being returned, even when passed # another equal frozen non-literal string. {StringDeduper} will always # return frozen strings. # # There are also differences in encoding handling. This implementation will # treat strings with different encodings as different strings. # {StringDeduper} will treat strings with the compatible encodings as the # same string. # # @private class UnaryMinusGlobalStringDeduper #:nodoc: # @param string [String] the string to deduplicate. # @return [bool] `string` if it is frozen, otherwise a frozen, possibly # pre-existing copy of `string`. def dedupe(string) # String#-@ on Ruby 2.6 will dedupe a frozen non-literal String. Ruby # 2.5 will just return frozen strings. # # The pooled implementation can't tell the difference between frozen # literals and frozen non-literals, so must always return frozen String # instances to avoid doing unncessary work when loading format 2 # TZInfo::Data modules. # # For compatibility with the pooled implementation, just return frozen # string instances (acting like Ruby 2.5). return string if string.frozen? -string end end private_constant :UnaryMinusGlobalStringDeduper StringDeduper.instance_variable_set(:@global, UnaryMinusGlobalStringDeduper.new) # :nocov_no_deduping_string_unary_minus: else # :nocov_deduping_string_unary_minus: StringDeduper.instance_variable_set(:@global, ConcurrentStringDeduper.new) # :nocov_deduping_string_unary_minus: end end tzinfo-2.0.4/lib/tzinfo/version.rb0000644000004100000410000000016613773021353017156 0ustar www-datawww-data# encoding: UTF-8 # frozen_string_literal: true module TZInfo # The TZInfo version number. VERSION = '2.0.4' end tzinfo-2.0.4/lib/tzinfo/info_timezone.rb0000644000004100000410000000152113773021353020332 0ustar www-datawww-data# encoding: UTF-8 module TZInfo # A {Timezone} based on a {DataSources::TimezoneInfo}. # # @abstract class InfoTimezone < Timezone # Initializes a new {InfoTimezone}. # # {InfoTimezone} instances should not normally be created directly. Use # the {Timezone.get} method to obtain {Timezone} instances. # # @param info [DataSources::TimezoneInfo] a {DataSources::TimezoneInfo} # instance supplied by a {DataSource} that will be used as the source of # data for this {InfoTimezone}. def initialize(info) super() @info = info end # (see Timezone#identifier) def identifier @info.identifier end protected # @return [DataSources::TimezoneInfo] the {DataSources::TimezoneInfo} this # {InfoTimezone} is based on. def info @info end end end tzinfo-2.0.4/lib/tzinfo/timestamp_with_offset.rb0000644000004100000410000000715613773021353022103 0ustar www-datawww-data# encoding: UTF-8 # frozen_string_literal: true module TZInfo # A subclass of {Timestamp} used to represent local times. # {TimestampWithOffset} holds a reference to the related {TimezoneOffset} and # overrides various methods to return results appropriate for the # {TimezoneOffset}. Certain operations will clear the associated # {TimezoneOffset} (if the {TimezoneOffset} would not necessarily be valid for # the result). Once the {TimezoneOffset} has been cleared, # {TimestampWithOffset} behaves identically to {Timestamp}. class TimestampWithOffset < Timestamp include WithOffset # @return [TimezoneOffset] the {TimezoneOffset} associated with this # instance. attr_reader :timezone_offset # Creates a new {TimestampWithOffset} from a given {Timestamp} and # {TimezoneOffset}. # # @param timestamp [Timestamp] a {Timestamp}. # @param timezone_offset [TimezoneOffset] a {TimezoneOffset} valid at the # time of `timestamp`. # @return [TimestampWithOffset] a {TimestampWithOffset} that has the same # {value value} and {sub_second sub_second} as the `timestamp` parameter, # a {utc_offset utc_offset} equal to the # {TimezoneOffset#observed_utc_offset observed_utc_offset} of the # `timezone_offset` parameter and {timezone_offset timezone_offset} set to # the `timezone_offset` parameter. # @raise [ArgumentError] if `timestamp` or `timezone_offset` is `nil`. def self.set_timezone_offset(timestamp, timezone_offset) raise ArgumentError, 'timestamp must be specified' unless timestamp raise ArgumentError, 'timezone_offset must be specified' unless timezone_offset new!(timestamp.value, timestamp.sub_second, timezone_offset.observed_utc_offset).set_timezone_offset(timezone_offset) end # Sets the associated {TimezoneOffset} of this {TimestampWithOffset}. # # @param timezone_offset [TimezoneOffset] a {TimezoneOffset} valid at the time # and for the offset of this {TimestampWithOffset}. # @return [TimestampWithOffset] `self`. # @raise [ArgumentError] if `timezone_offset` is `nil`. # @raise [ArgumentError] if {utc? self.utc?} is `true`. # @raise [ArgumentError] if `timezone_offset.observed_utc_offset` does not equal # `self.utc_offset`. def set_timezone_offset(timezone_offset) raise ArgumentError, 'timezone_offset must be specified' unless timezone_offset raise ArgumentError, 'timezone_offset.observed_utc_offset does not match self.utc_offset' if utc? || utc_offset != timezone_offset.observed_utc_offset @timezone_offset = timezone_offset self end # An overridden version of {Timestamp#to_time} that, if there is an # associated {TimezoneOffset}, returns a {TimeWithOffset} with that offset. # # @return [Time] if there is an associated {TimezoneOffset}, a # {TimeWithOffset} representation of this {TimestampWithOffset}, otherwise # a `Time` representation. def to_time to = timezone_offset if to new_time(TimeWithOffset).set_timezone_offset(to) else super end end # An overridden version of {Timestamp#to_datetime}, if there is an # associated {TimezoneOffset}, returns a {DateTimeWithOffset} with that # offset. # # @return [DateTime] if there is an associated {TimezoneOffset}, a # {DateTimeWithOffset} representation of this {TimestampWithOffset}, # otherwise a `DateTime` representation. def to_datetime to = timezone_offset if to new_datetime(DateTimeWithOffset).set_timezone_offset(to) else super end end end end tzinfo-2.0.4/lib/tzinfo/timezone_period.rb0000644000004100000410000001550113773021353020664 0ustar www-datawww-data# encoding: UTF-8 # frozen_string_literal: true module TZInfo # {TimezonePeriod} represents a period of time for a time zone where the same # offset from UTC applies. It provides access to the observed offset, time # zone abbreviation, start time and end time. # # The period of time can be unbounded at the start, end, or both the start # and end. # # @abstract Time zone period data will returned as an instance of one of the # subclasses of {TimezonePeriod}. class TimezonePeriod # @return [TimezoneOffset] the offset that applies in the period of time. attr_reader :offset # Initializes a {TimezonePeriod}. # # @param offset [TimezoneOffset] the offset that is observed for the period # of time. # @raise [ArgumentError] if `offset` is `nil`. def initialize(offset) raise ArgumentError, 'offset must be specified' unless offset @offset = offset end # @return [TimezoneTransition] the transition that defines the start of this # {TimezonePeriod} (`nil` if the start is unbounded). def start_transition raise_not_implemented(:start_transition) end # @return [TimezoneTransition] the transition that defines the end of this # {TimezonePeriod} (`nil` if the end is unbounded). def end_transition raise_not_implemented(:end_transition) end # Returns the base offset from UTC in seconds (`observed_utc_offset - # std_offset`). This does not include any adjustment made for daylight # savings time and will typically remain constant throughout the year. # # To obtain the currently observed offset from UTC, including the effect of # daylight savings time, use {observed_utc_offset} instead. # # If you require accurate {base_utc_offset} values, you should install the # tzinfo-data gem and set {DataSources::RubyDataSource} as the {DataSource}. # When using {DataSources::ZoneinfoDataSource}, the value of # {base_utc_offset} has to be derived from changes to the observed UTC # offset and DST status since it is not included in zoneinfo files. # # @return [Integer] the base offset from UTC in seconds. def base_utc_offset @offset.base_utc_offset end alias utc_offset base_utc_offset # Returns the offset from the time zone's standard time in seconds # (`observed_utc_offset - base_utc_offset`). Zero when daylight savings time # is not in effect. Non-zero (usually 3600 = 1 hour) if daylight savings is # being observed. # # If you require accurate {std_offset} values, you should install the # tzinfo-data gem and set {DataSources::RubyDataSource} as the {DataSource}. # When using {DataSources::ZoneinfoDataSource}, the value of {std_offset} # has to be derived from changes to the observed UTC offset and DST status # since it is not included in zoneinfo files. # # @return [Integer] the offset from the time zone's standard time in # seconds. def std_offset @offset.std_offset end # The abbreviation that identifies this offset. For example GMT # (Greenwich Mean Time) or BST (British Summer Time) for Europe/London. # # @return [String] the abbreviation that identifies this offset. def abbreviation @offset.abbreviation end alias abbr abbreviation alias zone_identifier abbreviation # Returns the observed offset from UTC in seconds (`base_utc_offset + # std_offset`). This includes adjustments made for daylight savings time. # # @return [Integer] the observed offset from UTC in seconds. def observed_utc_offset @offset.observed_utc_offset end alias utc_total_offset observed_utc_offset # Determines if daylight savings is in effect (i.e. if {std_offset} is # non-zero). # # @return [Boolean] `true` if {std_offset} is non-zero, otherwise `false`. def dst? @offset.dst? end # Returns the UTC start time of the period or `nil` if the start of the # period is unbounded. # # The result is returned as a {Timestamp}. To obtain the start time as a # `Time` or `DateTime`, call either {Timestamp#to_time to_time} or # {Timestamp#to_datetime to_datetime} on the result. # # @return [Timestamp] the UTC start time of the period or `nil` if the start # of the period is unbounded. def starts_at timestamp(start_transition) end # Returns the UTC end time of the period or `nil` if the end of the period # is unbounded. # # The result is returned as a {Timestamp}. To obtain the end time as a # `Time` or `DateTime`, call either {Timestamp#to_time to_time} or # {Timestamp#to_datetime to_datetime} on the result. # # @return [Timestamp] the UTC end time of the period or `nil` if the end of # the period is unbounded. def ends_at timestamp(end_transition) end # Returns the local start time of the period or `nil` if the start of the # period is unbounded. # # The result is returned as a {TimestampWithOffset}. To obtain the start # time as a `Time` or `DateTime`, call either {TimestampWithOffset#to_time # to_time} or {TimestampWithOffset#to_datetime to_datetime} on the result. # # @return [TimestampWithOffset] the local start time of the period or `nil` # if the start of the period is unbounded. def local_starts_at timestamp_with_offset(start_transition) end # Returns the local end time of the period or `nil` if the end of the period # is unbounded. # # The result is returned as a {TimestampWithOffset}. To obtain the end time # as a `Time` or `DateTime`, call either {TimestampWithOffset#to_time # to_time} or {TimestampWithOffset#to_datetime to_datetime} on the result. # # @return [TimestampWithOffset] the local end time of the period or `nil` if # the end of the period is unbounded. def local_ends_at timestamp_with_offset(end_transition) end private # Raises a {NotImplementedError} to indicate that subclasses should override # a method. # # @raise [NotImplementedError] always. def raise_not_implemented(method_name) raise NotImplementedError, "Subclasses must override #{method_name}" end # @param transition [TimezoneTransition] a transition or `nil`. # @return [Timestamp] the {Timestamp} representing when a transition occurs, # or `nil` if `transition` is `nil`. def timestamp(transition) transition ? transition.at : nil end # @param transition [TimezoneTransition] a transition or `nil`. # @return [TimestampWithOffset] a {Timestamp} representing when a transition # occurs with offset set to {#offset}, or `nil` if `transition` is `nil`. def timestamp_with_offset(transition) transition ? TimestampWithOffset.set_timezone_offset(transition.at, offset) : nil end end end tzinfo-2.0.4/lib/tzinfo/timezone_proxy.rb0000644000004100000410000000624413773021353020567 0ustar www-datawww-data# encoding: UTF-8 module TZInfo # A proxy class standing in for a {Timezone} with a given identifier. # {TimezoneProxy} inherits from {Timezone} and can be treated identically to # {Timezone} instances loaded with {Timezone.get}. # # {TimezoneProxy} instances are used to avoid the performance overhead of # loading time zone data into memory, for example, by {Timezone.all}. # # The first time an attempt is made to access the data for the time zone, the # real {Timezone} will be loaded is loaded. If the proxy's identifier was not # valid, then an exception will be raised at this point. class TimezoneProxy < Timezone # Initializes a new {TimezoneProxy}. # # The `identifier` parameter is not checked when initializing the proxy. It # will be validated when the real {Timezone} instance is loaded. # # @param identifier [String] an IANA Time Zone Database time zone # identifier. def initialize(identifier) super() @identifier = identifier @real_timezone = nil end # (see Timezone#identifier) def identifier @real_timezone ? @real_timezone.identifier : @identifier end # (see Timezone#period_for) def period_for(time) real_timezone.period_for_utc(time) end # (see Timezone#periods_for_local) def periods_for_local(local_time) real_timezone.periods_for_local(local_time) end # (see Timezone#transitions_up_to) def transitions_up_to(to, from = nil) real_timezone.transitions_up_to(to, from) end # (see Timezone#canonical_zone) def canonical_zone real_timezone.canonical_zone end # Returns a serialized representation of this {TimezoneProxy}. This method # is called when using `Marshal.dump` with an instance of {TimezoneProxy}. # # @param limit [Integer] the maximum depth to dump - ignored. @return # [String] a serialized representation of this {TimezoneProxy}. # @return [String] a serialized representation of this {TimezoneProxy}. def _dump(limit) identifier end # Loads a {TimezoneProxy} from the serialized representation returned by # {_dump}. This is method is called when using `Marshal.load` or # `Marshal.restore` to restore a serialized {Timezone}. # # @param data [String] a serialized representation of a {TimezoneProxy}. # @return [TimezoneProxy] the result of converting `data` back into a # {TimezoneProxy}. def self._load(data) TimezoneProxy.new(data) end private # Returns the real {Timezone} instance being proxied. # # The real {Timezone} is loaded using {Timezone.get} on the first access. # # @return [Timezone] the real {Timezone} instance being proxied. def real_timezone # Thread-safety: It is possible that the value of @real_timezone may be # calculated multiple times in concurrently executing threads. It is not # worth the overhead of locking to ensure that @real_timezone is only # calculated once. unless @real_timezone result = Timezone.get(@identifier) return result if frozen? @real_timezone = result end @real_timezone end end end tzinfo-2.0.4/lib/tzinfo/timezone_transition.rb0000644000004100000410000001002613773021353021571 0ustar www-datawww-data# encoding: UTF-8 # frozen_string_literal: true module TZInfo # Represents a transition from one observed UTC offset ({TimezoneOffset} to # another for a time zone. class TimezoneTransition # @return [TimezoneOffset] the offset this transition changes to. attr_reader :offset # @return [TimezoneOffset] the offset this transition changes from. attr_reader :previous_offset # When this transition occurs as an `Integer` number of seconds since # 1970-01-01 00:00:00 UTC ignoring leap seconds (i.e. each day is treated as # if it were 86,400 seconds long). Equivalent to the result of calling the # {Timestamp#value value} method on the {Timestamp} returned by {at}. # # @return [Integer] when this transition occurs as a number of seconds since # 1970-01-01 00:00:00 UTC ignoring leap seconds. attr_reader :timestamp_value # Initializes a new {TimezoneTransition}. # # {TimezoneTransition} instances should not normally be constructed # manually. # # @param offset [TimezoneOffset] the offset the transition changes to. # @param previous_offset [TimezoneOffset] the offset the transition changes # from. # @param timestamp_value [Integer] when the transition occurs as a # number of seconds since 1970-01-01 00:00:00 UTC ignoring leap seconds # (i.e. each day is treated as if it were 86,400 seconds long). def initialize(offset, previous_offset, timestamp_value) @offset = offset @previous_offset = previous_offset @timestamp_value = timestamp_value end # Returns a {Timestamp} instance representing the UTC time when this # transition occurs. # # To obtain the result as a `Time` or `DateTime`, call either # {Timestamp#to_time to_time} or {Timestamp#to_datetime to_datetime} on the # {Timestamp} instance that is returned. # # @return [Timestamp] the UTC time when this transition occurs. def at Timestamp.utc(@timestamp_value) end # Returns a {TimestampWithOffset} instance representing the local time when # this transition causes the previous observance to end (calculated from # {at} using {previous_offset}). # # To obtain the result as a `Time` or `DateTime`, call either # {TimestampWithOffset#to_time to_time} or {TimestampWithOffset#to_datetime # to_datetime} on the {TimestampWithOffset} instance that is returned. # # @return [TimestampWithOffset] the local time when this transition causes # the previous observance to end. def local_end_at TimestampWithOffset.new(@timestamp_value, 0, @previous_offset.observed_utc_offset).set_timezone_offset(@previous_offset) end # Returns a {TimestampWithOffset} instance representing the local time when # this transition causes the next observance to start (calculated from {at} # using {offset}). # # To obtain the result as a `Time` or `DateTime`, call either # {TimestampWithOffset#to_time to_time} or {TimestampWithOffset#to_datetime # to_datetime} on the {TimestampWithOffset} instance that is returned. # # @return [TimestampWithOffset] the local time when this transition causes # the next observance to start. def local_start_at TimestampWithOffset.new(@timestamp_value, 0, @offset.observed_utc_offset).set_timezone_offset(@offset) end # Determines if this {TimezoneTransition} is equal to another instance. # # @param tti [Object] the instance to test for equality. # @return [Boolean] `true` if `tti` is a {TimezoneTransition} with the same # {offset}, {previous_offset} and {timestamp_value} as this # {TimezoneTransition}, otherwise `false`. def ==(tti) tti.kind_of?(TimezoneTransition) && offset == tti.offset && previous_offset == tti.previous_offset && timestamp_value == tti.timestamp_value end alias eql? == # @return [Integer] a hash based on {offset}, {previous_offset} and # {timestamp_value}. def hash [@offset, @previous_offset, @timestamp_value].hash end end end tzinfo-2.0.4/lib/tzinfo/untaint_ext.rb0000644000004100000410000000060413773021353020030 0ustar www-datawww-data# encoding: UTF-8 # frozen_string_literal: true module TZInfo # Object#untaint is deprecated in Ruby >= 2.7 and will be removed in 3.2. # UntaintExt adds a refinement to make Object#untaint a no-op and avoid the # warning. # # @private module UntaintExt # :nodoc: refine Object do def untaint self end end end private_constant :UntaintExt end tzinfo-2.0.4/lib/tzinfo/country_timezone.rb0000644000004100000410000000663213773021353021112 0ustar www-datawww-data# encoding: UTF-8 module TZInfo # Information about a time zone used by a {Country}. class CountryTimezone # @return [String] the identifier of the {Timezone} being described. attr_reader :identifier # The latitude of this time zone in degrees. Positive numbers are degrees # north and negative numbers are degrees south. # # Note that depending on the data source, the position given by {#latitude} # and {#longitude} may not be within the country. # # @return [Rational] the latitude in degrees. attr_reader :latitude # The longitude of this time zone in degrees. Positive numbers are degrees # east and negative numbers are degrees west. # # Note that depending on the data source, the position given by {#latitude} # and {#longitude} may not be within the country. # # @return [Rational] the longitude in degrees. attr_reader :longitude # A description of this time zone in relation to the country, e.g. "Eastern # Time". This is usually `nil` for countries that have a single time zone. # # @return [String] an optional description of the time zone. attr_reader :description # Creates a new {CountryTimezone}. # # The passed in identifier and description instances will be frozen. # # {CountryTimezone} instances should normally only be constructed # by implementations of {DataSource}. # # @param identifier [String] the {Timezone} identifier. # @param latitude [Rational] the latitude of the time zone. # @param longitude [Rational] the longitude of the time zone. # @param description [String] an optional description of the time zone. def initialize(identifier, latitude, longitude, description = nil) @identifier = identifier.freeze @latitude = latitude @longitude = longitude @description = description && description.freeze end # Returns the associated {Timezone}. # # The result is actually an instance of {TimezoneProxy} in order to defer # loading of the time zone transition data until it is first needed. # # @return [Timezone] the associated {Timezone}. def timezone Timezone.get_proxy(@identifier) end # @return [String] the {description} if present, otherwise a human-readable # representation of the identifier (using {Timezone#friendly_identifier}). def description_or_friendly_identifier description || timezone.friendly_identifier(true) end # Tests if the given object is equal to the current instance (has the same # identifier, latitude, longitude and description). # # @param ct [Object] the object to be compared. # @return [TrueClass] `true` if `ct` is equal to the current instance. def ==(ct) ct.kind_of?(CountryTimezone) && identifier == ct.identifier && latitude == ct.latitude && longitude == ct.longitude && description == ct.description end # Tests if the given object is equal to the current instance (has the same # identifier, latitude, longitude and description). # # @param ct [Object] the object to be compared. # @return [Boolean] `true` if `ct` is equal to the current instance. def eql?(ct) self == ct end # @return [Integer] a hash based on the {identifier}, {latitude}, # {longitude} and {description}. def hash [@identifier, @latitude, @longitude, @description].hash end end end tzinfo-2.0.4/lib/tzinfo/data_timezone.rb0000644000004100000410000000251213773021353020311 0ustar www-datawww-data# encoding: UTF-8 # frozen_string_literal: true module TZInfo # Represents time zones that are defined by rules that set out when # transitions occur. class DataTimezone < InfoTimezone # (see Timezone#period_for) def period_for(time) raise ArgumentError, 'time must be specified' unless time timestamp = Timestamp.for(time) raise ArgumentError, 'time must have a specified utc_offset' unless timestamp.utc_offset info.period_for(timestamp) end # (see Timezone#periods_for_local) def periods_for_local(local_time) raise ArgumentError, 'local_time must be specified' unless local_time info.periods_for_local(Timestamp.for(local_time, :ignore)) end # (see Timezone#transitions_up_to) def transitions_up_to(to, from = nil) raise ArgumentError, 'to must be specified' unless to to_timestamp = Timestamp.for(to) from_timestamp = from && Timestamp.for(from) begin info.transitions_up_to(to_timestamp, from_timestamp) rescue ArgumentError => e raise ArgumentError, e.message.gsub('_timestamp', '') end end # Returns the canonical {Timezone} instance for this {DataTimezone}. # # For a {DataTimezone}, this is always `self`. # # @return [Timezone] `self`. def canonical_zone self end end end tzinfo-2.0.4/lib/tzinfo/annual_rules.rb0000644000004100000410000000525613773021353020166 0ustar www-datawww-data# encoding: UTF-8 # frozen_string_literal: true module TZInfo # A set of rules that define when transitions occur in time zones with # annually occurring daylight savings time. # # @private class AnnualRules #:nodoc: # @return [TimezoneOffset] the standard offset that applies when daylight # savings time is not in force. attr_reader :std_offset # @return [TimezoneOffset] the offset that applies when daylight savings # time is in force. attr_reader :dst_offset # @return [TransitionRule] the rule that determines when daylight savings # time starts. attr_reader :dst_start_rule # @return [TransitionRule] the rule that determines when daylight savings # time ends. attr_reader :dst_end_rule # Initializes a new {AnnualRules} instance. # # @param std_offset [TimezoneOffset] the standard offset that applies when # daylight savings time is not in force. # @param dst_offset [TimezoneOffset] the offset that applies when daylight # savings time is in force. # @param dst_start_rule [TransitionRule] the rule that determines when # daylight savings time starts. # @param dst_end_rule [TransitionRule] the rule that determines when daylight # savings time ends. def initialize(std_offset, dst_offset, dst_start_rule, dst_end_rule) @std_offset = std_offset @dst_offset = dst_offset @dst_start_rule = dst_start_rule @dst_end_rule = dst_end_rule end # Returns the transitions between standard and daylight savings time for a # given year. The results are ordered by time of occurrence (earliest to # latest). # # @param year [Integer] the year to calculate transitions for. # @return [Array] the transitions for the year. def transitions(year) start_dst = apply_rule(@dst_start_rule, @std_offset, @dst_offset, year) end_dst = apply_rule(@dst_end_rule, @dst_offset, @std_offset, year) end_dst.timestamp_value < start_dst.timestamp_value ? [end_dst, start_dst] : [start_dst, end_dst] end private # Applies a given rule between offsets on a year. # # @param rule [TransitionRule] the rule to apply. # @param from_offset [TimezoneOffset] the offset the rule transitions from. # @param to_offset [TimezoneOffset] the offset the rule transitions to. # @param year [Integer] the year when the transition occurs. # @return [TimezoneTransition] the transition determined by the rule. def apply_rule(rule, from_offset, to_offset, year) at = rule.at(from_offset, year) TimezoneTransition.new(to_offset, from_offset, at.value) end end private_constant :AnnualRules end tzinfo-2.0.4/lib/tzinfo/format1.rb0000644000004100000410000000027013773021353017036 0ustar www-datawww-data# encoding: UTF-8 module TZInfo # Modules and classes used by the format 1 version of TZInfo::Data. # # @private module Format1 #:nodoc: end private_constant :Format1 end tzinfo-2.0.4/lib/tzinfo/data_source.rb0000644000004100000410000004276413773021353017774 0ustar www-datawww-data# encoding: UTF-8 # frozen_string_literal: true require 'concurrent' require 'thread' module TZInfo # {InvalidDataSource} is raised if the selected {DataSource} doesn't implement # one of the required methods. class InvalidDataSource < StandardError end # {DataSourceNotFound} is raised if no data source could be found (i.e. if # `'tzinfo/data'` cannot be found on the load path and no valid zoneinfo # directory can be found on the system). class DataSourceNotFound < StandardError end # TZInfo can be used with different data sources for time zone and country # data. Each source of data is implemented as a subclass of {DataSource}. # # To choose a data source and override the default selection, use the # {DataSource.set} method. # # @abstract To create a custom data source, create a subclass of {DataSource} # and implement the {load_timezone_info}, {data_timezone_identifiers}, # {linked_timezone_identifiers}, {load_country_info} and {country_codes} # methods. class DataSource # The currently selected data source. # # @private @@instance = nil # A `Mutex` used to ensure the default data source is only created once. # # @private @@default_mutex = Mutex.new class << self # @return [DataSource] the currently selected source of data. def get # If a DataSource hasn't been manually set when the first request is # made to obtain a DataSource, then a default data source is created. # # This is done at the first request rather than when TZInfo is loaded to # avoid unnecessary attempts to find a suitable DataSource. # # A `Mutex` is used to ensure that only a single default instance is # created (this avoiding the possibility of retaining two copies of the # same data in memory). unless @@instance @@default_mutex.synchronize do set(create_default_data_source) unless @@instance end end @@instance end # Sets the currently selected data source for time zone and country data. # # This should usually be set to one of the two standard data source types: # # * `:ruby` - read data from the Ruby modules included in the TZInfo::Data # library (tzinfo-data gem). # * `:zoneinfo` - read data from the zoneinfo files included with most # Unix-like operating systems (e.g. in /usr/share/zoneinfo). # # To set TZInfo to use one of the standard data source types, call # `TZInfo::DataSource.set`` in one of the following ways: # # TZInfo::DataSource.set(:ruby) # TZInfo::DataSource.set(:zoneinfo) # TZInfo::DataSource.set(:zoneinfo, zoneinfo_dir) # TZInfo::DataSource.set(:zoneinfo, zoneinfo_dir, iso3166_tab_file) # # `DataSource.set(:zoneinfo)` will automatically search for the zoneinfo # directory by checking the paths specified in # {DataSources::ZoneinfoDataSource.search_path}. # {DataSources::ZoneinfoDirectoryNotFound} will be raised if no valid # zoneinfo directory could be found. # # `DataSource.set(:zoneinfo, zoneinfo_dir)` uses the specified # `zoneinfo_dir` directory as the data source. If the directory is not a # valid zoneinfo directory, a {DataSources::InvalidZoneinfoDirectory} # exception will be raised. # # `DataSource.set(:zoneinfo, zoneinfo_dir, iso3166_tab_file)` uses the # specified `zoneinfo_dir` directory as the data source, but loads the # `iso3166.tab` file from the path given by `iso3166_tab_file`. If the # directory is not a valid zoneinfo directory, a # {DataSources::InvalidZoneinfoDirectory} exception will be raised. # # Custom data sources can be created by subclassing TZInfo::DataSource and # implementing the following methods: # # * {load_timezone_info} # * {data_timezone_identifiers} # * {linked_timezone_identifiers} # * {load_country_info} # * {country_codes} # # To have TZInfo use the custom data source, call {DataSource.set}, # passing an instance of the custom data source implementation as follows: # # TZInfo::DataSource.set(CustomDataSource.new) # # Calling {DataSource.set} will only affect instances of {Timezone} and # {Country} obtained with {Timezone.get} and {Country.get} subsequent to # the {DataSource.set} call. Existing {Timezone} and {Country} instances # will be unaffected. # # If {DataSource.set} is not called, TZInfo will by default attempt to use # TZInfo::Data as the data source. 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 # {DataSources::ZoneinfoDataSource.search_path}). # # @param data_source_or_type [Object] either `:ruby`, `:zoneinfo` or an # instance of a {DataSource}. # @param args [Array] when `data_source_or_type` is a symbol, # optional arguments to use when initializing the data source. # @raise [ArgumentError] if `data_source_or_type` is not `:ruby`, # `:zoneinfo` or an instance of {DataSource}. def set(data_source_or_type, *args) if data_source_or_type.kind_of?(DataSource) @@instance = data_source_or_type elsif data_source_or_type == :ruby @@instance = DataSources::RubyDataSource.new elsif data_source_or_type == :zoneinfo @@instance = DataSources::ZoneinfoDataSource.new(*args) else raise ArgumentError, 'data_source_or_type must be a DataSource instance or a data source type (:ruby or :zoneinfo)' end end private # Creates a {DataSource} instance for use as the default. Used if no # preference has been specified manually. # # @return [DataSource] the newly created default {DataSource} instance. def create_default_data_source has_tzinfo_data = false begin require 'tzinfo/data' has_tzinfo_data = true rescue LoadError end return DataSources::RubyDataSource.new if has_tzinfo_data begin return DataSources::ZoneinfoDataSource.new rescue DataSources::ZoneinfoDirectoryNotFound raise DataSourceNotFound, "No source of timezone data could be found.\nPlease refer to https://tzinfo.github.io/datasourcenotfound for help resolving this error." end end end # Initializes a new {DataSource} instance. Typically only called via # subclasses of {DataSource}. def initialize @timezones = Concurrent::Map.new end # Returns a {DataSources::TimezoneInfo} instance for the given identifier. # The result will derive from either {DataSources::DataTimezoneInfo} for # time zones that define their own data or {DataSources::LinkedTimezoneInfo} # for links or aliases to other time zones. # # {get_timezone_info} calls {load_timezone_info} to create the # {DataSources::TimezoneInfo} instance. The returned instance is cached and # returned in subsequent calls to {get_timezone_info} for the identifier. # # @param identifier [String] A time zone identifier. # @return [DataSources::TimezoneInfo] a {DataSources::TimezoneInfo} instance # for a given identifier. # @raise [InvalidTimezoneIdentifier] if the time zone is not found or the # identifier is invalid. def get_timezone_info(identifier) result = @timezones[identifier] unless result # Thread-safety: It is possible that multiple equivalent TimezoneInfo # instances could be created here in concurrently executing threads. The # consequences of this are that the data may be loaded more than once # (depending on the data source). The performance benefit of ensuring # that only a single instance is created is unlikely to be worth the # overhead of only allowing one TimezoneInfo to be loaded at a time. result = load_timezone_info(identifier) @timezones[result.identifier] = result end result end # @return [Array] a frozen `Array`` of all the available time zone # identifiers. The identifiers are sorted according to `String#<=>`. def timezone_identifiers # Thread-safety: It is possible that the value of @timezone_identifiers # may be calculated multiple times in concurrently executing threads. It # is not worth the overhead of locking to ensure that # @timezone_identifiers is only calculated once. @timezone_identifiers ||= build_timezone_identifiers end # Returns a frozen `Array` of all the available time zone identifiers for # data time zones (i.e. those that actually contain definitions). The # identifiers are sorted according to `String#<=>`. # # @return [Array] a frozen `Array` of all the available time zone # identifiers for data time zones. def data_timezone_identifiers raise_invalid_data_source('data_timezone_identifiers') end # Returns a frozen `Array` of all the available time zone identifiers that # are links to other time zones. The identifiers are sorted according to # `String#<=>`. # # @return [Array] a frozen `Array` of all the available time zone # identifiers that are links to other time zones. def linked_timezone_identifiers raise_invalid_data_source('linked_timezone_identifiers') end # @param code [String] an ISO 3166-1 alpha-2 country code. # @return [DataSources::CountryInfo] a {DataSources::CountryInfo} instance # for the given ISO 3166-1 alpha-2 country code. # @raise [InvalidCountryCode] if the country could not be found or the code # is invalid. def get_country_info(code) load_country_info(code) end # Returns a frozen `Array` of all the available ISO 3166-1 alpha-2 country # codes. The identifiers are sorted according to `String#<=>`. # # @return [Array] a frozen `Array` of all the available ISO 3166-1 # alpha-2 country codes. def country_codes raise_invalid_data_source('country_codes') end # @return [String] a description of the {DataSource}. def to_s "Default DataSource" end # @return [String] the internal object state as a programmer-readable # `String`. def inspect "#<#{self.class}>" end protected # Returns a {DataSources::TimezoneInfo} instance for the given time zone # identifier. The result should derive from either # {DataSources::DataTimezoneInfo} for time zones that define their own data # or {DataSources::LinkedTimezoneInfo} for links to or aliases for other # time zones. # # @param identifier [String] A time zone identifier. # @return [DataSources::TimezoneInfo] a {DataSources::TimezoneInfo} instance # for the given time zone identifier. # @raise [InvalidTimezoneIdentifier] if the time zone is not found or the # identifier is invalid. def load_timezone_info(identifier) raise_invalid_data_source('load_timezone_info') end # @param code [String] an ISO 3166-1 alpha-2 country code. # @return [DataSources::CountryInfo] a {DataSources::CountryInfo} instance # for the given ISO 3166-1 alpha-2 country code. # @raise [InvalidCountryCode] if the country could not be found or the code # is invalid. def load_country_info(code) raise_invalid_data_source('load_country_info') end # @return [Encoding] the `Encoding` used by the `String` instances returned # by {data_timezone_identifiers} and {linked_timezone_identifiers}. def timezone_identifier_encoding Encoding::UTF_8 end # Checks that the given identifier is a valid time zone identifier (can be # found in the {timezone_identifiers} `Array`). If the identifier is valid, # the `String` instance representing that identifier from # `timezone_identifiers` is returned. Otherwise an # {InvalidTimezoneIdentifier} exception is raised. # # @param identifier [String] a time zone identifier to be validated. # @return [String] the `String` instance equivalent to `identifier` from # {timezone_identifiers}. # @raise [InvalidTimezoneIdentifier] if `identifier` was not found in # {timezone_identifiers}. def validate_timezone_identifier(identifier) raise InvalidTimezoneIdentifier, "Invalid identifier: #{identifier.nil? ? 'nil' : identifier}" unless identifier.kind_of?(String) valid_identifier = try_with_encoding(identifier, timezone_identifier_encoding) {|id| find_timezone_identifier(id) } return valid_identifier if valid_identifier raise InvalidTimezoneIdentifier, "Invalid identifier: #{identifier.encode(Encoding::UTF_8)}" end # Looks up a given code in the given hash of code to # {DataSources::CountryInfo} mappings. If the code is found the # {DataSources::CountryInfo} is returned. Otherwise an {InvalidCountryCode} # exception is raised. # # @param hash [String, DataSources::CountryInfo] a mapping from ISO 3166-1 # alpha-2 country codes to {DataSources::CountryInfo} instances. # @param code [String] a country code to lookup. # @param encoding [Encoding] the encoding used for the country codes in # `hash`. # @return [DataSources::CountryInfo] the {DataSources::CountryInfo} instance # corresponding to `code`. # @raise [InvalidCountryCode] if `code` was not found in `hash`. def lookup_country_info(hash, code, encoding = Encoding::UTF_8) raise InvalidCountryCode, "Invalid country code: #{code.nil? ? 'nil' : code}" unless code.kind_of?(String) info = try_with_encoding(code, encoding) {|c| hash[c] } return info if info raise InvalidCountryCode, "Invalid country code: #{code.encode(Encoding::UTF_8)}" end private # Raises {InvalidDataSource} to indicate that a method has not been # overridden by a particular data source implementation. # # @raise [InvalidDataSource] always. def raise_invalid_data_source(method_name) raise InvalidDataSource, "#{method_name} not defined" end # Combines {data_timezone_identifiers} and {linked_timezone_identifiers} # to create an `Array` containing all valid time zone identifiers. If # {linked_timezone_identifiers} is empty, the {data_timezone_identifiers} # instance is returned. # # The returned `Array` is frozen. The identifiers are sorted according to # `String#<=>`. # # @return [Array] an `Array` containing all valid time zone # identifiers. def build_timezone_identifiers data = data_timezone_identifiers linked = linked_timezone_identifiers linked.empty? ? data : (data + linked).sort!.freeze end if [].respond_to?(:bsearch) # If the given `identifier` is contained within the {timezone_identifiers} # `Array`, the `String` instance representing that identifier from # {timezone_identifiers} is returned. Otherwise, `nil` is returned. # # @param identifier [String] A time zone identifier to search for. # @return [String] the `String` instance representing `identifier` from # {timezone_identifiers} if found, or `nil` if not found. # # :nocov_no_array_bsearch: def find_timezone_identifier(identifier) result = timezone_identifiers.bsearch {|i| i >= identifier } result == identifier ? result : nil end # :nocov_no_array_bsearch: else # If the given `identifier` is contained within the {timezone_identifiers} # `Array`, the `String` instance representing that identifier from # {timezone_identifiers} is returned. Otherwise, `nil` is returned. # # @param identifier [String] A time zone identifier to search for. # @return [String] the `String` instance representing `identifier` from # {timezone_identifiers} if found, or `nil` if not found. # # :nocov_array_bsearch: def find_timezone_identifier(identifier) identifiers = timezone_identifiers low = 0 high = identifiers.length while low < high do mid = (low + high).div(2) mid_identifier = identifiers[mid] cmp = mid_identifier <=> identifier return mid_identifier if cmp == 0 if cmp > 0 high = mid else low = mid + 1 end end nil end # :nocov_array_bsearch: end # Tries an operation using `string` directly. If the operation fails, the # string is copied and encoded with `encoding` and the operation is tried # again. # # @param string [String] The `String` to perform the operation on. # @param encoding [Encoding] The `Encoding` to use if the initial attempt # fails. # @yield [s] the caller will be yielded to once or twice to attempt the # operation. # @yieldparam s [String] either `string` or an encoded copy of `string`. # @yieldreturn [Object] The result of the operation. Must be truthy if # successful. # @return [Object] the result of the operation or `nil` if the first attempt # fails and `string` is already encoded with `encoding`. def try_with_encoding(string, encoding) result = yield string return result if result unless encoding == string.encoding string = string.encode(encoding) yield string end end end end tzinfo-2.0.4/lib/tzinfo/timezone.rb0000644000004100000410000015024313773021353017325 0ustar www-datawww-data# encoding: UTF-8 # frozen_string_literal: true require 'set' module TZInfo # {AmbiguousTime} is raised to indicate that a specified local time has more # than one possible equivalent UTC time. Such ambiguities arise when the # clocks are set back in a time zone, most commonly during the repeated hour # when transitioning from daylight savings time to standard time. # # {AmbiguousTime} is raised by {Timezone#local_datetime}, # {Timezone#local_time}, {Timezone#local_timestamp}, {Timezone#local_to_utc} # and {Timezone#period_for_local} when using an ambiguous time and not # specifying how to resolve the ambiguity. class AmbiguousTime < StandardError end # {PeriodNotFound} is raised to indicate that no {TimezonePeriod} matching a # given time could be found. class PeriodNotFound < StandardError end # {InvalidTimezoneIdentifier} is raised by {Timezone.get} if the identifier # given is not valid. class InvalidTimezoneIdentifier < StandardError end # {UnknownTimezone} is raised when calling methods on an instance of # {Timezone} that was created directly. To obtain {Timezone} instances the # {Timezone.get} method should be used instead. class UnknownTimezone < StandardError end # The {Timezone} class represents a time zone. It provides a factory method, # {get}, to retrieve {Timezone} instances by their identifier. # # The {Timezone#to_local} method can be used to convert `Time` and `DateTime` # instances to the local time for the zone. For example: # # tz = TZInfo::Timezone.get('America/New_York') # local_time = tz.to_local(Time.utc(2005,8,29,15,35,0)) # local_datetime = tz.to_local(DateTime.new(2005,8,29,15,35,0)) # # Local `Time` and `DateTime` instances returned by `Timezone` have the # correct local offset. # # The {Timezone#local_to_utc} method can by used to convert local `Time` and # `DateTime` instances to UTC. {Timezone#local_to_utc} ignores the UTC offset # of the supplied value and treats if it is a local time for the zone. For # example: # # tz = TZInfo::Timezone.get('America/New_York') # utc_time = tz.local_to_utc(Time.new(2005,8,29,11,35,0)) # utc_datetime = tz.local_to_utc(DateTime.new(2005,8,29,11,35,0)) # # Each time zone is treated as sequence of periods of time ({TimezonePeriod}) # that observe the same offset ({TimezoneOffset}). Transitions # ({TimezoneTransition}) denote the end of one period and the start of the # next. The {Timezone} class has methods that allow the periods, offsets and # transitions of a time zone to be interrogated. # # All methods that take `Time` objects as parameters can be used with # arbitrary `Time`-like objects that respond to both `to_i` and `subsec` and # optionally `utc_offset`. # # The {Timezone} class is thread-safe. It is safe to use class and instance # methods of {Timezone} in concurrently executing threads. Instances of # {Timezone} can be shared across thread boundaries. # # The IANA 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)). The # {Country} class can be used to obtain lists of time zones by country, # including user-friendly descriptions and approximate locations. # # @abstract The {get} method returns an instance of either {DataTimezone} or # {LinkedTimezone}. The {get_proxy} method and other methods returning # collections of time zones return instances of {TimezoneProxy}. class Timezone include Comparable # The default value of the dst parameter of the {local_datetime}, # {local_time}, {local_timestamp}, {local_to_utc} and {period_for_local} # methods. # # @!visibility private @@default_dst = nil class << self # Sets the default value of the optional `dst` parameter of the # {local_datetime}, {local_time}, {local_timestamp}, {local_to_utc} and # {period_for_local} methods. Can be set to `nil`, `true` or `false`. # # @param value [Boolean] `nil`, `true` or `false`. def default_dst=(value) @@default_dst = value.nil? ? nil : !!value end # Returns the default value of the optional `dst` parameter of the # {local_time}, {local_datetime} and {local_timestamp}, {local_to_utc} # and {period_for_local} methods (`nil`, `true` or `false`). # # {default_dst} defaults to `nil` unless changed with {default_dst=}. # # @return [Boolean] the default value of the optional `dst` parameter of # the {local_time}, {local_datetime} and {local_timestamp}, # {local_to_utc} and {period_for_local} methods (`nil`, `true` or # `false`). def default_dst @@default_dst end # Returns a time zone by its IANA Time Zone Database identifier (e.g. # `"Europe/London"` or `"America/Chicago"`). Call {all_identifiers} for a # list of all the valid identifiers. # # The {get} method will return a subclass of {Timezone}, either a # {DataTimezone} (for a time zone defined by rules that set out when # transitions occur) or a {LinkedTimezone} (for a time zone that is just a # link to or alias for a another time zone). # # @param identifier [String] an IANA Time Zone Database time zone # identifier. # @return [Timezone] the {Timezone} with the given `identifier`. # @raise [InvalidTimezoneIdentifier] if the `identifier` is not valid. def get(identifier) data_source.get_timezone_info(identifier).create_timezone end # Returns a proxy for the time zone with the given identifier. This allows # loading of the time zone data to be deferred until it is first needed. # # The identifier will not be validated. If an invalid identifier is # specified, no exception will be raised until the proxy is used. # # @param identifier [String] an IANA Time Zone Database time zone # identifier. # @return [TimezoneProxy] a proxy for the time zone with the given # `identifier`. def get_proxy(identifier) TimezoneProxy.new(identifier) end # Returns an `Array` of all the available time zones. # # {TimezoneProxy} instances are returned to avoid the overhead of loading # time zone data until it is first needed. # # @return [Array] all available time zones. def all get_proxies(all_identifiers) end # @return [Array] an `Array` containing the identifiers of all the # available time zones. def all_identifiers data_source.timezone_identifiers end # Returns an `Array` of all the available time zones that are # defined by offsets and transitions. # # {TimezoneProxy} instances are returned to avoid the overhead of loading # time zone data until it is first needed. # # @return [Array] an `Array` of all the available time zones # that are defined by offsets and transitions. def all_data_zones get_proxies(all_data_zone_identifiers) end # @return [Array] an `Array` of the identifiers of all available # time zones that are defined by offsets and transitions. def all_data_zone_identifiers data_source.data_timezone_identifiers end # Returns an `Array` of all the available time zones that are # defined as links to / aliases for other time zones. # # {TimezoneProxy} instances are returned to avoid the overhead of loading # time zone data until it is first needed. # # @return [Array] an `Array` of all the available time zones # that are defined as links to / aliases for other time zones. def all_linked_zones get_proxies(all_linked_zone_identifiers) end # @return [Array] an `Array` of the identifiers of all available # time zones that are defined as links to / aliases for other time zones. def all_linked_zone_identifiers data_source.linked_timezone_identifiers end # Returns an `Array` of all the time zones that are observed by at least # one {Country}. This is not the complete set of time zones as some are # not country specific (e.g. `'Etc/GMT'`). # # {TimezoneProxy} instances are returned to avoid the overhead of loading # time zone data until it is first needed. # # @return [Array] an `Array` of all the time zones that are # observed by at least one {Country}. def all_country_zones Country.all.map(&:zones).flatten.uniq end # Returns an `Array` of the identifiers of all the time zones that are # observed by at least one {Country}. This is not the complete set of time # zone identifiers as some are not country specific (e.g. `'Etc/GMT'`). # # {TimezoneProxy} instances are returned to avoid the overhead of loading # time zone data until it is first needed. # # @return [Array] an `Array` of the identifiers of all the time # zones that are observed by at least one {Country}. def all_country_zone_identifiers Country.all.map(&:zone_identifiers).flatten.uniq end private # @param [Enumerable] identifiers an `Enumerable` of time zone # identifiers. # @return [Array] an `Array` of {TimezoneProxy} # instances corresponding to the given identifiers. def get_proxies(identifiers) identifiers.collect {|identifier| get_proxy(identifier)} end # @return [DataSource] the current DataSource. def data_source DataSource.get end end # @return [String] the identifier of the time zone, for example, # `"Europe/Paris"`. def identifier raise_unknown_timezone end # @return [String] the identifier of the time zone, for example, # `"Europe/Paris"`. def name # Don't use alias, as identifier gets overridden. identifier end # @return [String] {identifier}, modified to make it more readable. def to_s friendly_identifier end # @return [String] the internal object state as a programmer-readable # `String`. def inspect "#<#{self.class}: #{identifier}>" end # Returns {identifier}, modified to make it more readable. Set # `skip_first_part` to omit the first part of the identifier (typically a # region name) where there is more than one part. # # For example: # # TZInfo::Timezone.get('Europe/Paris').friendly_identifier(false) #=> "Europe - Paris" # TZInfo::Timezone.get('Europe/Paris').friendly_identifier(true) #=> "Paris" # TZInfo::Timezone.get('America/Indiana/Knox').friendly_identifier(false) #=> "America - Knox, Indiana" # TZInfo::Timezone.get('America/Indiana/Knox').friendly_identifier(true) #=> "Knox, Indiana" # # @param skip_first_part [Boolean] whether the first part of the identifier # (typically a region name) should be omitted. # @return [String] the modified identifier. def friendly_identifier(skip_first_part = false) id = identifier id = id.encode(Encoding::UTF_8) unless id.encoding.ascii_compatible? parts = id.split('/') if parts.empty? # shouldn't happen identifier elsif parts.length == 1 parts[0] else prefix = skip_first_part ? nil : "#{parts[0]} - " parts = parts.drop(1).map do |part| part.gsub!(/_/, ' ') if part.index(/[a-z]/) # Missing a space if a lower case followed by an upper case and the # name isn't McXxxx. part.gsub!(/([^M][a-z])([A-Z])/, '\1 \2') part.gsub!(/([M][a-bd-z])([A-Z])/, '\1 \2') # Missing an apostrophe if two consecutive upper case characters. part.gsub!(/([A-Z])([A-Z])/, '\1\'\2') end part end "#{prefix}#{parts.reverse.join(', ')}" end end # Returns the {TimezonePeriod} that is valid at a given time. # # Unlike {period_for_local} and {period_for_utc}, the UTC offset of the # `time` parameter is taken into consideration. # # @param time [Object] a `Time`, `DateTime` or {Timestamp}. # @return [TimezonePeriod] the {TimezonePeriod} that is valid at `time`. # @raise [ArgumentError] if `time` is `nil`. # @raise [ArgumentError] if `time` is a {Timestamp} with an unspecified # offset. def period_for(time) raise_unknown_timezone end # Returns the set of {TimezonePeriod}s that are valid for the given # local time as an `Array`. # # The UTC offset of the `local_time` parameter is ignored (it is treated as # a time in the time zone represented by `self`). # # This will typically return an `Array` containing a single # {TimezonePeriod}. More than one {TimezonePeriod} will be returned when the # local time is ambiguous (for example, when daylight savings time ends). An # empty `Array` will be returned when the local time is not valid (for # example, when daylight savings time begins). # # To obtain just a single {TimezonePeriod} in all cases, use # {period_for_local} instead and specify how ambiguities should be resolved. # # @param local_time [Object] a `Time`, `DateTime` or {Timestamp}. # @return [Array] the set of {TimezonePeriod}s that are # valid at `local_time`. # @raise [ArgumentError] if `local_time` is `nil`. def periods_for_local(local_time) raise_unknown_timezone end # Returns an `Array` of {TimezoneTransition} instances representing the # times where the UTC offset of the timezone changes. # # Transitions are returned up to a given time (`to`). # # A from time may also be supplied using the `from` parameter. If from is # not `nil`, only transitions from that time onwards will be returned. # # Comparisons with `to` are exclusive. Comparisons with `from` are # inclusive. If a transition falls precisely on `to`, it will be excluded. # If a transition falls on `from`, it will be included. # # @param to [Object] a `Time`, `DateTime` or {Timestamp} specifying the # latest (exclusive) transition to return. # @param from [Object] an optional `Time`, `DateTime` or {Timestamp} # specifying the earliest (inclusive) transition to return. # @return [Array] the transitions that are earlier than # `to` and, if specified, at or later than `from`. Transitions are ordered # by when they occur, from earliest to latest. # @raise [ArgumentError] if `from` is specified and `to` is not greater than # `from`. # @raise [ArgumentError] is raised if `to` is `nil`. # @raise [ArgumentError] if either `to` or `from` is a {Timestamp} with an # unspecified offset. def transitions_up_to(to, from = nil) raise_unknown_timezone end # Returns the canonical {Timezone} instance for this {Timezone}. # # The IANA Time Zone database contains two types of definition: Zones and # Links. Zones are defined by rules that set out when transitions occur. # Links are just references to fully defined Zone, creating an alias for # that Zone. # # Links are commonly used where a time zone has been renamed in a release of # the Time Zone database. For example, the US/Eastern Zone was renamed as # America/New_York. A US/Eastern Link was added in its place, linking to # (and creating an alias for) America/New_York. # # Links are also used for time zones that are currently identical to a full # Zone, but that are administered separately. For example, Europe/Vatican is # a Link to (and alias for) Europe/Rome. # # For a full Zone (implemented by {DataTimezone}), {canonical_zone} returns # self. # # For a Link (implemented by {LinkedTimezone}), {canonical_zone} returns a # {Timezone} instance representing the full Zone that the link targets. # # TZInfo can be used with different data sources (see the documentation for # {TZInfo::DataSource}). Some DataSource implementations may not support # distinguishing between full Zones and Links and will treat all time zones # as full Zones. In this case, {canonical_zone} will always return `self`. # # There are two built-in DataSource implementations. # {DataSources::RubyDataSource} (which will be used if the tzinfo-data gem # is available) supports Link zones. {DataSources::ZoneinfoDataSource} # returns Link zones as if they were full Zones. If the {canonical_zone} or # {canonical_identifier} methods are needed, the tzinfo-data gem should be # installed. # # The {TZInfo::DataSource.get} method can be used to check which DataSource # implementation is being used. # # @return [Timezone] the canonical {Timezone} instance for this {Timezone}. def canonical_zone raise_unknown_timezone end # Returns the {TimezonePeriod} that is valid at a given time. # # The UTC offset of the `utc_time` parameter is ignored (it is treated as a # UTC time). Use the {period_for} method instead if the UTC offset of the # time needs to be taken into consideration. # # @param utc_time [Object] a `Time`, `DateTime` or {Timestamp}. # @return [TimezonePeriod] the {TimezonePeriod} that is valid at `utc_time`. # @raise [ArgumentError] if `utc_time` is `nil`. def period_for_utc(utc_time) raise ArgumentError, 'utc_time must be specified' unless utc_time period_for(Timestamp.for(utc_time, :treat_as_utc)) end # Returns the {TimezonePeriod} that is valid at the given local time. # # The UTC offset of the `local_time` parameter is ignored (it is treated as # a time in the time zone represented by `self`). Use the {period_for} # method instead if the the UTC offset of the time needs to be taken into # consideration. # # _Warning:_ There are local times that have no equivalent UTC times (for # example, during the transition from standard time to daylight savings # time). There are also local times that have more than one UTC equivalent # (for example, during the transition from daylight savings time to standard # time). # # In the first case (no equivalent UTC time), a {PeriodNotFound} exception # will be raised. # # In the second case (more than one equivalent UTC time), an {AmbiguousTime} # exception will be raised unless the optional `dst` parameter or block # handles the ambiguity. # # If the ambiguity is due to a transition from daylight savings time to # standard time, the `dst` parameter can be used to select whether the # daylight savings time or local time is used. For example, the following # code would raise an {AmbiguousTime} exception: # # tz = TZInfo::Timezone.get('America/New_York') # tz.period_for_local(Time.new(2004,10,31,1,30,0)) # # Specifying `dst = true` would select the daylight savings period from # April to October 2004. Specifying `dst = false` would return the # standard time period from October 2004 to April 2005. # # The `dst` parameter will not be able to resolve an ambiguity resulting # from the clocks being set back without changing from daylight savings time # to standard time. In this case, if a block is specified, it will be called # to resolve the ambiguity. The block must take a single parameter - an # `Array` of {TimezonePeriod}s that need to be resolved. The block can # select and return a single {TimezonePeriod} or return `nil` or an empty # `Array` to cause an {AmbiguousTime} exception to be raised. # # The default value of the `dst` parameter can be specified using # {Timezone.default_dst=}. # # @param local_time [Object] a `Time`, `DateTime` or {Timestamp}. # @param dst [Boolean] whether to resolve ambiguous local times by always # selecting the period observing daylight savings time (`true`), always # selecting the period observing standard time (`false`), or leaving the # ambiguity unresolved (`nil`). # @yield [periods] if the `dst` parameter did not resolve an ambiguity, an # optional block is yielded to. # @yieldparam periods [Array] an `Array` containing all # the {TimezonePeriod}s that still match `local_time` after applying the # `dst` parameter. # @yieldreturn [Object] to resolve the ambiguity: a chosen {TimezonePeriod} # or an `Array` containing a chosen {TimezonePeriod}; to leave the # ambiguity unresolved: an empty `Array`, an `Array` containing more than # one {TimezonePeriod}, or `nil`. # @return [TimezonePeriod] the {TimezonePeriod} that is valid at # `local_time`. # @raise [ArgumentError] if `local_time` is `nil`. # @raise [PeriodNotFound] if `local_time` is not valid for the time zone # (there is no equivalent UTC time). # @raise [AmbiguousTime] if `local_time` was ambiguous for the time zone and # the `dst` parameter or block did not resolve the ambiguity. def period_for_local(local_time, dst = Timezone.default_dst) raise ArgumentError, 'local_time must be specified' unless local_time local_time = Timestamp.for(local_time, :ignore) results = periods_for_local(local_time) if results.empty? raise PeriodNotFound, "#{local_time.strftime('%Y-%m-%d %H:%M:%S')} is an invalid local time." elsif results.size < 2 results.first else # ambiguous result try to resolve if !dst.nil? matches = results.find_all {|period| period.dst? == dst} results = matches if !matches.empty? end if results.size < 2 results.first else # still ambiguous, try the block if block_given? results = yield results end if results.is_a?(TimezonePeriod) results elsif results && results.size == 1 results.first else raise AmbiguousTime, "#{local_time.strftime('%Y-%m-%d %H:%M:%S')} is an ambiguous local time." end end end end # Converts a time to the local time for the time zone. # # The result will be of type {TimeWithOffset} (if passed a `Time`), # {DateTimeWithOffset} (if passed a `DateTime`) or {TimestampWithOffset} (if # passed a {Timestamp}). {TimeWithOffset}, {DateTimeWithOffset} and # {TimestampWithOffset} are subclasses of `Time`, `DateTime` and {Timestamp} # that provide additional information about the local result. # # Unlike {utc_to_local}, {to_local} takes the UTC offset of the given time # into consideration. # # @param time [Object] a `Time`, `DateTime` or {Timestamp}. # @return [Object] the local equivalent of `time` as a {TimeWithOffset}, # {DateTimeWithOffset} or {TimestampWithOffset}. # @raise [ArgumentError] if `time` is `nil`. # @raise [ArgumentError] if `time` is a {Timestamp} that does not have a # specified UTC offset. def to_local(time) raise ArgumentError, 'time must be specified' unless time Timestamp.for(time) do |ts| TimestampWithOffset.set_timezone_offset(ts, period_for(ts).offset) end end # Converts a time in UTC to the local time for the time zone. # # The result will be of type {TimeWithOffset} (if passed a `Time`), # {DateTimeWithOffset} (if passed a `DateTime`) or {TimestampWithOffset} (if # passed a {Timestamp}). {TimeWithOffset}, {DateTimeWithOffset} and # {TimestampWithOffset} are subclasses of `Time`, `DateTime` and {Timestamp} # that provide additional information about the local result. # # The UTC offset of the `utc_time` parameter is ignored (it is treated as a # UTC time). Use the {to_local} method instead if the the UTC offset of the # time needs to be taken into consideration. # # @param utc_time [Object] a `Time`, `DateTime` or {Timestamp}. # @return [Object] the local equivalent of `utc_time` as a {TimeWithOffset}, # {DateTimeWithOffset} or {TimestampWithOffset}. # @raise [ArgumentError] if `utc_time` is `nil`. def utc_to_local(utc_time) raise ArgumentError, 'utc_time must be specified' unless utc_time Timestamp.for(utc_time, :treat_as_utc) do |ts| to_local(ts) end end # Converts a local time for the time zone to UTC. # # The result will either be a `Time`, `DateTime` or {Timestamp} according to # the type of the `local_time` parameter. # # The UTC offset of the `local_time` parameter is ignored (it is treated as # a time in the time zone represented by `self`). # # _Warning:_ There are local times that have no equivalent UTC times (for # example, during the transition from standard time to daylight savings # time). There are also local times that have more than one UTC equivalent # (for example, during the transition from daylight savings time to standard # time). # # In the first case (no equivalent UTC time), a {PeriodNotFound} exception # will be raised. # # In the second case (more than one equivalent UTC time), an {AmbiguousTime} # exception will be raised unless the optional `dst` parameter or block # handles the ambiguity. # # If the ambiguity is due to a transition from daylight savings time to # standard time, the `dst` parameter can be used to select whether the # daylight savings time or local time is used. For example, the following # code would raise an {AmbiguousTime} exception: # # tz = TZInfo::Timezone.get('America/New_York') # tz.period_for_local(Time.new(2004,10,31,1,30,0)) # # Specifying `dst = true` would select the daylight savings period from # April to October 2004. Specifying `dst = false` would return the # standard time period from October 2004 to April 2005. # # The `dst` parameter will not be able to resolve an ambiguity resulting # from the clocks being set back without changing from daylight savings time # to standard time. In this case, if a block is specified, it will be called # to resolve the ambiguity. The block must take a single parameter - an # `Array` of {TimezonePeriod}s that need to be resolved. The block can # select and return a single {TimezonePeriod} or return `nil` or an empty # `Array` to cause an {AmbiguousTime} exception to be raised. # # The default value of the `dst` parameter can be specified using # {Timezone.default_dst=}. # # @param local_time [Object] a `Time`, `DateTime` or {Timestamp}. # @param dst [Boolean] whether to resolve ambiguous local times by always # selecting the period observing daylight savings time (`true`), always # selecting the period observing standard time (`false`), or leaving the # ambiguity unresolved (`nil`). # @yield [periods] if the `dst` parameter did not resolve an ambiguity, an # optional block is yielded to. # @yieldparam periods [Array] an `Array` containing all # the {TimezonePeriod}s that still match `local_time` after applying the # `dst` parameter. # @yieldreturn [Object] to resolve the ambiguity: a chosen {TimezonePeriod} # or an `Array` containing a chosen {TimezonePeriod}; to leave the # ambiguity unresolved: an empty `Array`, an `Array` containing more than # one {TimezonePeriod}, or `nil`. # @return [Object] the UTC equivalent of `local_time` as a `Time`, # `DateTime` or {Timestamp}. # @raise [ArgumentError] if `local_time` is `nil`. # @raise [PeriodNotFound] if `local_time` is not valid for the time zone # (there is no equivalent UTC time). # @raise [AmbiguousTime] if `local_time` was ambiguous for the time zone and # the `dst` parameter or block did not resolve the ambiguity. def local_to_utc(local_time, dst = Timezone.default_dst) raise ArgumentError, 'local_time must be specified' unless local_time Timestamp.for(local_time, :ignore) do |ts| period = if block_given? period_for_local(ts, dst) {|periods| yield periods } else period_for_local(ts, dst) end ts.add_and_set_utc_offset(-period.observed_utc_offset, :utc) end end # Creates a `Time` object based on the given (Gregorian calendar) date and # time parameters. The parameters are interpreted as a local time in the # time zone. The result has the appropriate `utc_offset`, `zone` and # {TimeWithOffset#timezone_offset timezone_offset}. # # _Warning:_ There are time values that are not valid as local times in a # time zone (for example, during the transition from standard time to # daylight savings time). There are also time values that are ambiguous, # occurring more than once with different offsets to UTC (for example, # during the transition from daylight savings time to standard time). # # In the first case (an invalid local time), a {PeriodNotFound} exception # will be raised. # # In the second case (more than one occurrence), an {AmbiguousTime} # exception will be raised unless the optional `dst` parameter or block # handles the ambiguity. # # If the ambiguity is due to a transition from daylight savings time to # standard time, the `dst` parameter can be used to select whether the # daylight savings time or local time is used. For example, the following # code would raise an {AmbiguousTime} exception: # # tz = TZInfo::Timezone.get('America/New_York') # tz.local_time(2004,10,31,1,30,0,0) # # Specifying `dst = true` would return a `Time` with a UTC offset of -4 # hours and abbreviation EDT (Eastern Daylight Time). Specifying `dst = # false` would return a `Time` with a UTC offset of -5 hours and # abbreviation EST (Eastern Standard Time). # # The `dst` parameter will not be able to resolve an ambiguity resulting # from the clocks being set back without changing from daylight savings time # to standard time. In this case, if a block is specified, it will be called # to resolve the ambiguity. The block must take a single parameter - an # `Array` of {TimezonePeriod}s that need to be resolved. The block can # select and return a single {TimezonePeriod} or return `nil` or an empty # `Array` to cause an {AmbiguousTime} exception to be raised. # # The default value of the `dst` parameter can be specified using # {Timezone.default_dst=}. # # @param year [Integer] the year. # @param month [Integer] the month (1-12). # @param day [Integer] the day of the month (1-31). # @param hour [Integer] the hour (0-23). # @param minute [Integer] the minute (0-59). # @param second [Integer] the second (0-59). # @param sub_second [Numeric] the fractional part of the second as either # a `Rational` that is greater than or equal to 0 and less than 1, or # the `Integer` 0. # @param dst [Boolean] whether to resolve ambiguous local times by always # selecting the period observing daylight savings time (`true`), always # selecting the period observing standard time (`false`), or leaving the # ambiguity unresolved (`nil`). # @yield [periods] if the `dst` parameter did not resolve an ambiguity, an # optional block is yielded to. # @yieldparam periods [Array] an `Array` containing all # the {TimezonePeriod}s that still match `local_time` after applying the # `dst` parameter. # @yieldreturn [Object] to resolve the ambiguity: a chosen {TimezonePeriod} # or an `Array` containing a chosen {TimezonePeriod}; to leave the # ambiguity unresolved: an empty `Array`, an `Array` containing more than # one {TimezonePeriod}, or `nil`. # @return [TimeWithOffset] a new `Time` object based on the given values, # interpreted as a local time in the time zone. # @raise [ArgumentError] if either of `year`, `month`, `day`, `hour`, # `minute`, or `second` is not an `Integer`. # @raise [ArgumentError] if `sub_second` is not a `Rational`, or the # `Integer` 0. # @raise [ArgumentError] if `utc_offset` is not `nil`, not an `Integer` # and not the `Symbol` `:utc`. # @raise [RangeError] if `month` is not between 1 and 12. # @raise [RangeError] if `day` is not between 1 and 31. # @raise [RangeError] if `hour` is not between 0 and 23. # @raise [RangeError] if `minute` is not between 0 and 59. # @raise [RangeError] if `second` is not between 0 and 59. # @raise [RangeError] if `sub_second` is a `Rational` but that is less # than 0 or greater than or equal to 1. # @raise [PeriodNotFound] if the date and time parameters do not specify a # valid local time in the time zone. # @raise [AmbiguousTime] if the date and time parameters are ambiguous for # the time zone and the `dst` parameter or block did not resolve the # ambiguity. def local_time(year, month = 1, day = 1, hour = 0, minute = 0, second = 0, sub_second = 0, dst = Timezone.default_dst, &block) local_timestamp(year, month, day, hour, minute, second, sub_second, dst, &block).to_time end # Creates a `DateTime` object based on the given (Gregorian calendar) date # and time parameters. The parameters are interpreted as a local time in the # time zone. The result has the appropriate `offset` and # {DateTimeWithOffset#timezone_offset timezone_offset}. # # _Warning:_ There are time values that are not valid as local times in a # time zone (for example, during the transition from standard time to # daylight savings time). There are also time values that are ambiguous, # occurring more than once with different offsets to UTC (for example, # during the transition from daylight savings time to standard time). # # In the first case (an invalid local time), a {PeriodNotFound} exception # will be raised. # # In the second case (more than one occurrence), an {AmbiguousTime} # exception will be raised unless the optional `dst` parameter or block # handles the ambiguity. # # If the ambiguity is due to a transition from daylight savings time to # standard time, the `dst` parameter can be used to select whether the # daylight savings time or local time is used. For example, the following # code would raise an {AmbiguousTime} exception: # # tz = TZInfo::Timezone.get('America/New_York') # tz.local_datetime(2004,10,31,1,30,0,0) # # Specifying `dst = true` would return a `Time` with a UTC offset of -4 # hours and abbreviation EDT (Eastern Daylight Time). Specifying `dst = # false` would return a `Time` with a UTC offset of -5 hours and # abbreviation EST (Eastern Standard Time). # # The `dst` parameter will not be able to resolve an ambiguity resulting # from the clocks being set back without changing from daylight savings time # to standard time. In this case, if a block is specified, it will be called # to resolve the ambiguity. The block must take a single parameter - an # `Array` of {TimezonePeriod}s that need to be resolved. The block can # select and return a single {TimezonePeriod} or return `nil` or an empty # `Array` to cause an {AmbiguousTime} exception to be raised. # # The default value of the `dst` parameter can be specified using # {Timezone.default_dst=}. # # @param year [Integer] the year. # @param month [Integer] the month (1-12). # @param day [Integer] the day of the month (1-31). # @param hour [Integer] the hour (0-23). # @param minute [Integer] the minute (0-59). # @param second [Integer] the second (0-59). # @param sub_second [Numeric] the fractional part of the second as either # a `Rational` that is greater than or equal to 0 and less than 1, or # the `Integer` 0. # @param dst [Boolean] whether to resolve ambiguous local times by always # selecting the period observing daylight savings time (`true`), always # selecting the period observing standard time (`false`), or leaving the # ambiguity unresolved (`nil`). # @yield [periods] if the `dst` parameter did not resolve an ambiguity, an # optional block is yielded to. # @yieldparam periods [Array] an `Array` containing all # the {TimezonePeriod}s that still match `local_time` after applying the # `dst` parameter. # @yieldreturn [Object] to resolve the ambiguity: a chosen {TimezonePeriod} # or an `Array` containing a chosen {TimezonePeriod}; to leave the # ambiguity unresolved: an empty `Array`, an `Array` containing more than # one {TimezonePeriod}, or `nil`. # @return [DateTimeWithOffset] a new `DateTime` object based on the given # values, interpreted as a local time in the time zone. # @raise [ArgumentError] if either of `year`, `month`, `day`, `hour`, # `minute`, or `second` is not an `Integer`. # @raise [ArgumentError] if `sub_second` is not a `Rational`, or the # `Integer` 0. # @raise [ArgumentError] if `utc_offset` is not `nil`, not an `Integer` # and not the `Symbol` `:utc`. # @raise [RangeError] if `month` is not between 1 and 12. # @raise [RangeError] if `day` is not between 1 and 31. # @raise [RangeError] if `hour` is not between 0 and 23. # @raise [RangeError] if `minute` is not between 0 and 59. # @raise [RangeError] if `second` is not between 0 and 59. # @raise [RangeError] if `sub_second` is a `Rational` but that is less # than 0 or greater than or equal to 1. # @raise [PeriodNotFound] if the date and time parameters do not specify a # valid local time in the time zone. # @raise [AmbiguousTime] if the date and time parameters are ambiguous for # the time zone and the `dst` parameter or block did not resolve the # ambiguity. def local_datetime(year, month = 1, day = 1, hour = 0, minute = 0, second = 0, sub_second = 0, dst = Timezone.default_dst, &block) local_timestamp(year, month, day, hour, minute, second, sub_second, dst, &block).to_datetime end # Creates a {Timestamp} object based on the given (Gregorian calendar) date # and time parameters. The parameters are interpreted as a local time in the # time zone. The result has the appropriate {Timestamp#utc_offset # utc_offset} and {TimestampWithOffset#timezone_offset timezone_offset}. # # _Warning:_ There are time values that are not valid as local times in a # time zone (for example, during the transition from standard time to # daylight savings time). There are also time values that are ambiguous, # occurring more than once with different offsets to UTC (for example, # during the transition from daylight savings time to standard time). # # In the first case (an invalid local time), a {PeriodNotFound} exception # will be raised. # # In the second case (more than one occurrence), an {AmbiguousTime} # exception will be raised unless the optional `dst` parameter or block # handles the ambiguity. # # If the ambiguity is due to a transition from daylight savings time to # standard time, the `dst` parameter can be used to select whether the # daylight savings time or local time is used. For example, the following # code would raise an {AmbiguousTime} exception: # # tz = TZInfo::Timezone.get('America/New_York') # tz.local_timestamp(2004,10,31,1,30,0,0) # # Specifying `dst = true` would return a `Time` with a UTC offset of -4 # hours and abbreviation EDT (Eastern Daylight Time). Specifying `dst = # false` would return a `Time` with a UTC offset of -5 hours and # abbreviation EST (Eastern Standard Time). # # The `dst` parameter will not be able to resolve an ambiguity resulting # from the clocks being set back without changing from daylight savings time # to standard time. In this case, if a block is specified, it will be called # to resolve the ambiguity. The block must take a single parameter - an # `Array` of {TimezonePeriod}s that need to be resolved. The block can # select and return a single {TimezonePeriod} or return `nil` or an empty # `Array` to cause an {AmbiguousTime} exception to be raised. # # The default value of the `dst` parameter can be specified using # {Timezone.default_dst=}. # # @param year [Integer] the year. # @param month [Integer] the month (1-12). # @param day [Integer] the day of the month (1-31). # @param hour [Integer] the hour (0-23). # @param minute [Integer] the minute (0-59). # @param second [Integer] the second (0-59). # @param sub_second [Numeric] the fractional part of the second as either # a `Rational` that is greater than or equal to 0 and less than 1, or # the `Integer` 0. # @param dst [Boolean] whether to resolve ambiguous local times by always # selecting the period observing daylight savings time (`true`), always # selecting the period observing standard time (`false`), or leaving the # ambiguity unresolved (`nil`). # @yield [periods] if the `dst` parameter did not resolve an ambiguity, an # optional block is yielded to. # @yieldparam periods [Array] an `Array` containing all # the {TimezonePeriod}s that still match `local_time` after applying the # `dst` parameter. # @yieldreturn [Object] to resolve the ambiguity: a chosen {TimezonePeriod} # or an `Array` containing a chosen {TimezonePeriod}; to leave the # ambiguity unresolved: an empty `Array`, an `Array` containing more than # one {TimezonePeriod}, or `nil`. # @return [TimestampWithOffset] a new {Timestamp} object based on the given # values, interpreted as a local time in the time zone. # @raise [ArgumentError] if either of `year`, `month`, `day`, `hour`, # `minute`, or `second` is not an `Integer`. # @raise [ArgumentError] if `sub_second` is not a `Rational`, or the # `Integer` 0. # @raise [ArgumentError] if `utc_offset` is not `nil`, not an `Integer` # and not the `Symbol` `:utc`. # @raise [RangeError] if `month` is not between 1 and 12. # @raise [RangeError] if `day` is not between 1 and 31. # @raise [RangeError] if `hour` is not between 0 and 23. # @raise [RangeError] if `minute` is not between 0 and 59. # @raise [RangeError] if `second` is not between 0 and 59. # @raise [RangeError] if `sub_second` is a `Rational` but that is less # than 0 or greater than or equal to 1. # @raise [PeriodNotFound] if the date and time parameters do not specify a # valid local time in the time zone. # @raise [AmbiguousTime] if the date and time parameters are ambiguous for # the time zone and the `dst` parameter or block did not resolve the # ambiguity. def local_timestamp(year, month = 1, day = 1, hour = 0, minute = 0, second = 0, sub_second = 0, dst = Timezone.default_dst, &block) ts = Timestamp.create(year, month, day, hour, minute, second, sub_second) timezone_offset = period_for_local(ts, dst, &block).offset utc_offset = timezone_offset.observed_utc_offset TimestampWithOffset.new(ts.value - utc_offset, sub_second, utc_offset).set_timezone_offset(timezone_offset) end # Returns the unique offsets used by the time zone up to a given time (`to`) # as an `Array` of {TimezoneOffset} instances. # # A from time may also be supplied using the `from` parameter. If from is # not `nil`, only offsets used from that time onwards will be returned. # # Comparisons with `to` are exclusive. Comparisons with `from` are # inclusive. # # @param to [Object] a `Time`, `DateTime` or {Timestamp} specifying the # latest (exclusive) offset to return. # @param from [Object] an optional `Time`, `DateTime` or {Timestamp} # specifying the earliest (inclusive) offset to return. # @return [Array] the offsets that are used earlier than # `to` and, if specified, at or later than `from`. Offsets may be returned # in any order. # @raise [ArgumentError] if `from` is specified and `to` is not greater than # `from`. # @raise [ArgumentError] is raised if `to` is `nil`. # @raise [ArgumentError] if either `to` or `from` is a {Timestamp} with an # unspecified offset. def offsets_up_to(to, from = nil) raise ArgumentError, 'to must be specified' unless to to_timestamp = Timestamp.for(to) from_timestamp = from && Timestamp.for(from) transitions = transitions_up_to(to_timestamp, from_timestamp) if transitions.empty? # No transitions in the range, find the period that covers it. if from_timestamp # Use the from date as it is inclusive. period = period_for(from_timestamp) else # to is exclusive, so this can't be used with period_for. However, any # time earlier than to can be used. Subtract 1 hour. period = period_for(to_timestamp.add_and_set_utc_offset(-3600, :utc)) end [period.offset] else result = Set.new first = transitions.first result << first.previous_offset unless from_timestamp && first.at == from_timestamp transitions.each do |t| result << t.offset end result.to_a end end # Returns the canonical identifier of this time zone. # # This is a shortcut for calling `canonical_zone.identifier`. Please refer # to the {canonical_zone} documentation for further information. # # @return [String] the canonical identifier of this time zone. def canonical_identifier canonical_zone.identifier end # @return [TimeWithOffset] the current local time in the time zone. def now to_local(Time.now) end # @return [TimezonePeriod] the current {TimezonePeriod} for the time zone. def current_period period_for(Time.now) end # Returns the current local time and {TimezonePeriod} for the time zone as # an `Array`. The first element is the time as a {TimeWithOffset}. The # second element is the period. # # @return [Array] an `Array` containing the current {TimeWithOffset} for the # time zone as the first element and the current {TimezonePeriod} for the # time zone as the second element. def current_time_and_period period = nil local_time = Timestamp.for(Time.now) do |ts| period = period_for(ts) TimestampWithOffset.set_timezone_offset(ts, period.offset) end [local_time, period] end alias current_period_and_time current_time_and_period # Converts a time to local time for the time zone and returns a `String` # representation of the local time according to the given format. # # `Timezone#strftime` first expands any occurrences of `%Z` in the format # string to the time zone abbreviation for the local time (for example, EST # or EDT). Depending on the type of `time` parameter, the result of the # expansion is then passed to either `Time#strftime`, `DateTime#strftime` or # `Timestamp#strftime` to handle any other format directives. # # This method is equivalent to the following: # # time_zone.to_local(time).strftime(format) # # @param format [String] the format string. # @param time [Object] a `Time`, `DateTime` or `Timestamp`. # @return [String] the formatted local time. # @raise [ArgumentError] if `format` or `time` is `nil`. # @raise [ArgumentError] if `time` is a {Timestamp} with an unspecified UTC # offset. def strftime(format, time = Time.now) to_local(time).strftime(format) end # @param time [Object] a `Time`, `DateTime` or `Timestamp`. # @return [String] the abbreviation of this {Timezone} at the given time. # @raise [ArgumentError] if `time` is `nil`. # @raise [ArgumentError] if `time` is a {Timestamp} with an unspecified UTC # offset. def abbreviation(time = Time.now) period_for(time).abbreviation end alias abbr abbreviation # @param time [Object] a `Time`, `DateTime` or `Timestamp`. # @return [Boolean] whether daylight savings time is in effect at the given # time. # @raise [ArgumentError] if `time` is `nil`. # @raise [ArgumentError] if `time` is a {Timestamp} with an unspecified UTC # offset. def dst?(time = Time.now) period_for(time).dst? end # Returns the base offset from UTC in seconds at the given time. This does # not include any adjustment made for daylight savings time and will # typically remain constant throughout the year. # # To obtain the observed offset from UTC, including the effect of daylight # savings time, use {observed_utc_offset} instead. # # If you require accurate {base_utc_offset} values, you should install the # tzinfo-data gem and set {DataSources::RubyDataSource} as the {DataSource}. # When using {DataSources::ZoneinfoDataSource}, the value of # {base_utc_offset} has to be derived from changes to the observed UTC # offset and DST status since it is not included in zoneinfo files. # # @param time [Object] a `Time`, `DateTime` or `Timestamp`. # @return [Integer] the base offset from UTC in seconds at the given time. # @raise [ArgumentError] if `time` is `nil`. # @raise [ArgumentError] if `time` is a {Timestamp} with an unspecified UTC # offset. def base_utc_offset(time = Time.now) period_for(time).base_utc_offset end # Returns the observed offset from UTC in seconds at the given time. This # includes adjustments made for daylight savings time. # # @param time [Object] a `Time`, `DateTime` or `Timestamp`. # @return [Integer] the observed offset from UTC in seconds at the given # time. # @raise [ArgumentError] if `time` is `nil`. # @raise [ArgumentError] if `time` is a {Timestamp} with an unspecified UTC # offset. def observed_utc_offset(time = Time.now) period_for(time).observed_utc_offset end alias utc_offset observed_utc_offset # Compares this {Timezone} with another based on the {identifier}. # # @param tz [Object] an `Object` to compare this {Timezone} with. # @return [Integer] -1 if `tz` is less than `self`, 0 if `tz` is equal to # `self` and +1 if `tz` is greater than `self`, or `nil` if `tz` is not an # instance of {Timezone}. def <=>(tz) return nil unless tz.is_a?(Timezone) identifier <=> tz.identifier end # @param tz [Object] an `Object` to compare this {Timezone} with. # @return [Boolean] `true` if `tz` is an instance of {Timezone} and has the # same {identifier} as `self`, otherwise `false`. def eql?(tz) self == tz end # @return [Integer] a hash based on the {identifier}. def hash identifier.hash end # Matches `regexp` against the {identifier} of this {Timezone}. # # @param regexp [Regexp] a `Regexp` to match against the {identifier} of # this {Timezone}. # @return [Integer] the position the match starts, or `nil` if there is no # match. def =~(regexp) regexp =~ identifier end # Returns a serialized representation of this {Timezone}. This method is # called when using `Marshal.dump` with an instance of {Timezone}. # # @param limit [Integer] the maximum depth to dump - ignored. # @return [String] a serialized representation of this {Timezone}. def _dump(limit) identifier end # Loads a {Timezone} from the serialized representation returned by {_dump}. # This is method is called when using `Marshal.load` or `Marshal.restore` # to restore a serialized {Timezone}. # # @param data [String] a serialized representation of a {Timezone}. # @return [Timezone] the result of converting `data` back into a {Timezone}. def self._load(data) Timezone.get(data) end private # Raises an {UnknownTimezone} exception. # # @raise [UnknownTimezone] always. def raise_unknown_timezone raise UnknownTimezone, 'TZInfo::Timezone should not be constructed directly (use TZInfo::Timezone.get instead)' end end end tzinfo-2.0.4/lib/tzinfo/offset_timezone_period.rb0000644000004100000410000000241513773021353022232 0ustar www-datawww-data# encoding: UTF-8 module TZInfo # Represents the infinite period of time in a time zone that constantly # observes the same offset from UTC (has an unbounded start and end). class OffsetTimezonePeriod < TimezonePeriod # Initializes an {OffsetTimezonePeriod}. # # @param offset [TimezoneOffset] the offset that is constantly observed. # @raise [ArgumentError] if `offset` is `nil`. def initialize(offset) super end # @return [TimezoneTransition] the transition that defines the start of this # {TimezonePeriod}, always `nil` for {OffsetTimezonePeriod}. def start_transition nil end # @return [TimezoneTransition] the transition that defines the end of this # {TimezonePeriod}, always `nil` for {OffsetTimezonePeriod}. def end_transition nil end # Determines if this {OffsetTimezonePeriod} is equal to another instance. # # @param p [Object] the instance to test for equality. # @return [Boolean] `true` if `p` is a {OffsetTimezonePeriod} with the same # {offset}, otherwise `false`. def ==(p) p.kind_of?(OffsetTimezonePeriod) && offset == p.offset end alias eql? == # @return [Integer] a hash based on {offset}. def hash offset.hash end end end tzinfo-2.0.4/lib/tzinfo/timestamp.rb0000644000004100000410000005577413773021353017513 0ustar www-datawww-data# encoding: UTF-8 # frozen_string_literal: true module TZInfo # A time represented as an `Integer` number of seconds since 1970-01-01 # 00:00:00 UTC (ignoring leap seconds), the fraction through the second # (sub_second as a `Rational`) and an optional UTC offset. Like Ruby's `Time` # class, {Timestamp} can distinguish between a local time with a zero offset # and a time specified explicitly as UTC. class Timestamp include Comparable # The Unix epoch (1970-01-01 00:00:00 UTC) as a chronological Julian day # number. JD_EPOCH = 2440588 private_constant :JD_EPOCH class << self # Returns a new {Timestamp} representing the (Gregorian calendar) date and # time specified by the supplied parameters. # # If `utc_offset` is `nil`, `:utc` or 0, the date and time parameters will # be interpreted as representing a UTC date and time. Otherwise the date # and time parameters will be interpreted as a local date and time with # the given offset. # # @param year [Integer] the year. # @param month [Integer] the month (1-12). # @param day [Integer] the day of the month (1-31). # @param hour [Integer] the hour (0-23). # @param minute [Integer] the minute (0-59). # @param second [Integer] the second (0-59). # @param sub_second [Numeric] the fractional part of the second as either # a `Rational` that is greater than or equal to 0 and less than 1, or # the `Integer` 0. # @param utc_offset [Object] either `nil` for a {Timestamp} without a # specified offset, an offset from UTC specified as an `Integer` number # of seconds or the `Symbol` `:utc`). # @return [Timestamp] a new {Timestamp} representing the specified # (Gregorian calendar) date and time. # @raise [ArgumentError] if either of `year`, `month`, `day`, `hour`, # `minute`, or `second` is not an `Integer`. # @raise [ArgumentError] if `sub_second` is not a `Rational`, or the # `Integer` 0. # @raise [ArgumentError] if `utc_offset` is not `nil`, not an `Integer` # and not the `Symbol` `:utc`. # @raise [RangeError] if `month` is not between 1 and 12. # @raise [RangeError] if `day` is not between 1 and 31. # @raise [RangeError] if `hour` is not between 0 and 23. # @raise [RangeError] if `minute` is not between 0 and 59. # @raise [RangeError] if `second` is not between 0 and 59. # @raise [RangeError] if `sub_second` is a `Rational` but that is less # than 0 or greater than or equal to 1. def create(year, month = 1, day = 1, hour = 0, minute = 0, second = 0, sub_second = 0, utc_offset = nil) raise ArgumentError, 'year must be an Integer' unless year.kind_of?(Integer) raise ArgumentError, 'month must be an Integer' unless month.kind_of?(Integer) raise ArgumentError, 'day must be an Integer' unless day.kind_of?(Integer) raise ArgumentError, 'hour must be an Integer' unless hour.kind_of?(Integer) raise ArgumentError, 'minute must be an Integer' unless minute.kind_of?(Integer) raise ArgumentError, 'second must be an Integer' unless second.kind_of?(Integer) raise RangeError, 'month must be between 1 and 12' if month < 1 || month > 12 raise RangeError, 'day must be between 1 and 31' if day < 1 || day > 31 raise RangeError, 'hour must be between 0 and 23' if hour < 0 || hour > 23 raise RangeError, 'minute must be between 0 and 59' if minute < 0 || minute > 59 raise RangeError, 'second must be between 0 and 59' if second < 0 || second > 59 # Based on days_from_civil from https://howardhinnant.github.io/date_algorithms.html#days_from_civil after_february = month > 2 year -= 1 unless after_february era = year / 400 year_of_era = year - era * 400 day_of_year = (153 * (month + (after_february ? -3 : 9)) + 2) / 5 + day - 1 day_of_era = year_of_era * 365 + year_of_era / 4 - year_of_era / 100 + day_of_year days_since_epoch = era * 146097 + day_of_era - 719468 value = ((days_since_epoch * 24 + hour) * 60 + minute) * 60 + second value -= utc_offset if utc_offset.kind_of?(Integer) new(value, sub_second, utc_offset) end # When used without a block, returns a {Timestamp} representation of a # given `Time`, `DateTime` or {Timestamp}. # # When called with a block, the {Timestamp} representation of `value` is # passed to the block. The block must then return a {Timestamp}, which # will be converted back to the type of the initial value. If the initial # value was a {Timestamp}, the block result will just be returned. # # The UTC offset of `value` can either be preserved (the {Timestamp} # representation will have the same UTC offset as `value`), ignored (the # {Timestamp} representation will have no defined UTC offset), or treated # as though it were UTC (the {Timestamp} representation will have a # {utc_offset} of 0 and {utc?} will return `true`). # # @param value [Object] a `Time`, `DateTime` or {Timestamp}. # @param offset [Symbol] either `:preserve` to preserve the offset of # `value`, `:ignore` to ignore the offset of `value` and create a # {Timestamp} with an unspecified offset, or `:treat_as_utc` to treat # the offset of `value` as though it were UTC and create a UTC # {Timestamp}. # @yield [timestamp] if a block is provided, the {Timestamp} # representation is passed to the block. # @yieldparam timestamp [Timestamp] the {Timestamp} representation of # `value`. # @yieldreturn [Timestamp] a {Timestamp} to be converted back to the type # of `value`. # @return [Object] if called without a block, the {Timestamp} # representation of `value`, otherwise the result of the block, # converted back to the type of `value`. def for(value, offset = :preserve) raise ArgumentError, 'value must be specified' unless value case offset when :ignore ignore_offset = true target_utc_offset = nil when :treat_as_utc ignore_offset = true target_utc_offset = :utc when :preserve ignore_offset = false target_utc_offset = nil else raise ArgumentError, 'offset must be :preserve, :ignore or :treat_as_utc' end time_like = false timestamp = case value when Time for_time(value, ignore_offset, target_utc_offset) when DateTime for_datetime(value, ignore_offset, target_utc_offset) when Timestamp for_timestamp(value, ignore_offset, target_utc_offset) else raise ArgumentError, "#{value.class} values are not supported" unless is_time_like?(value) time_like = true for_time_like(value, ignore_offset, target_utc_offset) end if block_given? result = yield timestamp raise ArgumentError, 'block must return a Timestamp' unless result.kind_of?(Timestamp) case value when Time result.to_time when DateTime result.to_datetime else # A Time-like value or a Timestamp time_like ? result.to_time : result end else timestamp end end # Creates a new UTC {Timestamp}. # # @param value [Integer] the number of seconds since 1970-01-01 00:00:00 # UTC ignoring leap seconds. # @param sub_second [Numeric] the fractional part of the second as either # a `Rational` that is greater than or equal to 0 and less than 1, or # the `Integer` 0. # @raise [ArgumentError] if `value` is not an `Integer`. # @raise [ArgumentError] if `sub_second` is not a `Rational`, or the # `Integer` 0. # @raise [RangeError] if `sub_second` is a `Rational` but that is less # than 0 or greater than or equal to 1. def utc(value, sub_second = 0) new(value, sub_second, :utc) end private # Constructs a new instance of `self` (i.e. {Timestamp} or a subclass of # {Timestamp}) without validating the parameters. This method is used # internally within {Timestamp} to avoid the overhead of checking # parameters. # # @param value [Integer] the number of seconds since 1970-01-01 00:00:00 # UTC ignoring leap seconds. # @param sub_second [Numeric] the fractional part of the second as either # a `Rational` that is greater than or equal to 0 and less than 1, or # the `Integer` 0. # @param utc_offset [Object] either `nil` for a {Timestamp} without a # specified offset, an offset from UTC specified as an `Integer` number # of seconds or the `Symbol` `:utc`). # @return [Timestamp] a new instance of `self`. def new!(value, sub_second = 0, utc_offset = nil) result = allocate result.send(:initialize!, value, sub_second, utc_offset) result end # Creates a {Timestamp} that represents a given `Time`, optionally # ignoring the offset. # # @param time [Time] a `Time`. # @param ignore_offset [Boolean] whether to ignore the offset of `time`. # @param target_utc_offset [Object] if `ignore_offset` is `true`, the UTC # offset of the result (`:utc`, `nil` or an `Integer`). # @return [Timestamp] the {Timestamp} representation of `time`. def for_time(time, ignore_offset, target_utc_offset) value = time.to_i sub_second = time.subsec if ignore_offset utc_offset = target_utc_offset value += time.utc_offset elsif time.utc? utc_offset = :utc else utc_offset = time.utc_offset end new!(value, sub_second, utc_offset) end # Creates a {Timestamp} that represents a given `DateTime`, optionally # ignoring the offset. # # @param datetime [DateTime] a `DateTime`. # @param ignore_offset [Boolean] whether to ignore the offset of # `datetime`. # @param target_utc_offset [Object] if `ignore_offset` is `true`, the UTC # offset of the result (`:utc`, `nil` or an `Integer`). # @return [Timestamp] the {Timestamp} representation of `datetime`. def for_datetime(datetime, ignore_offset, target_utc_offset) value = (datetime.jd - JD_EPOCH) * 86400 + datetime.sec + datetime.min * 60 + datetime.hour * 3600 sub_second = datetime.sec_fraction if ignore_offset utc_offset = target_utc_offset else utc_offset = (datetime.offset * 86400).to_i value -= utc_offset end new!(value, sub_second, utc_offset) end # Returns a {Timestamp} that represents another {Timestamp}, optionally # ignoring the offset. If the result would be identical to `value`, the # same instance is returned. If the passed in value is an instance of a # subclass of {Timestamp}, then a new {Timestamp} will always be returned. # # @param timestamp [Timestamp] a {Timestamp}. # @param ignore_offset [Boolean] whether to ignore the offset of # `timestamp`. # @param target_utc_offset [Object] if `ignore_offset` is `true`, the UTC # offset of the result (`:utc`, `nil` or an `Integer`). # @return [Timestamp] a [Timestamp] representation of `timestamp`. def for_timestamp(timestamp, ignore_offset, target_utc_offset) if ignore_offset if target_utc_offset unless target_utc_offset == :utc && timestamp.utc? || timestamp.utc_offset == target_utc_offset return new!(timestamp.value + (timestamp.utc_offset || 0), timestamp.sub_second, target_utc_offset) end elsif timestamp.utc_offset return new!(timestamp.value + timestamp.utc_offset, timestamp.sub_second) end end unless timestamp.instance_of?(Timestamp) # timestamp is identical in value, sub_second and utc_offset but is a # subclass (i.e. TimestampWithOffset). Return a new Timestamp # instance. return new!(timestamp.value, timestamp.sub_second, timestamp.utc? ? :utc : timestamp.utc_offset) end timestamp end # Determines if an object is like a `Time` (for the purposes of converting # to a {Timestamp} with {for}), responding to `to_i` and `subsec`. # # @param value [Object] an object to test. # @return [Boolean] `true` if the object is `Time`-like, otherwise # `false`. def is_time_like?(value) value.respond_to?(:to_i) && value.respond_to?(:subsec) end # Creates a {Timestamp} that represents a given `Time`-like object, # optionally ignoring the offset (if the `time_like` responds to # `utc_offset`). # # @param time_like [Object] a `Time`-like object. # @param ignore_offset [Boolean] whether to ignore the offset of `time`. # @param target_utc_offset [Object] if `ignore_offset` is `true`, the UTC # offset of the result (`:utc`, `nil` or an `Integer`). # @return [Timestamp] the {Timestamp} representation of `time_like`. def for_time_like(time_like, ignore_offset, target_utc_offset) value = time_like.to_i sub_second = time_like.subsec.to_r if ignore_offset utc_offset = target_utc_offset value += time_like.utc_offset.to_i if time_like.respond_to?(:utc_offset) elsif time_like.respond_to?(:utc_offset) utc_offset = time_like.utc_offset.to_i else utc_offset = 0 end new(value, sub_second, utc_offset) end end # @return [Integer] the number of seconds since 1970-01-01 00:00:00 UTC # ignoring leap seconds (i.e. each day is treated as if it were 86,400 # seconds long). attr_reader :value # @return [Numeric] the fraction of a second elapsed since timestamp as # either a `Rational` or the `Integer` 0. Always greater than or equal to # 0 and less than 1. attr_reader :sub_second # @return [Integer] the offset from UTC in seconds or `nil` if the # {Timestamp} doesn't have a specified offset. attr_reader :utc_offset # Initializes a new {Timestamp}. # # @param value [Integer] the number of seconds since 1970-01-01 00:00:00 UTC # ignoring leap seconds. # @param sub_second [Numeric] the fractional part of the second as either a # `Rational` that is greater than or equal to 0 and less than 1, or # the `Integer` 0. # @param utc_offset [Object] either `nil` for a {Timestamp} without a # specified offset, an offset from UTC specified as an `Integer` number of # seconds or the `Symbol` `:utc`). # @raise [ArgumentError] if `value` is not an `Integer`. # @raise [ArgumentError] if `sub_second` is not a `Rational`, or the # `Integer` 0. # @raise [RangeError] if `sub_second` is a `Rational` but that is less # than 0 or greater than or equal to 1. # @raise [ArgumentError] if `utc_offset` is not `nil`, not an `Integer` and # not the `Symbol` `:utc`. def initialize(value, sub_second = 0, utc_offset = nil) raise ArgumentError, 'value must be an Integer' unless value.kind_of?(Integer) raise ArgumentError, 'sub_second must be a Rational or the Integer 0' unless (sub_second.kind_of?(Integer) && sub_second == 0) || sub_second.kind_of?(Rational) raise RangeError, 'sub_second must be >= 0 and < 1' if sub_second < 0 || sub_second >= 1 raise ArgumentError, 'utc_offset must be an Integer, :utc or nil' if utc_offset && utc_offset != :utc && !utc_offset.kind_of?(Integer) initialize!(value, sub_second, utc_offset) end # @return [Boolean] `true` if this {Timestamp} represents UTC, `false` if # the {Timestamp} wasn't specified as UTC or `nil` if the {Timestamp} has # no specified offset. def utc? @utc end # Adds a number of seconds to the {Timestamp} value, setting the UTC offset # of the result. # # @param seconds [Integer] the number of seconds to be added. # @param utc_offset [Object] either `nil` for a {Timestamp} without a # specified offset, an offset from UTC specified as an `Integer` number of # seconds or the `Symbol` `:utc`). # @return [Timestamp] the result of adding `seconds` to the # {Timestamp} value as a new {Timestamp} instance with the chosen # `utc_offset`. # @raise [ArgumentError] if `seconds` is not an `Integer`. # @raise [ArgumentError] if `utc_offset` is not `nil`, not an `Integer` and # not the `Symbol` `:utc`. def add_and_set_utc_offset(seconds, utc_offset) raise ArgumentError, 'seconds must be an Integer' unless seconds.kind_of?(Integer) raise ArgumentError, 'utc_offset must be an Integer, :utc or nil' if utc_offset && utc_offset != :utc && !utc_offset.kind_of?(Integer) return self if seconds == 0 && utc_offset == (@utc ? :utc : @utc_offset) Timestamp.send(:new!, @value + seconds, @sub_second, utc_offset) end # @return [Timestamp] a UTC {Timestamp} equivalent to this instance. Returns # `self` if {#utc? self.utc?} is `true`. def utc return self if @utc Timestamp.send(:new!, @value, @sub_second, :utc) end # Converts this {Timestamp} to a `Time`. # # @return [Time] a `Time` representation of this {Timestamp}. If the UTC # offset of this {Timestamp} is not specified, a UTC `Time` will be # returned. def to_time time = new_time if @utc_offset && !@utc time.localtime(@utc_offset) else time.utc end end # Converts this {Timestamp} to a `DateTime`. # # @return [DateTime] a DateTime representation of this {Timestamp}. If the # UTC offset of this {Timestamp} is not specified, a UTC `DateTime` will # be returned. def to_datetime new_datetime end # Converts this {Timestamp} to an `Integer` number of seconds since # 1970-01-01 00:00:00 UTC (ignoring leap seconds). # # @return [Integer] an Integer representation of this {Timestamp} (the # number of seconds since 1970-01-01 00:00:00 UTC ignoring leap seconds). def to_i value end # Formats this {Timestamp} according to the directives in the given format # string. # # @param format [String] the format string. Please refer to `Time#strftime` # for a list of supported format directives. # @return [String] the formatted {Timestamp}. # @raise [ArgumentError] if `format` is not specified. def strftime(format) raise ArgumentError, 'format must be specified' unless format to_time.strftime(format) end # @return [String] a `String` representation of this {Timestamp}. def to_s return value_and_sub_second_to_s unless @utc_offset return "#{value_and_sub_second_to_s} UTC" if @utc sign = @utc_offset >= 0 ? '+' : '-' min, sec = @utc_offset.abs.divmod(60) hour, min = min.divmod(60) "#{value_and_sub_second_to_s(@utc_offset)} #{sign}#{'%02d' % hour}:#{'%02d' % min}#{sec > 0 ? ':%02d' % sec : nil}#{@utc_offset != 0 ? " (#{value_and_sub_second_to_s} UTC)" : nil}" end # Compares this {Timestamp} with another. # # {Timestamp} instances without a defined UTC offset are not comparable with # {Timestamp} instances that have a defined UTC offset. # # @param t [Timestamp] the {Timestamp} to compare this instance with. # @return [Integer] -1, 0 or 1 depending if this instance is earlier, equal # or later than `t` respectively. Returns `nil` when comparing a # {Timestamp} that does not have a defined UTC offset with a {Timestamp} # that does have a defined UTC offset. Returns `nil` if `t` is not a # {Timestamp}. def <=>(t) return nil unless t.kind_of?(Timestamp) return nil if utc_offset && !t.utc_offset return nil if !utc_offset && t.utc_offset result = value <=> t.value result = sub_second <=> t.sub_second if result == 0 result end alias eql? == # @return [Integer] a hash based on the value, sub-second and whether there # is a defined UTC offset. def hash [@value, @sub_second, !!@utc_offset].hash end # @return [String] the internal object state as a programmer-readable # `String`. def inspect "#<#{self.class}: @value=#{@value}, @sub_second=#{@sub_second}, @utc_offset=#{@utc_offset.inspect}, @utc=#{@utc.inspect}>" end protected # Creates a new instance of a `Time` or `Time`-like class matching the # {value} and {sub_second} of this {Timestamp}, but not setting the offset. # # @param klass [Class] the class to instantiate. # # @private def new_time(klass = Time) klass.at(@value, @sub_second * 1_000_000) end # Constructs a new instance of a `DateTime` or `DateTime`-like class with # the same {value}, {sub_second} and {utc_offset} as this {Timestamp}. # # @param klass [Class] the class to instantiate. # # @private def new_datetime(klass = DateTime) datetime = klass.jd(JD_EPOCH + ((@value.to_r + @sub_second) / 86400)) @utc_offset && @utc_offset != 0 ? datetime.new_offset(Rational(@utc_offset, 86400)) : datetime end private # Converts the value and sub-seconds to a `String`, adding on the given # offset. # # @param offset [Integer] the offset to add to the value. # @return [String] the value and sub-seconds. def value_and_sub_second_to_s(offset = 0) "#{@value + offset}#{sub_second_to_s}" end # Converts the {sub_second} value to a `String` suitable for appending to # the `String` representation of a {Timestamp}. # # @return [String] a `String` representation of {sub_second}. def sub_second_to_s if @sub_second == 0 '' else " #{@sub_second.numerator}/#{@sub_second.denominator}" end end # Initializes a new {Timestamp} without validating the parameters. This # method is used internally within {Timestamp} to avoid the overhead of # checking parameters. # # @param value [Integer] the number of seconds since 1970-01-01 00:00:00 UTC # ignoring leap seconds. # @param sub_second [Numeric] the fractional part of the second as either a # `Rational` that is greater than or equal to 0 and less than 1, or the # `Integer` 0. # @param utc_offset [Object] either `nil` for a {Timestamp} without a # specified offset, an offset from UTC specified as an `Integer` number of # seconds or the `Symbol` `:utc`). def initialize!(value, sub_second = 0, utc_offset = nil) @value = value # Convert Rational(0,1) to 0. @sub_second = sub_second == 0 ? 0 : sub_second if utc_offset @utc = utc_offset == :utc @utc_offset = @utc ? 0 : utc_offset else @utc = @utc_offset = nil end end end end tzinfo-2.0.4/lib/tzinfo/transitions_timezone_period.rb0000644000004100000410000000471313773021353023324 0ustar www-datawww-data# encoding: UTF-8 # frozen_string_literal: true module TZInfo # Represents a period of time in a time zone where the same offset from UTC # applies. The period of time is bounded at at least one end, either having a # start transition, end transition or both start and end transitions. class TransitionsTimezonePeriod < TimezonePeriod # @return [TimezoneTransition] the transition that defines the start of this # {TimezonePeriod} (`nil` if the start is unbounded). attr_reader :start_transition # @return [TimezoneTransition] the transition that defines the end of this # {TimezonePeriod} (`nil` if the end is unbounded). attr_reader :end_transition # Initializes a {TransitionsTimezonePeriod}. # # At least one of `start_transition` and `end_transition` must be specified. # # @param start_transition [TimezoneTransition] the transition that defines # the start of the period, or `nil` if the start is unbounded. # @param end_transition [TimezoneTransition] the transition that defines the # end of the period, or `nil` if the end is unbounded. # @raise [ArgumentError] if both `start_transition` and `end_transition` are # `nil`. def initialize(start_transition, end_transition) if start_transition super(start_transition.offset) elsif end_transition super(end_transition.previous_offset) else raise ArgumentError, 'At least one of start_transition and end_transition must be specified' end @start_transition = start_transition @end_transition = end_transition end # Determines if this {TransitionsTimezonePeriod} is equal to another # instance. # # @param p [Object] the instance to test for equality. # @return [Boolean] `true` if `p` is a {TransitionsTimezonePeriod} with the # same {offset}, {start_transition} and {end_transition}, otherwise # `false`. def ==(p) p.kind_of?(TransitionsTimezonePeriod) && start_transition == p.start_transition && end_transition == p.end_transition end alias eql? == # @return [Integer] a hash based on {start_transition} and {end_transition}. def hash [@start_transition, @end_transition].hash end # @return [String] the internal object state as a programmer-readable # `String`. def inspect "#<#{self.class}: @start_transition=#{@start_transition.inspect}, @end_transition=#{@end_transition.inspect}>" end end end tzinfo-2.0.4/lib/tzinfo/format1/0000755000004100000410000000000013773021353016512 5ustar www-datawww-datatzinfo-2.0.4/lib/tzinfo/format1/timezone_definer.rb0000644000004100000410000000631713773021353022374 0ustar www-datawww-data# encoding: UTF-8 # frozen_string_literal: true module TZInfo module Format1 # Instances of {Format1::TimezoneDefiner} are yielded to TZInfo::Data # format 1 modules by {TimezoneDefinition} to allow the offsets and # transitions of the time zone to be specified. # # @private class TimezoneDefiner < Format2::TimezoneDefiner #:nodoc: undef_method :subsequent_rules # Defines an offset. # # @param id [Symbol] an arbitrary value used identify the offset in # subsequent calls to transition. It must be unique. # @param utc_offset [Integer] the base offset from UTC of the zone in # seconds. This does not include daylight savings time. # @param std_offset [Integer] the daylight savings offset from the base # offset in seconds. Typically either 0 or 3600. # @param abbreviation [Symbol] an abbreviation for the offset, for # example, `:EST` or `:EDT`. # @raise [ArgumentError] if another offset has already been defined with # the given id. def offset(id, utc_offset, std_offset, abbreviation) super(id, utc_offset, std_offset, abbreviation.to_s) end # Defines a transition to a given offset. # # Transitions must be defined in increasing time order. # # @param year [Integer] the UTC year in which the transition occurs. Used # in earlier versions of TZInfo, but now ignored. # @param month [Integer] the UTC month in which the transition occurs. # Used in earlier versions of TZInfo, but now ignored. # @param offset_id [Symbol] references the id of a previously defined # offset (see #offset). # @param timestamp_value [Integer] the time the transition occurs as an # Integer number of seconds since 1970-01-01 00:00:00 UTC ignoring leap # seconds (i.e. each day is treated as if it were 86,400 seconds long). # @param datetime_numerator [Integer] the time of the transition as the # numerator of the `Rational` returned by `DateTime#ajd`. Used in # earlier versions of TZInfo, but now ignored. # @param datetime_denominator [Integer] the time of the transition as the # denominator of the `Rational` returned by `DateTime#ajd`. Used in # earlier versions of TZInfo, but now ignored. # @raise [ArgumentError] if `offset_id` does not reference a defined # offset. # @raise [ArgumentError] if `timestamp_value` is not greater than the # `timestamp_value` of the previously defined transition. # @raise [ArgumentError] if `datetime_numerator` is specified, but # `datetime_denominator` is not. In older versions of TZInfo, it was # possible to define a transition with the `DateTime` numerator as the # 4th parameter and the denominator as the 5th parameter. This style of # definition is not used in released versions of TZInfo::Data. def transition(year, month, offset_id, timestamp_value, datetime_numerator = nil, datetime_denominator = nil) raise ArgumentError, 'DateTime-only transitions are not supported' if datetime_numerator && !datetime_denominator super(offset_id, timestamp_value) end end end end tzinfo-2.0.4/lib/tzinfo/format1/timezone_definition.rb0000644000004100000410000000177213773021353023110 0ustar www-datawww-data# encoding: UTF-8 module TZInfo module Format1 # {Format1::TimezoneDefinition} is included into format 1 time zone # definition modules and provides the methods for defining time zones. # # @private module TimezoneDefinition #:nodoc: # Adds class methods to the includee. # # @param base [Module] the includee. def self.append_features(base) super base.extend(Format2::TimezoneDefinition::ClassMethods) base.extend(ClassMethods) end # Class methods for inclusion. # # @private module ClassMethods #:nodoc: private # @return the class to be instantiated and yielded by # {Format2::TimezoneDefinition::ClassMethods#timezone}. def timezone_definer_class TimezoneDefiner end end end end # Alias used by TZInfo::Data format1 releases. # # @private TimezoneDefinition = Format1::TimezoneDefinition #:nodoc: private_constant :TimezoneDefinition end tzinfo-2.0.4/lib/tzinfo/format1/timezone_index_definition.rb0000644000004100000410000000442213773021353024272 0ustar www-datawww-data# encoding: UTF-8 module TZInfo module Format1 # The format 1 TZInfo::Data time zone index file includes # {Format1::TimezoneIndexDefinition}, which provides methods used to define # time zones in the index. # # @private module TimezoneIndexDefinition #:nodoc: # Adds class methods to the includee and initializes class instance # variables. # # @param base [Module] the includee. def self.append_features(base) super base.extend(ClassMethods) base.instance_eval do @timezones = [] @data_timezones = [] @linked_timezones = [] end end # Class methods for inclusion. # # @private module ClassMethods #:nodoc: # @return [Array] a frozen `Array` containing the identifiers of # all data time zones. Identifiers are sorted according to # `String#<=>`. def data_timezones unless @data_timezones.frozen? @data_timezones = @data_timezones.sort.freeze end @data_timezones end # @return [Array] a frozen `Array` containing the identifiers of # all linked time zones. Identifiers are sorted according to # `String#<=>`. def linked_timezones unless @linked_timezones.frozen? @linked_timezones = @linked_timezones.sort.freeze end @linked_timezones end private # Adds a data time zone to the index. # # @param identifier [String] the time zone identifier. def timezone(identifier) identifier = StringDeduper.global.dedupe(identifier) @timezones << identifier @data_timezones << identifier end # Adds a linked time zone to the index. # # @param identifier [String] the time zone identifier. def linked_timezone(identifier) identifier = StringDeduper.global.dedupe(identifier) @timezones << identifier @linked_timezones << identifier end end end end # Alias used by TZInfo::Data format 1 releases. # # @private TimezoneIndexDefinition = Format1::TimezoneIndexDefinition #:nodoc: private_constant :TimezoneIndexDefinition end tzinfo-2.0.4/lib/tzinfo/format1/country_index_definition.rb0000644000004100000410000000372313773021353024146 0ustar www-datawww-data# encoding: UTF-8 module TZInfo module Format1 # The format 1 TZInfo::Data country index file includes # {Format1::CountryIndexDefinition}, which provides a # {CountryIndexDefinition::ClassMethods#country country} method used to # define each country in the index. # # @private module CountryIndexDefinition #:nodoc: # Adds class methods to the includee and initializes class instance # variables. # # @param base [Module] the includee. def self.append_features(base) super base.extend(ClassMethods) base.instance_eval { @countries = {} } end # Class methods for inclusion. # # @private module ClassMethods #:nodoc: # @return [Hash] a frozen `Hash` # of all the countries that have been defined in the index keyed by # their codes. def countries @description_deduper = nil @countries.freeze end private # Defines a country with an ISO 3166-1 alpha-2 country code and name. # # @param code [String] the ISO 3166-1 alpha-2 country code. # @param name [String] the name of the country. # @yield [definer] (optional) to obtain the time zones for the country. # @yieldparam definer [CountryDefiner] a {CountryDefiner} instance. def country(code, name) @description_deduper ||= StringDeduper.new zones = if block_given? definer = CountryDefiner.new(StringDeduper.global, @description_deduper) yield definer definer.timezones else [] end @countries[code.freeze] = DataSources::CountryInfo.new(code, name, zones) end end end end # Alias used by TZInfo::Data format1 releases. # # @private CountryIndexDefinition = Format1::CountryIndexDefinition #:nodoc: private_constant :CountryIndexDefinition end tzinfo-2.0.4/lib/tzinfo/format1/country_definer.rb0000644000004100000410000000102513773021353022234 0ustar www-datawww-data# encoding: UTF-8 module TZInfo module Format1 # Instances of {Format1::CountryDefiner} are yielded to the format 1 version # of `TZInfo::Data::Indexes::Countries` by {CountryIndexDefinition} to allow # the zones of a country to be specified. # # @private class CountryDefiner < Format2::CountryDefiner #:nodoc: # Initializes a new {CountryDefiner}. def initialize(identifier_deduper, description_deduper) super(nil, identifier_deduper, description_deduper) end end end end tzinfo-2.0.4/lib/tzinfo/format2/0000755000004100000410000000000013773021353016513 5ustar www-datawww-datatzinfo-2.0.4/lib/tzinfo/format2/timezone_definer.rb0000644000004100000410000000762213773021353022375 0ustar www-datawww-data# encoding: UTF-8 # frozen_string_literal: true module TZInfo module Format2 # Instances of {TimezoneDefiner} are yielded to TZInfo::Data modules by # {TimezoneDefinition} to allow the offsets and transitions of the time zone # to be specified. # # @private class TimezoneDefiner #:nodoc: # @return [Array] the defined transitions of the time # zone. attr_reader :transitions # Initializes a new TimezoneDefiner. # # @param string_deduper [StringDeduper] a {StringDeduper} instance to use # when deduping abbreviations. def initialize(string_deduper) @string_deduper = string_deduper @offsets = {} @transitions = [] end # Returns the first offset to be defined or `nil` if no offsets have been # defined. The first offset is observed before the time of the first # transition. # # @return [TimezoneOffset] the first offset to be defined or `nil` if no # offsets have been defined. def first_offset first = @offsets.first first && first.last end # Defines an offset. # # @param id [Symbol] an arbitrary value used identify the offset in # subsequent calls to transition. It must be unique. # @param base_utc_offset [Integer] the base offset from UTC of the zone in # seconds. This does not include daylight savings time. # @param std_offset [Integer] the daylight savings offset from the base # offset in seconds. Typically either 0 or 3600. # @param abbreviation [String] an abbreviation for the offset, for # example, EST or EDT. # @raise [ArgumentError] if another offset has already been defined with # the given id. def offset(id, base_utc_offset, std_offset, abbreviation) raise ArgumentError, 'An offset has already been defined with the given id' if @offsets.has_key?(id) # Dedupe non-frozen literals from format 1 on all Ruby versions and # format 2 on Ruby < 2.3 (without frozen_string_literal support). abbreviation = @string_deduper.dedupe(abbreviation) offset = TimezoneOffset.new(base_utc_offset, std_offset, abbreviation) @offsets[id] = offset @previous_offset ||= offset end # Defines a transition to a given offset. # # Transitions must be defined in increasing time order. # # @param offset_id [Symbol] references the id of a previously defined # offset. # @param timestamp_value [Integer] the time the transition occurs as a # number of seconds since 1970-01-01 00:00:00 UTC ignoring leap seconds # (i.e. each day is treated as if it were 86,400 seconds long). # @raise [ArgumentError] if `offset_id` does not reference a defined # offset. # @raise [ArgumentError] if `timestamp_value` is not greater than the # `timestamp_value` of the previously defined transition. def transition(offset_id, timestamp_value) offset = @offsets[offset_id] raise ArgumentError, 'offset_id has not been defined' unless offset raise ArgumentError, 'timestamp is not greater than the timestamp of the previously defined transition' if !@transitions.empty? && @transitions.last.timestamp_value >= timestamp_value @transitions << TimezoneTransition.new(offset, @previous_offset, timestamp_value) @previous_offset = offset end # Defines the rules that will be used for handling instants after the last # transition. # # This method is currently just a placeholder for forward compatibility # that accepts and ignores any arguments passed. # # Support for subsequent rules will be added in a future version of TZInfo # and the rules will be included in format 2 releases of TZInfo::Data. def subsequent_rules(*args) end end end end tzinfo-2.0.4/lib/tzinfo/format2/country_index_definer.rb0000644000004100000410000000616313773021353023434 0ustar www-datawww-data# encoding: UTF-8 module TZInfo module Format2 # Instances of {Format2::CountryIndexDefiner} are yielded to the format 2 # version of `TZInfo::Data::Indexes::Countries` by {CountryIndexDefinition} # to allow countries and their time zones to be specified. # # @private class CountryIndexDefiner #:nodoc: # @return [Hash] a `Hash` of all the countries that # have been defined in the index keyed by their codes. attr_reader :countries # Initializes a new {CountryIndexDefiner}. # # @param identifier_deduper [StringDeduper] a {StringDeduper} instance to # use when deduping time zone identifiers. # @param description_deduper [StringDeduper] a {StringDeduper} instance to # use when deduping time zone descriptions. def initialize(identifier_deduper, description_deduper) @identifier_deduper = identifier_deduper @description_deduper = description_deduper @shared_timezones = {} @countries = {} end # Defines a time zone shared by many countries with an reference for # subsequent use in country definitions. The latitude and longitude are # given as the numerator and denominator of a `Rational`. # # @param reference [Symbol] a unique reference for the time zone. # @param identifier [String] the time zone identifier. # @param latitude_numerator [Integer] the numerator of the latitude. # @param latitude_denominator [Integer] the denominator of the latitude. # @param longitude_numerator [Integer] the numerator of the longitude. # @param longitude_denominator [Integer] the denominator of the longitude. # @param description [String] an optional description for the time zone. def timezone(reference, identifier, latitude_numerator, latitude_denominator, longitude_numerator, longitude_denominator, description = nil) # Dedupe non-frozen literals from format 1 on all Ruby versions and # format 2 on Ruby < 2.3 (without frozen_string_literal support). @shared_timezones[reference] = CountryTimezone.new(@identifier_deduper.dedupe(identifier), Rational(latitude_numerator, latitude_denominator), Rational(longitude_numerator, longitude_denominator), description && @description_deduper.dedupe(description)) end # Defines a country. # # @param code [String] The ISO 3166-1 alpha-2 code of the country. # @param name [String] Then name of the country. # @yield [definer] yields (optional) to obtain the time zones for the # country. # @yieldparam definer [CountryDefiner] a {CountryDefiner} # instance that should be used to specify the time zones of the country. def country(code, name) timezones = if block_given? definer = CountryDefiner.new(@shared_timezones, @identifier_deduper, @description_deduper) yield definer definer.timezones else [] end @countries[code.freeze] = DataSources::CountryInfo.new(code, name, timezones) end end end end tzinfo-2.0.4/lib/tzinfo/format2/timezone_index_definer.rb0000644000004100000410000000301213773021353023551 0ustar www-datawww-data# encoding: UTF-8 module TZInfo module Format2 # Instances of {TimezoneIndexDefiner} are yielded by # {TimezoneIndexDefinition} to allow the time zone index to be defined. # # @private class TimezoneIndexDefiner #:nodoc: # @return [Array] the identifiers of all data time zones. attr_reader :data_timezones # @return [Array] the identifiers of all linked time zones. attr_reader :linked_timezones # Initializes a new TimezoneDefiner. # # @param string_deduper [StringDeduper] a {StringDeduper} instance to use # when deduping identifiers. def initialize(string_deduper) @string_deduper = string_deduper @data_timezones = [] @linked_timezones = [] end # Adds a data time zone to the index. # # @param identifier [String] the time zone identifier. def data_timezone(identifier) # Dedupe non-frozen literals from format 1 on all Ruby versions and # format 2 on Ruby < 2.3 (without frozen_string_literal support). @data_timezones << @string_deduper.dedupe(identifier) end # Adds a linked time zone to the index. # # @param identifier [String] the time zone identifier. def linked_timezone(identifier) # Dedupe non-frozen literals from format 1 on all Ruby versions and # format 2 on Ruby < 2.3 (without frozen_string_literal support). @linked_timezones << @string_deduper.dedupe(identifier) end end end end tzinfo-2.0.4/lib/tzinfo/format2/timezone_definition.rb0000644000004100000410000000500313773021353023100 0ustar www-datawww-data# encoding: UTF-8 module TZInfo module Format2 # {Format2::TimezoneDefinition} is included into format 2 time zone # definition modules and provides methods for defining time zones. # # @private module TimezoneDefinition #:nodoc: # Adds class methods to the includee. # # @param base [Module] the includee. def self.append_features(base) super base.extend(ClassMethods) end # Class methods for inclusion. # # @private module ClassMethods #:nodoc: # @return [TimezoneInfo] the last time zone to be defined. def get @timezone end private # @return [Class] the class to be instantiated and yielded by # {#timezone}. def timezone_definer_class TimezoneDefiner end # Defines a data time zone. # # @param identifier [String] the identifier of the time zone. # @yield [definer] yields to the caller to define the time zone. # @yieldparam definer [Object] an instance of the class returned by # {#timezone_definer_class}, typically {TimezoneDefiner}. def timezone(identifier) # Dedupe non-frozen literals from format 1 on all Ruby versions and # format 2 on Ruby < 2.3 (without frozen_string_literal support). string_deduper = StringDeduper.global identifier = string_deduper.dedupe(identifier) definer = timezone_definer_class.new(string_deduper) yield definer transitions = definer.transitions @timezone = if transitions.empty? DataSources::ConstantOffsetDataTimezoneInfo.new(identifier, definer.first_offset) else DataSources::TransitionsDataTimezoneInfo.new(identifier, transitions) end end # Defines a linked time zone. # # @param identifier [String] the identifier of the time zone being # defined. # @param link_to_identifier [String] the identifier the new time zone # links to (is an alias for). def linked_timezone(identifier, link_to_identifier) # Dedupe non-frozen literals from format 1 on all Ruby versions and # format 2 on Ruby < 2.3 (without frozen_string_literal support). string_deduper = StringDeduper.global @timezone = DataSources::LinkedTimezoneInfo.new(string_deduper.dedupe(identifier), string_deduper.dedupe(link_to_identifier)) end end end end end tzinfo-2.0.4/lib/tzinfo/format2/timezone_index_definition.rb0000644000004100000410000000344013773021353024272 0ustar www-datawww-data# encoding: UTF-8 module TZInfo module Format2 # The format 2 time zone index file includes {TimezoneIndexDefinition}, # which provides the {TimezoneIndexDefinition::ClassMethods#timezone_index # timezone_index} method used to define the index. # # @private module TimezoneIndexDefinition #:nodoc: # Adds class methods to the includee and initializes class instance # variables. # # @param base [Module] the includee. def self.append_features(base) super base.extend(ClassMethods) base.instance_eval do empty = [].freeze @timezones = empty @data_timezones = empty @linked_timezones = empty end end # Class methods for inclusion. # # @private module ClassMethods #:nodoc: # @return [Array] a frozen `Array` containing the identifiers of # all data time zones. Identifiers are sorted according to # `String#<=>`. attr_reader :data_timezones # @return [Array] a frozen `Array` containing the identifiers of # all linked time zones. Identifiers are sorted according to # `String#<=>`. attr_reader :linked_timezones # Defines the index. # # @yield [definer] yields to the caller to allow the index to be # defined. # @yieldparam definer [TimezoneIndexDefiner] a {TimezoneIndexDefiner} # instance that should be used to define the index. def timezone_index definer = TimezoneIndexDefiner.new(StringDeduper.global) yield definer @data_timezones = definer.data_timezones.sort!.freeze @linked_timezones = definer.linked_timezones.sort!.freeze end end end end end tzinfo-2.0.4/lib/tzinfo/format2/country_index_definition.rb0000644000004100000410000000262113773021353024143 0ustar www-datawww-data# encoding: UTF-8 module TZInfo module Format2 # The format 2 country index file includes # {Format2::CountryIndexDefinition}, which provides a # {CountryIndexDefinition::ClassMethods#country_index country_index} method # used to define the country index. # # @private module CountryIndexDefinition #:nodoc: # Adds class methods to the includee and initializes class instance # variables. # # @param base [Module] the includee. def self.append_features(base) super base.extend(ClassMethods) base.instance_eval { @countries = {}.freeze } end # Class methods for inclusion. # # @private module ClassMethods #:nodoc: # @return [Hash] a frozen `Hash` # of all the countries that have been defined in the index keyed by # their codes. attr_reader :countries private # Defines the index. # # @yield [definer] yields to allow the index to be defined. # @yieldparam definer [CountryIndexDefiner] a {CountryIndexDefiner} # instance that should be used to define the index. def country_index definer = CountryIndexDefiner.new(StringDeduper.global, StringDeduper.new) yield definer @countries = definer.countries.freeze end end end end end tzinfo-2.0.4/lib/tzinfo/format2/country_definer.rb0000644000004100000410000000651313773021353022244 0ustar www-datawww-data# encoding: UTF-8 # frozen_string_literal: true module TZInfo module Format2 # Instances of {Format2::CountryDefiner} are yielded to the format 2 version # of `TZInfo::Data::Indexes::Countries` by {CountryIndexDefiner} to allow # the zones of a country to be specified. # # @private class CountryDefiner #:nodoc: # @return [Array] the time zones observed in the country. attr_reader :timezones # Initializes a new {CountryDefiner}. # # @param shared_timezones [Hash] a `Hash` # containing time zones shared by more than one country, keyed by a # unique reference. # @param identifier_deduper [StringDeduper] a {StringDeduper} instance to # use when deduping time zone identifiers. # @param description_deduper [StringDeduper] a {StringDeduper} instance to # use when deduping time zone descriptions. def initialize(shared_timezones, identifier_deduper, description_deduper) @shared_timezones = shared_timezones @identifier_deduper = identifier_deduper @description_deduper = description_deduper @timezones = [] end # @overload timezone(reference) # Defines a time zone of a country as a reference to a pre-defined # shared time zone. # @param reference [Symbol] a reference for a pre-defined shared time # zone. # @overload timezone(identifier, latitude_numerator, latitude_denominator, longitude_numerator, longitude_denominator, description) # Defines a (non-shared) time zone of a country. The latitude and # longitude are given as the numerator and denominator of a `Rational`. # @param identifier [String] the time zone identifier. # @param latitude_numerator [Integer] the numerator of the latitude. # @param latitude_denominator [Integer] the denominator of the latitude. # @param longitude_numerator [Integer] the numerator of the longitude. # @param longitude_denominator [Integer] the denominator of the # longitude. # @param description [String] an optional description for the time zone. def timezone(identifier_or_reference, latitude_numerator = nil, latitude_denominator = nil, longitude_numerator = nil, longitude_denominator = nil, description = nil) if latitude_numerator unless latitude_denominator && longitude_numerator && longitude_denominator raise ArgumentError, 'Either just a reference should be supplied, or the identifier, latitude and longitude must all be specified' end # Dedupe non-frozen literals from format 1 on all Ruby versions and # format 2 on Ruby < 2.3 (without frozen_string_literal support). @timezones << CountryTimezone.new(@identifier_deduper.dedupe(identifier_or_reference), Rational(latitude_numerator, latitude_denominator), Rational(longitude_numerator, longitude_denominator), description && @description_deduper.dedupe(description)) else shared_timezone = @shared_timezones[identifier_or_reference] raise ArgumentError, "Unknown shared timezone: #{identifier_or_reference}" unless shared_timezone @timezones << shared_timezone end end end end end tzinfo-2.0.4/lib/tzinfo/with_offset.rb0000644000004100000410000000436513773021353020017 0ustar www-datawww-data# encoding: UTF-8 # frozen_string_literal: true module TZInfo # The {WithOffset} module is included in {TimeWithOffset}, # {DateTimeWithOffset} and {TimestampWithOffset}. It provides an override for # the {strftime} method that handles expanding the `%Z` directive according to # the {TimezoneOffset#abbreviation abbreviation} of the {TimezoneOffset} # associated with a local time. module WithOffset # Overrides the `Time`, `DateTime` or {Timestamp} version of `strftime`, # replacing `%Z` with the {TimezoneOffset#abbreviation abbreviation} of the # associated {TimezoneOffset}. If there is no associated offset, `%Z` is # expanded by the base class instead. # # All the format directives handled by the base class are supported. # # @param format [String] the format string. # @return [String] the formatted time. # @raise [ArgumentError] if `format` is `nil`. def strftime(format) raise ArgumentError, 'format must be specified' unless format if_timezone_offset do |o| abbreviation = nil format = format.gsub(/%(%*)Z/) do if $1.length.odd? # Return %%Z so the real strftime treats it as a literal %Z too. "#$1%Z" else "#$1#{abbreviation ||= o.abbreviation.gsub(/%/, '%%')}" end end end super end protected # Performs a calculation if there is an associated {TimezoneOffset}. # # @param result [Object] a result value that can be manipulated by the block # if there is an associated {TimezoneOffset}. # @yield [period, result] if there is an associated {TimezoneOffset}, the # block is yielded to in order to calculate the method result. # @yieldparam period [TimezoneOffset] the associated {TimezoneOffset}. # @yieldparam result [Object] the `result` parameter. # @yieldreturn [Object] the result of the calculation performed if there is # an associated {TimezoneOffset}. # @return [Object] the result of the block if there is an associated # {TimezoneOffset}, otherwise the `result` parameter. # # @private def if_timezone_offset(result = nil) #:nodoc: to = timezone_offset to ? yield(to, result) : result end end end tzinfo-2.0.4/lib/tzinfo/timezone_offset.rb0000644000004100000410000001071113773021353020666 0ustar www-datawww-data# encoding: UTF-8 # frozen_string_literal: true module TZInfo # Represents an offset from UTC observed by a time zone. class TimezoneOffset # Returns the base offset from UTC in seconds (`observed_utc_offset - # std_offset`). This does not include any adjustment made for daylight # savings time and will typically remain constant throughout the year. # # To obtain the currently observed offset from UTC, including the effect of # daylight savings time, use {observed_utc_offset} instead. # # If you require accurate {base_utc_offset} values, you should install the # tzinfo-data gem and set {DataSources::RubyDataSource} as the {DataSource}. # When using {DataSources::ZoneinfoDataSource}, the value of # {base_utc_offset} has to be derived from changes to the observed UTC # offset and DST status since it is not included in zoneinfo files. # # @return [Integer] the base offset from UTC in seconds. attr_reader :base_utc_offset alias utc_offset base_utc_offset # Returns the offset from the time zone's standard time in seconds # (`observed_utc_offset - base_utc_offset`). Zero when daylight savings time # is not in effect. Non-zero (usually 3600 = 1 hour) if daylight savings is # being observed. # # If you require accurate {std_offset} values, you should install the # tzinfo-data gem and set {DataSources::RubyDataSource} as the {DataSource}. # When using {DataSources::ZoneinfoDataSource}, the value of {std_offset} # has to be derived from changes to the observed UTC offset and DST status # since it is not included in zoneinfo files. # # @return [Integer] the offset from the time zone's standard time in # seconds. attr_reader :std_offset # Returns the observed offset from UTC in seconds (`base_utc_offset + # std_offset`). This includes adjustments made for daylight savings time. # # @return [Integer] the observed offset from UTC in seconds. attr_reader :observed_utc_offset alias utc_total_offset observed_utc_offset # The abbreviation that identifies this offset. For example GMT # (Greenwich Mean Time) or BST (British Summer Time) for Europe/London. # # @return [String] the abbreviation that identifies this offset. attr_reader :abbreviation alias abbr abbreviation # Initializes a new {TimezoneOffset}. # # {TimezoneOffset} instances should not normally be constructed manually. # # The passed in `abbreviation` instance will be frozen. # # @param base_utc_offset [Integer] the base offset from UTC in seconds. # @param std_offset [Integer] the offset from standard time in seconds. # @param abbreviation [String] the abbreviation identifying the offset. def initialize(base_utc_offset, std_offset, abbreviation) @base_utc_offset = base_utc_offset @std_offset = std_offset @abbreviation = abbreviation.freeze @observed_utc_offset = @base_utc_offset + @std_offset end # Determines if daylight savings is in effect (i.e. if {std_offset} is # non-zero). # # @return [Boolean] `true` if {std_offset} is non-zero, otherwise `false`. def dst? @std_offset != 0 end # Determines if this {TimezoneOffset} is equal to another instance. # # @param toi [Object] the instance to test for equality. # @return [Boolean] `true` if `toi` is a {TimezoneOffset} with the same # {utc_offset}, {std_offset} and {abbreviation} as this {TimezoneOffset}, # otherwise `false`. def ==(toi) toi.kind_of?(TimezoneOffset) && base_utc_offset == toi.base_utc_offset && std_offset == toi.std_offset && abbreviation == toi.abbreviation end # Determines if this {TimezoneOffset} is equal to another instance. # # @param toi [Object] the instance to test for equality. # @return [Boolean] `true` if `toi` is a {TimezoneOffset} with the same # {utc_offset}, {std_offset} and {abbreviation} as this {TimezoneOffset}, # otherwise `false`. def eql?(toi) self == toi end # @return [Integer] a hash based on {utc_offset}, {std_offset} and # {abbreviation}. def hash [@base_utc_offset, @std_offset, @abbreviation].hash end # @return [String] the internal object state as a programmer-readable # `String`. def inspect "#<#{self.class}: @base_utc_offset=#{@base_utc_offset}, @std_offset=#{@std_offset}, @abbreviation=#{@abbreviation}>" end end end tzinfo-2.0.4/lib/tzinfo/time_with_offset.rb0000644000004100000410000001232613773021353021031 0ustar www-datawww-data# encoding: UTF-8 # frozen_string_literal: true module TZInfo # A subclass of `Time` used to represent local times. {TimeWithOffset} holds a # reference to the related {TimezoneOffset} and overrides various methods to # return results appropriate for the {TimezoneOffset}. Certain operations will # clear the associated {TimezoneOffset} (if the {TimezoneOffset} would not # necessarily be valid for the result). Once the {TimezoneOffset} has been # cleared, {TimeWithOffset} behaves identically to `Time`. # # Arithmetic performed on {TimeWithOffset} instances is _not_ time zone-aware. # Regardless of whether transitions in the time zone are crossed, results of # arithmetic operations will always maintain the same offset from UTC # (`utc_offset`). The associated {TimezoneOffset} will aways be cleared. class TimeWithOffset < Time include WithOffset # @return [TimezoneOffset] the {TimezoneOffset} associated with this # instance. attr_reader :timezone_offset # Marks this {TimeWithOffset} as a local time with the UTC offset of a given # {TimezoneOffset} and sets the associated {TimezoneOffset}. # # @param timezone_offset [TimezoneOffset] the {TimezoneOffset} to use to set # the offset of this {TimeWithOffset}. # @return [TimeWithOffset] `self`. # @raise [ArgumentError] if `timezone_offset` is `nil`. def set_timezone_offset(timezone_offset) raise ArgumentError, 'timezone_offset must be specified' unless timezone_offset localtime(timezone_offset.observed_utc_offset) @timezone_offset = timezone_offset self end # An overridden version of `Time#dst?` that, if there is an associated # {TimezoneOffset}, returns the result of calling {TimezoneOffset#dst? dst?} # on that offset. # # @return [Boolean] `true` if daylight savings time is being observed, # otherwise `false`. def dst? to = timezone_offset to ? to.dst? : super end alias isdst dst? # An overridden version of `Time#getlocal` that clears the associated # {TimezoneOffset} if the base implementation of `getlocal` returns a # {TimeWithOffset}. # # @return [Time] a representation of the {TimeWithOffset} using either the # local time zone or the given offset. def getlocal(*args) # JRuby < 9.3 returns a Time in all cases. # JRuby >= 9.3 returns a Time when called with no arguments and a # TimeWithOffset with a timezone_offset assigned when called with an # offset argument. result = super result.clear_timezone_offset if result.kind_of?(TimeWithOffset) result end # An overridden version of `Time#gmtime` that clears the associated # {TimezoneOffset}. # # @return [TimeWithOffset] `self`. def gmtime super @timezone_offset = nil self end # An overridden version of `Time#localtime` that clears the associated # {TimezoneOffset}. # # @return [TimeWithOffset] `self`. def localtime(*args) super @timezone_offset = nil self end # An overridden version of `Time#round` that, if there is an associated # {TimezoneOffset}, returns a {TimeWithOffset} preserving that offset. # # @return [Time] the rounded time. def round(ndigits = 0) if_timezone_offset(super) {|o,t| self.class.at(t.to_i, t.subsec * 1_000_000).set_timezone_offset(o) } end # An overridden version of `Time#to_a`. The `isdst` (index 8) and `zone` # (index 9) elements of the array are set according to the associated # {TimezoneOffset}. # # @return [Array] an `Array` representation of the {TimeWithOffset}. def to_a if_timezone_offset(super) do |o,a| a[8] = o.dst? a[9] = o.abbreviation a end end # An overridden version of `Time#utc` that clears the associated # {TimezoneOffset}. # # @return [TimeWithOffset] `self`. def utc super @timezone_offset = nil self end # An overridden version of `Time#zone` that, if there is an associated # {TimezoneOffset}, returns the {TimezoneOffset#abbreviation abbreviation} # of that offset. # # @return [String] the {TimezoneOffset#abbreviation abbreviation} of the # associated {TimezoneOffset}, or the result from `Time#zone` if there is # no such offset. def zone to = timezone_offset to ? to.abbreviation : super end # An overridden version of `Time#to_datetime` that, if there is an # associated {TimezoneOffset}, returns a {DateTimeWithOffset} with that # offset. # # @return [DateTime] if there is an associated {TimezoneOffset}, a # {DateTimeWithOffset} representation of this {TimeWithOffset}, otherwise # a `Time` representation. def to_datetime if_timezone_offset(super) do |o,dt| offset = dt.offset result = DateTimeWithOffset.jd(dt.jd + dt.day_fraction - offset) result = result.new_offset(offset) unless offset == 0 result.set_timezone_offset(o) end end protected # Clears the associated {TimezoneOffset}. # # @return [TimeWithOffset] `self`. def clear_timezone_offset @timezone_offset = nil self end end end tzinfo-2.0.4/lib/tzinfo/datetime_with_offset.rb0000644000004100000410000001245713773021353021674 0ustar www-datawww-data# encoding: UTF-8 # frozen_string_literal: true require 'date' module TZInfo # A subclass of `DateTime` used to represent local times. {DateTimeWithOffset} # holds a reference to the related {TimezoneOffset} and overrides various # methods to return results appropriate for the {TimezoneOffset}. Certain # operations will clear the associated {TimezoneOffset} (if the # {TimezoneOffset} would not necessarily be valid for the result). Once the # {TimezoneOffset} has been cleared, {DateTimeWithOffset} behaves identically # to `DateTime`. # # Arithmetic performed on {DateTimeWithOffset} instances is _not_ time # zone-aware. Regardless of whether transitions in the time zone are crossed, # results of arithmetic operations will always maintain the same offset from # UTC (`offset`). The associated {TimezoneOffset} will aways be cleared. class DateTimeWithOffset < DateTime include WithOffset # @return [TimezoneOffset] the {TimezoneOffset} associated with this # instance. attr_reader :timezone_offset # Sets the associated {TimezoneOffset}. # # @param timezone_offset [TimezoneOffset] a {TimezoneOffset} valid at the # time and for the offset of this {DateTimeWithOffset}. # @return [DateTimeWithOffset] `self`. # @raise [ArgumentError] if `timezone_offset` is `nil`. # @raise [ArgumentError] if `timezone_offset.observed_utc_offset` does not # equal `self.offset * 86400`. def set_timezone_offset(timezone_offset) raise ArgumentError, 'timezone_offset must be specified' unless timezone_offset raise ArgumentError, 'timezone_offset.observed_utc_offset does not match self.utc_offset' if offset * 86400 != timezone_offset.observed_utc_offset @timezone_offset = timezone_offset self end # An overridden version of `DateTime#to_time` that, if there is an # associated {TimezoneOffset}, returns a {DateTimeWithOffset} with that # offset. # # @return [Time] if there is an associated {TimezoneOffset}, a # {TimeWithOffset} representation of this {DateTimeWithOffset}, otherwise # a `Time` representation. def to_time if_timezone_offset(super) do |o,t| # Ruby 2.4.0 changed the behaviour of to_time so that it preserves the # offset instead of converting to the system local timezone. # # When self has an associated TimezonePeriod, this implementation will # preserve the offset on all versions of Ruby. TimeWithOffset.at(t.to_i, t.subsec * 1_000_000).set_timezone_offset(o) end end # An overridden version of `DateTime#downto` that clears the associated # {TimezoneOffset} of the returned or yielded instances. def downto(min) if block_given? super {|dt| yield dt.clear_timezone_offset } else enum = super enum.each {|dt| dt.clear_timezone_offset } enum end end # An overridden version of `DateTime#england` that preserves the associated # {TimezoneOffset}. # # @return [DateTime] def england # super doesn't call #new_start on MRI, so each method has to be # individually overridden. if_timezone_offset(super) {|o,dt| dt.set_timezone_offset(o) } end # An overridden version of `DateTime#gregorian` that preserves the # associated {TimezoneOffset}. # # @return [DateTime] def gregorian # super doesn't call #new_start on MRI, so each method has to be # individually overridden. if_timezone_offset(super) {|o,dt| dt.set_timezone_offset(o) } end # An overridden version of `DateTime#italy` that preserves the associated # {TimezoneOffset}. # # @return [DateTime] def italy # super doesn't call #new_start on MRI, so each method has to be # individually overridden. if_timezone_offset(super) {|o,dt| dt.set_timezone_offset(o) } end # An overridden version of `DateTime#julian` that preserves the associated # {TimezoneOffset}. # # @return [DateTime] def julian # super doesn't call #new_start on MRI, so each method has to be # individually overridden. if_timezone_offset(super) {|o,dt| dt.set_timezone_offset(o) } end # An overridden version of `DateTime#new_start` that preserves the # associated {TimezoneOffset}. # # @return [DateTime] def new_start(start = Date::ITALY) if_timezone_offset(super) {|o,dt| dt.set_timezone_offset(o) } end # An overridden version of `DateTime#step` that clears the associated # {TimezoneOffset} of the returned or yielded instances. def step(limit, step = 1) if block_given? super {|dt| yield dt.clear_timezone_offset } else enum = super enum.each {|dt| dt.clear_timezone_offset } enum end end # An overridden version of `DateTime#upto` that clears the associated # {TimezoneOffset} of the returned or yielded instances. def upto(max) if block_given? super {|dt| yield dt.clear_timezone_offset } else enum = super enum.each {|dt| dt.clear_timezone_offset } enum end end protected # Clears the associated {TimezoneOffset}. # # @return [DateTimeWithOffset] `self`. def clear_timezone_offset @timezone_offset = nil self end end end tzinfo-2.0.4/lib/tzinfo/country.rb0000644000004100000410000001634213773021353017177 0ustar www-datawww-data# encoding: UTF-8 # frozen_string_literal: true module TZInfo # {InvalidCountryCode} is raised by {Country#get} if the code given is not a # valid ISO 3166-1 alpha-2 code. class InvalidCountryCode < StandardError end # The {Country} class represents an ISO 3166-1 country. It can be used to # obtain a list of time zones observed by a country. For example: # # united_states = Country.get('US') # united_states.zone_identifiers # united_states.zones # united_states.zone_info # # The {Country} class is thread-safe. It is safe to use class and instance # methods of {Country} in concurrently executing threads. Instances of # {Country} can be shared across thread boundaries. # # Country information available through TZInfo is intended as an aid for # users, to help them select time zone data appropriate for their practical # needs. It is not intended to take or endorse any position on legal or # territorial claims. class Country include Comparable class << self # Gets a {Country} by its ISO 3166-1 alpha-2 code. # # The {Country.all_codes} method can be used to obtain a list of valid ISO # 3166-1 alpha-2 codes. # # @param code [String] An ISO 3166-1 alpha-2 code. # @return [Country] a {Country} instance representing the ISO-3166-1 # country identified by the `code` parameter. # @raise [InvalidCountryCode] If {code} is not a valid ISO 3166-1 alpha-2 # code it couldn't be found. def get(code) Country.new(data_source.get_country_info(code)) end # @return [Array] an `Array` containing all the valid ISO 3166-1 # alpha-2 country codes. def all_codes data_source.country_codes end # @return [Array] an `Array` containing one {Country} instance # for each defined country. def all data_source.country_codes.collect {|code| get(code)} end private # @return [DataSource] the current DataSource. def data_source DataSource.get end end # Initializes a new {Country} based upon a {DataSources::CountryInfo} # instance. # # {Country} instances should not normally be constructed directly. Use # the {Country.get} method to obtain instances instead. # # @param info [DataSources::CountryInfo] the data to base the new {Country} # instance upon. def initialize(info) @info = info end # @return [String] the ISO 3166-1 alpha-2 country code. def code @info.code end # @return [String] the name of the country. def name @info.name end # @return [String] a `String` representation of this {Country} (the name of # the country). def to_s name end # @return [String] the internal object state as a programmer-readable # `String`. def inspect "#<#{self.class}: #{@info.code}>" end # Returns an `Array` containing the identifier for each time zone observed # by the country. These are in an order that # # 1. makes some geographical sense, and # 2. puts the most populous zones first, where that does not contradict 1. # # Returned zone identifiers may refer to cities and regions outside of the # country. This will occur if the zone covers multiple countries. Any zones # referring to a city or region in a different country will be listed after # those relating to this country. # # @return [Array] an `Array` containing the identifier for each time # zone observed by the country def zone_identifiers zone_info.map(&:identifier) end alias zone_names zone_identifiers # Returns An `Array` containing a {Timezone} instance for each time zone # observed by the country. These are in an order that # # 1. makes some geographical sense, and # 2. puts the most populous zones first, where that does not contradict 1. # # The identifiers of the time zones returned may refer to cities and regions # outside of the country. This will occur if the time zone covers multiple # countries. Any zones referring to a city or region in a different country # will be listed after those relating to this country. # # The results are actually instances of {TimezoneProxy} in order to defer # loading of the time zone transition data until it is first needed. # # @return [Array] an `Array` containing a {Timezone} instance for # each time zone observed by the country. def zones zone_info.map(&:timezone) end # Returns a frozen `Array` containing a {CountryTimezone} instance for each # time zone observed by the country. These are in an order that # # 1. makes some geographical sense, and # 2. puts the most populous zones first, where that does not contradict 1. # # The {CountryTimezone} instances can be used to obtain the location and # descriptions of the observed time zones. # # Identifiers and descriptions of the time zones returned may refer to # cities and regions outside of the country. This will occur if the time # zone covers multiple countries. Any zones referring to a city or region in # a different country will be listed after those relating to this country. # # @return [Array] a frozen `Array` containing a # {CountryTimezone} instance for each time zone observed by the country. def zone_info @info.zones end # Compares this {Country} with another based on their {code}. # # @param c [Object] an `Object` to compare this {Country} with. # @return [Integer] -1 if `c` is less than `self`, 0 if `c` is equal to # `self` and +1 if `c` is greater than `self`, or `nil` if `c` is not an # instance of {Country}. def <=>(c) return nil unless c.is_a?(Country) code <=> c.code end # @param c [Object] an `Object` to compare this {Country} with. # @return [Boolean] `true` if `c` is an instance of {Country} and has the # same code as `self`, otherwise `false`. def eql?(c) self == c end # @return [Integer] a hash based on the {code}. def hash code.hash end # Matches `regexp` against the {code} of this {Country}. # # @param regexp [Regexp] a `Regexp` to match against the {code} of # this {Country}. # @return [Integer] the position the match starts, or `nil` if there is no # match. def =~(regexp) regexp =~ code end # Returns a serialized representation of this {Country}. This method is # called when using `Marshal.dump` with an instance of {Country}. # # @param limit [Integer] the maximum depth to dump - ignored. # @return [String] a serialized representation of this {Country}. def _dump(limit) code end # Loads a {Country} from the serialized representation returned by {_dump}. # This is method is called when using `Marshal.load` or `Marshal.restore` # to restore a serialized {Country}. # # @param data [String] a serialized representation of a {Country}. # @return [Country] the result of converting `data` back into a {Country}. def self._load(data) Country.get(data) end end end tzinfo-2.0.4/lib/tzinfo/format2.rb0000644000004100000410000000027013773021353017037 0ustar www-datawww-data# encoding: UTF-8 module TZInfo # Modules and classes used by the format 2 version of TZInfo::Data. # # @private module Format2 #:nodoc: end private_constant :Format2 end tzinfo-2.0.4/lib/tzinfo/linked_timezone.rb0000644000004100000410000000255013773021353020650 0ustar www-datawww-data# encoding: UTF-8 module TZInfo # Represents time zones that are defined as a link to or alias for another # time zone. class LinkedTimezone < InfoTimezone # Initializes a new {LinkedTimezone}. # # {LinkedTimezone} instances should not normally be created directly. Use # the {Timezone.get} method to obtain {Timezone} instances. # # @param info [DataSources::LinkedTimezoneInfo] a # {DataSources::LinkedTimezoneInfo} instance supplied by a {DataSource} # that will be used as the source of data for this {LinkedTimezone}. def initialize(info) super @linked_timezone = Timezone.get(info.link_to_identifier) end # (see Timezone#period_for) def period_for(time) @linked_timezone.period_for(time) end # (see Timezone#periods_for_local) def periods_for_local(local_time) @linked_timezone.periods_for_local(local_time) end # (see Timezone#transitions_up_to) def transitions_up_to(to, from = nil) @linked_timezone.transitions_up_to(to, from) end # Returns the canonical {Timezone} instance for this {LinkedTimezone}. # # For a {LinkedTimezone}, this is the canonical zone of the link target. # # @return [Timezone] the canonical {Timezone} instance for this {Timezone}. def canonical_zone @linked_timezone.canonical_zone end end end tzinfo-2.0.4/lib/tzinfo/data_sources/0000755000004100000410000000000013773021353017615 5ustar www-datawww-datatzinfo-2.0.4/lib/tzinfo/data_sources/constant_offset_data_timezone_info.rb0000644000004100000410000000400713773021353027260 0ustar www-datawww-data# encoding: UTF-8 # frozen_string_literal: true module TZInfo module DataSources # Represents a data time zone defined by a constantly observed offset. class ConstantOffsetDataTimezoneInfo < DataTimezoneInfo # @return [TimezoneOffset] the offset that is constantly observed. attr_reader :constant_offset # Initializes a new {ConstantOffsetDataTimezoneInfo}. # # The passed in `identifier` instance will be frozen. A reference to the # passed in {TimezoneOffset} will be retained. # # @param identifier [String] the identifier of the time zone. # @param constant_offset [TimezoneOffset] the constantly observed offset. # @raise [ArgumentError] if `identifier` or `constant_offset` is `nil`. def initialize(identifier, constant_offset) super(identifier) raise ArgumentError, 'constant_offset must be specified' unless constant_offset @constant_offset = constant_offset end # @param timestamp [Timestamp] ignored. # @return [TimezonePeriod] an unbounded {TimezonePeriod} for the time # zone's constantly observed offset. def period_for(timestamp) constant_period end # @param local_timestamp [Timestamp] ignored. # @return [Array] an `Array` containing a single unbounded # {TimezonePeriod} for the time zone's constantly observed offset. def periods_for_local(local_timestamp) [constant_period] end # @param to_timestamp [Timestamp] ignored. # @param from_timestamp [Timestamp] ignored. # @return [Array] an empty `Array`, since there are no transitions in time # zones that observe a constant offset. def transitions_up_to(to_timestamp, from_timestamp = nil) [] end private # @return [TimezonePeriod] an unbounded {TimezonePeriod} with the constant # offset of this timezone. def constant_period OffsetTimezonePeriod.new(@constant_offset) end end end end tzinfo-2.0.4/lib/tzinfo/data_sources/transitions_data_timezone_info.rb0000644000004100000410000002174013773021353026441 0ustar www-datawww-data# encoding: UTF-8 # frozen_string_literal: true module TZInfo module DataSources # Represents a data time zone defined by a list of transitions that change # the locally observed time. class TransitionsDataTimezoneInfo < DataTimezoneInfo # @return [Array] the transitions that define this # time zone in order of ascending timestamp. attr_reader :transitions # Initializes a new {TransitionsDataTimezoneInfo}. # # The passed in `identifier` instance will be frozen. A reference to the # passed in `Array` will be retained. # # The `transitions` `Array` must be sorted in order of ascending # timestamp. Each transition must have a # {TimezoneTransition#timestamp_value timestamp_value} that is greater # than the {TimezoneTransition#timestamp_value timestamp_value} of the # prior transition. # # @param identifier [String] the identifier of the time zone. # @param transitions [Array] an `Array` of # transitions that each indicate when a change occurs in the locally # observed time. # @raise [ArgumentError] if `identifier` is `nil`. # @raise [ArgumentError] if `transitions` is `nil`. # @raise [ArgumentError] if `transitions` is an empty `Array`. def initialize(identifier, transitions) super(identifier) raise ArgumentError, 'transitions must be specified' unless transitions raise ArgumentError, 'transitions must not be an empty Array' if transitions.empty? @transitions = transitions.freeze end # (see DataTimezoneInfo#period_for) def period_for(timestamp) raise ArgumentError, 'timestamp must be specified' unless timestamp raise ArgumentError, 'timestamp must have a specified utc_offset' unless timestamp.utc_offset timestamp_value = timestamp.value index = find_minimum_transition {|t| t.timestamp_value >= timestamp_value } if index transition = @transitions[index] if transition.timestamp_value == timestamp_value # timestamp occurs within the second of the found transition, so is # the transition that starts the period. start_transition = transition end_transition = @transitions[index + 1] else # timestamp occurs before the second of the found transition, so is # the transition that ends the period. start_transition = index == 0 ? nil : @transitions[index - 1] end_transition = transition end else start_transition = @transitions.last end_transition = nil end TransitionsTimezonePeriod.new(start_transition, end_transition) end # (see DataTimezoneInfo#periods_for_local) def periods_for_local(local_timestamp) raise ArgumentError, 'local_timestamp must be specified' unless local_timestamp raise ArgumentError, 'local_timestamp must have an unspecified utc_offset' if local_timestamp.utc_offset local_timestamp_value = local_timestamp.value latest_possible_utc_value = local_timestamp_value + 86400 earliest_possible_utc_value = local_timestamp_value - 86400 # Find the index of the first transition that occurs after a latest # possible UTC representation of the local timestamp and then search # backwards until an earliest possible UTC representation. index = find_minimum_transition {|t| t.timestamp_value >= latest_possible_utc_value } # No transitions after latest_possible_utc_value, set to max index + 1 # to search backwards including the period after the last transition index = @transitions.length unless index result = [] index.downto(0) do |i| start_transition = i > 0 ? @transitions[i - 1] : nil end_transition = @transitions[i] offset = start_transition ? start_transition.offset : end_transition.previous_offset utc_timestamp_value = local_timestamp_value - offset.observed_utc_offset # It is not necessary to compare the sub-seconds because a timestamp # is in the period if is >= the start transition (sub-seconds would # make == become >) and if it is < the end transition (which # sub-seconds cannot affect). if (!start_transition || utc_timestamp_value >= start_transition.timestamp_value) && (!end_transition || utc_timestamp_value < end_transition.timestamp_value) result << TransitionsTimezonePeriod.new(start_transition, end_transition) elsif end_transition && end_transition.timestamp_value < earliest_possible_utc_value break end end result.reverse! end # (see DataTimezoneInfo#transitions_up_to) def transitions_up_to(to_timestamp, from_timestamp = nil) raise ArgumentError, 'to_timestamp must be specified' unless to_timestamp raise ArgumentError, 'to_timestamp must have a specified utc_offset' unless to_timestamp.utc_offset if from_timestamp raise ArgumentError, 'from_timestamp must have a specified utc_offset' unless from_timestamp.utc_offset raise ArgumentError, 'to_timestamp must be greater than from_timestamp' if to_timestamp <= from_timestamp end if from_timestamp from_index = find_minimum_transition {|t| transition_on_or_after_timestamp?(t, from_timestamp) } return [] unless from_index else from_index = 0 end to_index = find_minimum_transition {|t| transition_on_or_after_timestamp?(t, to_timestamp) } if to_index return [] if to_index < 1 to_index -= 1 else to_index = -1 end @transitions[from_index..to_index] end private # Array#bsearch_index was added in Ruby 2.3.0. Use bsearch_index to find # transitions if it is available, otherwise use a Ruby implementation. if [].respond_to?(:bsearch_index) # Performs a binary search on {transitions} to find the index of the # earliest transition satisfying a condition. # # @yield [transition] the caller will be yielded to to test the search # condition. # @yieldparam transition [TimezoneTransition] a {TimezoneTransition} # instance from {transitions}. # @yieldreturn [Boolean] `true` for the earliest transition that # satisfies the condition and return `true` for all subsequent # transitions. In all other cases, the result of the block must be # `false`. # @return [Integer] the index of the earliest transition safisfying # the condition or `nil` if there are no such transitions. # # :nocov_no_array_bsearch_index: def find_minimum_transition(&block) @transitions.bsearch_index(&block) end # :nocov_no_array_bsearch_index: else # Performs a binary search on {transitions} to find the index of the # earliest transition satisfying a condition. # # @yield [transition] the caller will be yielded to to test the search # condition. # @yieldparam transition [TimezoneTransition] a {TimezoneTransition} # instance from {transitions}. # @yieldreturn [Boolean] `true` for the earliest transition that # satisfies the condition and return `true` for all subsequent # transitions. In all other cases, the result of the block must be # `false`. # @return [Integer] the index of the earliest transition safisfying # the condition or `nil` if there are no such transitions. # # :nocov_array_bsearch_index: def find_minimum_transition # A Ruby implementation of the find-minimum mode of Array#bsearch_index. low = 0 high = @transitions.length satisfied = false while low < high do mid = (low + high).div(2) if yield @transitions[mid] satisfied = true high = mid else low = mid + 1 end end satisfied ? low : nil end # :nocov_array_bsearch_index: end # Determines if a transition occurs at or after a given {Timestamp}, # taking the {Timestamp#sub_second sub_second} into consideration. # # @param transition [TimezoneTransition] the transition to compare. # @param timestamp [Timestamp] the timestamp to compare. # @return [Boolean] `true` if `transition` occurs at or after `timestamp`, # otherwise `false`. def transition_on_or_after_timestamp?(transition, timestamp) transition_timestamp_value = transition.timestamp_value timestamp_value = timestamp.value transition_timestamp_value > timestamp_value || transition_timestamp_value == timestamp_value && timestamp.sub_second == 0 end end end end tzinfo-2.0.4/lib/tzinfo/data_sources/linked_timezone_info.rb0000644000004100000410000000232013773021353024332 0ustar www-datawww-data# encoding: UTF-8 module TZInfo module DataSources # Represents a time zone that is defined as a link to or alias of another # zone. class LinkedTimezoneInfo < TimezoneInfo # @return [String] the identifier of the time zone that provides the data # (that this zone links to or is an alias for). attr_reader :link_to_identifier # Initializes a new {LinkedTimezoneInfo}. The passed in `identifier` and # `link_to_identifier` instances will be frozen. # # @param identifier [String] the identifier of the time zone. # @param link_to_identifier [String] the identifier of the time zone that # this zone link to. # @raise [ArgumentError] if `identifier` or `link_to_identifier` are # `nil`. def initialize(identifier, link_to_identifier) super(identifier) raise ArgumentError, 'link_to_identifier must be specified' unless link_to_identifier @link_to_identifier = link_to_identifier.freeze end # @return [LinkedTimezone] a new {LinkedTimezone} instance for the time # zone represented by this {LinkedTimezoneInfo}. def create_timezone LinkedTimezone.new(self) end end end end tzinfo-2.0.4/lib/tzinfo/data_sources/country_info.rb0000644000004100000410000000256313773021353022666 0ustar www-datawww-data# encoding: UTF-8 # frozen_string_literal: true module TZInfo module DataSources # Represents a country and references to its time zones as returned by a # {DataSource}. class CountryInfo # @return [String] the ISO 3166-1 alpha-2 country code. attr_reader :code # @return [String] the name of the country. attr_reader :name # @return [Array] the time zones observed in the country. attr_reader :zones # Initializes a new {CountryInfo}. The passed in `code`, `name` and # `zones` instances will be frozen. # # @param code [String] an ISO 3166-1 alpha-2 country code. # @param name [String] the name of the country. # @param zones [Array] the time zones observed in the # country. # @raise [ArgumentError] if `code`, `name` or `zones` is `nil`. def initialize(code, name, zones) raise ArgumentError, 'code must be specified' unless code raise ArgumentError, 'name must be specified' unless name raise ArgumentError, 'zones must be specified' unless zones @code = code.freeze @name = name.freeze @zones = zones.freeze end # @return [String] the internal object state as a programmer-readable # `String`. def inspect "#<#{self.class}: #@code>" end end end end tzinfo-2.0.4/lib/tzinfo/data_sources/zoneinfo_data_source.rb0000644000004100000410000005660513773021353024356 0ustar www-datawww-data# encoding: UTF-8 # frozen_string_literal: true module TZInfo # Use send as a workaround for erroneous 'wrong number of arguments' errors # with JRuby 9.0.5.0 when calling methods with Java implementations. See #114. send(:using, UntaintExt) if TZInfo.const_defined?(:UntaintExt) module DataSources # An {InvalidZoneinfoDirectory} exception is raised if {ZoneinfoDataSource} # is initialized with a specific zoneinfo path that is not a valid zoneinfo # directory. A valid zoneinfo directory is one that contains time zone # files, a country code index file named iso3166.tab and a time zone index # file named zone1970.tab or zone.tab. class InvalidZoneinfoDirectory < StandardError end # A {ZoneinfoDirectoryNotFound} exception is raised if no valid zoneinfo # directory could be found when checking the paths listed in # {ZoneinfoDataSource.search_path}. A valid zoneinfo directory is one that # contains time zone files, a country code index file named iso3166.tab and # a time zone index file named zone1970.tab or zone.tab. class ZoneinfoDirectoryNotFound < StandardError end # A DataSource implementation that loads data from a 'zoneinfo' directory # containing compiled "TZif" version 3 (or earlier) files in addition to # iso3166.tab and zone1970.tab or zone.tab index files. # # To have TZInfo load the system zoneinfo files, call # {TZInfo::DataSource.set} as follows: # # TZInfo::DataSource.set(:zoneinfo) # # To load zoneinfo files from a particular directory, pass the directory to # {TZInfo::DataSource.set}: # # TZInfo::DataSource.set(:zoneinfo, directory) # # To load zoneinfo files from a particular directory, but load the # iso3166.tab index file from a separate location, pass the directory and # path to the iso3166.tab file to {TZInfo::DataSource.set}: # # TZInfo::DataSource.set(:zoneinfo, directory, iso3166_path) # # Please note that versions of the 'zic' tool (used to build zoneinfo files) # that were released prior to February 2006 created zoneinfo files that used # 32-bit integers for transition timestamps. Later versions of zic produce # zoneinfo files that use 64-bit integers. If you have 32-bit zoneinfo files # on your system, then any queries falling outside of the range 1901-12-13 # 20:45:52 to 2038-01-19 03:14:07 may be inaccurate. # # Most modern platforms include 64-bit zoneinfo files. However, Mac OS X (up # to at least 10.8.4) still uses 32-bit zoneinfo files. # # To check whether your zoneinfo files contain 32-bit or 64-bit transition # data, you can run the following code (substituting the identifier of the # zone you want to test for `zone_identifier`): # # TZInfo::DataSource.set(:zoneinfo) # dir = TZInfo::DataSource.get.zoneinfo_dir # File.open(File.join(dir, zone_identifier), 'r') {|f| f.read(5) } # # If the last line returns `"TZif\\x00"`, then you have a 32-bit zoneinfo # file. If it returns `"TZif2"` or `"TZif3"` then you have a 64-bit zoneinfo # file. # # It is also worth noting that as of the 2017c release of the IANA Time Zone # Database, 64-bit zoneinfo files only include future transitions up to # 2038-01-19 03:14:07. Any queries falling after this time may be # inaccurate. class ZoneinfoDataSource < DataSource # The default value of {ZoneinfoDataSource.search_path}. DEFAULT_SEARCH_PATH = ['/usr/share/zoneinfo', '/usr/share/lib/zoneinfo', '/etc/zoneinfo'].freeze private_constant :DEFAULT_SEARCH_PATH # The default value of {ZoneinfoDataSource.alternate_iso3166_tab_search_path}. DEFAULT_ALTERNATE_ISO3166_TAB_SEARCH_PATH = ['/usr/share/misc/iso3166.tab', '/usr/share/misc/iso3166'].freeze private_constant :DEFAULT_ALTERNATE_ISO3166_TAB_SEARCH_PATH # Paths to be checked to find the system zoneinfo directory. # # @private @@search_path = DEFAULT_SEARCH_PATH.dup # Paths to possible alternate iso3166.tab files (used to locate the # system-wide iso3166.tab files on FreeBSD and OpenBSD). # # @private @@alternate_iso3166_tab_search_path = DEFAULT_ALTERNATE_ISO3166_TAB_SEARCH_PATH.dup class << self # An `Array` of directories that will be checked to find the system # zoneinfo directory. # # Directories are checked in the order they appear in the `Array`. # # The default value is `['/usr/share/zoneinfo', # '/usr/share/lib/zoneinfo', '/etc/zoneinfo']`. # # @return [Array] an `Array` of directories to check in order to # find the system zoneinfo directory. def search_path @@search_path end # Sets the directories to be checked when locating the system zoneinfo # directory. # # Can be set to an `Array` of directories or a `String` containing # directories separated with `File::PATH_SEPARATOR`. # # Directories are checked in the order they appear in the `Array` or # `String`. # # Set to `nil` to revert to the default paths. # # @param search_path [Object] either `nil` or a list of directories to # check as either an `Array` of `String` or a `File::PATH_SEPARATOR` # separated `String`. def search_path=(search_path) @@search_path = process_search_path(search_path, DEFAULT_SEARCH_PATH) end # An `Array` of paths that will be checked to find an alternate # iso3166.tab file if one was not included in the zoneinfo directory # (for example, on FreeBSD and OpenBSD systems). # # Paths are checked in the order they appear in the `Array`. # # The default value is `['/usr/share/misc/iso3166.tab', # '/usr/share/misc/iso3166']`. # # @return [Array] an `Array` of paths to check in order to # locate an iso3166.tab file. def alternate_iso3166_tab_search_path @@alternate_iso3166_tab_search_path end # Sets the paths to check to locate an alternate iso3166.tab file if one # was not included in the zoneinfo directory. # # Can be set to an `Array` of paths or a `String` containing paths # separated with `File::PATH_SEPARATOR`. # # Paths are checked in the order they appear in the array. # # Set to `nil` to revert to the default paths. # # @param alternate_iso3166_tab_search_path [Object] either `nil` or a # list of paths to check as either an `Array` of `String` or a # `File::PATH_SEPARATOR` separated `String`. def alternate_iso3166_tab_search_path=(alternate_iso3166_tab_search_path) @@alternate_iso3166_tab_search_path = process_search_path(alternate_iso3166_tab_search_path, DEFAULT_ALTERNATE_ISO3166_TAB_SEARCH_PATH) end private # Processes a path for use as the {search_path} or # {alternate_iso3166_tab_search_path}. # # @param path [Object] either `nil` or a list of paths to check as # either an `Array` of `String` or a `File::PATH_SEPARATOR` separated # `String`. # @param default [Array] the default value. # @return [Array] the processed path. def process_search_path(path, default) if path if path.kind_of?(String) path.split(File::PATH_SEPARATOR) else path.collect(&:to_s) end else default.dup end end end # @return [String] the zoneinfo directory being used. attr_reader :zoneinfo_dir # (see DataSource#country_codes) attr_reader :country_codes # Initializes a new {ZoneinfoDataSource}. # # If `zoneinfo_dir` is specified, it will be checked and used as the # source of zoneinfo files. # # The directory must contain a file named iso3166.tab and a file named # either zone1970.tab or zone.tab. These may either be included in the # root of the directory or in a 'tab' sub-directory and named country.tab # and zone_sun.tab respectively (as is the case on Solaris). # # Additionally, the path to iso3166.tab can be overridden using the # `alternate_iso3166_tab_path` parameter. # # If `zoneinfo_dir` is not specified or `nil`, the paths referenced in # {search_path} are searched in order to find a valid zoneinfo directory # (one that contains zone1970.tab or zone.tab and iso3166.tab files as # above). # # The paths referenced in {alternate_iso3166_tab_search_path} are also # searched to find an iso3166.tab file if one of the searched zoneinfo # directories doesn't contain an iso3166.tab file. # # @param zoneinfo_dir [String] an optional path to a directory to use as # the source of zoneinfo files. # @param alternate_iso3166_tab_path [String] an optional path to the # iso3166.tab file. # @raise [InvalidZoneinfoDirectory] if the iso3166.tab and zone1970.tab or # zone.tab files cannot be found using the `zoneinfo_dir` and # `alternate_iso3166_tab_path` parameters. # @raise [ZoneinfoDirectoryNotFound] if no valid directory can be found # by searching. def initialize(zoneinfo_dir = nil, alternate_iso3166_tab_path = nil) super() if zoneinfo_dir iso3166_tab_path, zone_tab_path = validate_zoneinfo_dir(zoneinfo_dir, alternate_iso3166_tab_path) unless iso3166_tab_path && zone_tab_path raise InvalidZoneinfoDirectory, "#{zoneinfo_dir} is not a directory or doesn't contain a iso3166.tab file and a zone1970.tab or zone.tab file." end @zoneinfo_dir = zoneinfo_dir else @zoneinfo_dir, iso3166_tab_path, zone_tab_path = find_zoneinfo_dir unless @zoneinfo_dir && iso3166_tab_path && zone_tab_path raise ZoneinfoDirectoryNotFound, "None of the paths included in #{self.class.name}.search_path are valid zoneinfo directories." end end @zoneinfo_dir = File.expand_path(@zoneinfo_dir).freeze @timezone_identifiers = load_timezone_identifiers.freeze @countries = load_countries(iso3166_tab_path, zone_tab_path).freeze @country_codes = @countries.keys.sort!.freeze string_deduper = ConcurrentStringDeduper.new posix_tz_parser = PosixTimeZoneParser.new(string_deduper) @zoneinfo_reader = ZoneinfoReader.new(posix_tz_parser, string_deduper) end # Returns a frozen `Array` of all the available time zone identifiers. The # identifiers are sorted according to `String#<=>`. # # @return [Array] a frozen `Array` of all the available time zone # identifiers. def data_timezone_identifiers @timezone_identifiers end # Returns an empty `Array`. There is no information about linked/aliased # time zones in the zoneinfo files. When using {ZoneinfoDataSource}, every # time zone will be returned as a {DataTimezone}. # # @return [Array] an empty `Array`. def linked_timezone_identifiers [].freeze end # (see DataSource#to_s) def to_s "Zoneinfo DataSource: #{@zoneinfo_dir}" end # (see DataSource#inspect) def inspect "#<#{self.class}: #{@zoneinfo_dir}>" end protected # Returns a {TimezoneInfo} instance for the given time zone identifier. # The result will either be a {ConstantOffsetDataTimezoneInfo} or a # {TransitionsDataTimezoneInfo}. # # @param identifier [String] A time zone identifier. # @return [TimezoneInfo] a {TimezoneInfo} instance for the given time zone # identifier. # @raise [InvalidTimezoneIdentifier] if the time zone is not found, the # identifier is invalid, the zoneinfo file cannot be opened or the # zoneinfo file is not valid. def load_timezone_info(identifier) valid_identifier = validate_timezone_identifier(identifier) path = File.join(@zoneinfo_dir, valid_identifier) zoneinfo = begin @zoneinfo_reader.read(path) rescue Errno::EACCES, InvalidZoneinfoFile => e raise InvalidTimezoneIdentifier, "#{e.message.encode(Encoding::UTF_8)} (loading #{valid_identifier})" rescue Errno::EISDIR, Errno::ENAMETOOLONG, Errno::ENOENT, Errno::ENOTDIR raise InvalidTimezoneIdentifier, "Invalid identifier: #{valid_identifier}" end if zoneinfo.kind_of?(TimezoneOffset) ConstantOffsetDataTimezoneInfo.new(valid_identifier, zoneinfo) else TransitionsDataTimezoneInfo.new(valid_identifier, zoneinfo) end end # (see DataSource#load_country_info) def load_country_info(code) lookup_country_info(@countries, code) end private # Validates a zoneinfo directory and returns the paths to the iso3166.tab # and zone1970.tab or zone.tab files if valid. If the directory is not # valid, returns `nil`. # # The path to the iso3166.tab file may be overridden by passing in a path. # This is treated as either absolute or relative to the current working # directory. # # @param path [String] the path to a possible zoneinfo directory. # @param iso3166_tab_path [String] an optional path to an external # iso3166.tab file. # @return [Array] an `Array` containing the iso3166.tab and # zone.tab paths if the directory is valid, otherwise `nil`. def validate_zoneinfo_dir(path, iso3166_tab_path = nil) if File.directory?(path) if iso3166_tab_path return nil unless File.file?(iso3166_tab_path) else iso3166_tab_path = resolve_tab_path(path, ['iso3166.tab'], 'country.tab') return nil unless iso3166_tab_path end zone_tab_path = resolve_tab_path(path, ['zone1970.tab', 'zone.tab'], 'zone_sun.tab') return nil unless zone_tab_path [iso3166_tab_path, zone_tab_path] else nil end end # Attempts to resolve the path to a tab file given its standard names and # tab sub-directory name (as used on Solaris). # # @param zoneinfo_path [String] the path to a zoneinfo directory. # @param standard_names [Array] the standard names for the tab # file. # @param tab_name [String] the alternate name for the tab file to check in # the tab sub-directory. # @return [String] the path to the tab file. def resolve_tab_path(zoneinfo_path, standard_names, tab_name) standard_names.each do |standard_name| path = File.join(zoneinfo_path, standard_name) return path if File.file?(path) end path = File.join(zoneinfo_path, 'tab', tab_name) return path if File.file?(path) nil end # Finds a zoneinfo directory using {search_path} and # {alternate_iso3166_tab_search_path}. # # @return [Array] an `Array` containing the iso3166.tab and # zone.tab paths if a zoneinfo directory was found, otherwise `nil`. def find_zoneinfo_dir alternate_iso3166_tab_path = self.class.alternate_iso3166_tab_search_path.detect do |path| File.file?(path) end self.class.search_path.each do |path| # Try without the alternate_iso3166_tab_path first. iso3166_tab_path, zone_tab_path = validate_zoneinfo_dir(path) return path, iso3166_tab_path, zone_tab_path if iso3166_tab_path && zone_tab_path if alternate_iso3166_tab_path iso3166_tab_path, zone_tab_path = validate_zoneinfo_dir(path, alternate_iso3166_tab_path) return path, iso3166_tab_path, zone_tab_path if iso3166_tab_path && zone_tab_path end end # Not found. nil end # Scans @zoneinfo_dir and returns an `Array` of available time zone # identifiers. The result is sorted according to `String#<=>`. # # @return [Array] an `Array` containing all the time zone # identifiers found. def load_timezone_identifiers index = [] # Ignoring particular files: # +VERSION is included on Mac OS X. # leapseconds is a list of leap seconds. # localtime is the current local timezone (may be a link). # posix, posixrules and right are directories containing other versions of the zoneinfo files. # src is a directory containing the tzdata source included on Solaris. # timeconfig is a symlink included on Slackware. enum_timezones([], ['+VERSION', 'leapseconds', 'localtime', 'posix', 'posixrules', 'right', 'src', 'timeconfig']) do |identifier| index << identifier.join('/').freeze end index.sort! end # Recursively enumerate a directory of time zones. # # @param dir [Array] the directory to enumerate as an `Array` of # path components. # @param exclude [Array] file names to exclude when scanning # `dir`. # @yield [path] the path of each time zone file found is passed to # the block. # @yieldparam path [Array] the path of a time zone file as an # `Array` of path components. def enum_timezones(dir, exclude = [], &block) Dir.foreach(File.join(@zoneinfo_dir, *dir)) do |entry| begin entry.encode!(Encoding::UTF_8) rescue EncodingError next end unless entry =~ /\./ || exclude.include?(entry) entry.untaint path = dir + [entry] full_path = File.join(@zoneinfo_dir, *path) if File.directory?(full_path) enum_timezones(path, [], &block) elsif File.file?(full_path) yield path end end end end # Uses the iso3166.tab and zone1970.tab or zone.tab files to return a Hash # mapping country codes to CountryInfo instances. # # @param iso3166_tab_path [String] the path to the iso3166.tab file. # @param zone_tab_path [String] the path to the zone.tab file. # @return [Hash] a mapping from ISO 3166-1 alpha-2 # country codes to {CountryInfo} instances. def load_countries(iso3166_tab_path, zone_tab_path) # Handle standard 3 to 4 column zone.tab files as well as the 4 to 5 # column format used by Solaris. # # On Solaris, an extra column before the comment gives an optional # linked/alternate timezone identifier (or '-' if not set). # # Additionally, there is a section at the end of the file for timezones # covering regions. These are given lower-case "country" codes. The timezone # identifier column refers to a continent instead of an identifier. These # lines will be ignored by TZInfo. # # Since the last column is optional in both formats, testing for the # Solaris format is done in two passes. The first pass identifies if there # are any lines using 5 columns. # The first column is allowed to be a comma separated list of country # codes, as used in zone1970.tab (introduced in tzdata 2014f). # # The first country code in the comma-separated list is the country that # contains the city the zone identifier is based on. The first country # code on each line is considered to be primary with the others # secondary. # # The zones for each country are ordered primary first, then secondary. # Within the primary and secondary groups, the zones are ordered by their # order in the file. file_is_5_column = false zone_tab = [] file = File.read(zone_tab_path, external_encoding: Encoding::UTF_8, internal_encoding: Encoding::UTF_8) file.each_line do |line| line.chomp! if line =~ /\A([A-Z]{2}(?:,[A-Z]{2})*)\t(?:([+\-])(\d{2})(\d{2})([+\-])(\d{3})(\d{2})|([+\-])(\d{2})(\d{2})(\d{2})([+\-])(\d{3})(\d{2})(\d{2}))\t([^\t]+)(?:\t([^\t]+))?(?:\t([^\t]+))?\z/ codes = $1 if $2 latitude = dms_to_rational($2, $3, $4) longitude = dms_to_rational($5, $6, $7) else latitude = dms_to_rational($8, $9, $10, $11) longitude = dms_to_rational($12, $13, $14, $15) end zone_identifier = $16 column4 = $17 column5 = $18 file_is_5_column = true if column5 zone_tab << [codes.split(','.freeze), zone_identifier, latitude, longitude, column4, column5] end end string_deduper = StringDeduper.new primary_zones = {} secondary_zones = {} zone_tab.each do |codes, zone_identifier, latitude, longitude, column4, column5| description = file_is_5_column ? column5 : column4 description = string_deduper.dedupe(description) if description # Lookup the identifier in the timezone index, so that the same # String instance can be used (saving memory). begin zone_identifier = validate_timezone_identifier(zone_identifier) rescue InvalidTimezoneIdentifier # zone_identifier is not valid, dedupe and allow anyway. zone_identifier = string_deduper.dedupe(zone_identifier) end country_timezone = CountryTimezone.new(zone_identifier, latitude, longitude, description) # codes will always have at least one element (primary_zones[codes.first.freeze] ||= []) << country_timezone codes[1..-1].each do |code| (secondary_zones[code.freeze] ||= []) << country_timezone end end countries = {} file = File.read(iso3166_tab_path, external_encoding: Encoding::UTF_8, internal_encoding: Encoding::UTF_8) file.each_line do |line| line.chomp! # Handle both the two column alpha-2 and name format used in the tz # database as well as the 4 column alpha-2, alpha-3, numeric-3 and # name format used by FreeBSD and OpenBSD. if line =~ /\A([A-Z]{2})(?:\t[A-Z]{3}\t[0-9]{3})?\t(.+)\z/ code = $1 name = $2 zones = (primary_zones[code] || []) + (secondary_zones[code] || []) countries[code] = CountryInfo.new(code, name, zones) end end countries end # Converts degrees, minutes and seconds to a Rational. # # @param sign [String] `'-'` or `'+'`. # @param degrees [String] the number of degrees. # @param minutes [String] the number of minutes. # @param seconds [String] the number of seconds (optional). # @return [Rational] the result of converting from degrees, minutes and # seconds to a `Rational`. def dms_to_rational(sign, degrees, minutes, seconds = nil) degrees = degrees.to_i minutes = minutes.to_i sign = sign == '-'.freeze ? -1 : 1 if seconds Rational(sign * (degrees * 3600 + minutes * 60 + seconds.to_i), 3600) else Rational(sign * (degrees * 60 + minutes), 60) end end end end end tzinfo-2.0.4/lib/tzinfo/data_sources/posix_time_zone_parser.rb0000644000004100000410000001610613773021353024735 0ustar www-datawww-data# encoding: UTF-8 # frozen_string_literal: true require 'strscan' module TZInfo # Use send as a workaround for erroneous 'wrong number of arguments' errors # with JRuby 9.0.5.0 when calling methods with Java implementations. See #114. send(:using, UntaintExt) if TZInfo.const_defined?(:UntaintExt) module DataSources # An {InvalidPosixTimeZone} exception is raised if an invalid POSIX-style # time zone string is encountered. # # @private class InvalidPosixTimeZone < StandardError #:nodoc: end private_constant :InvalidPosixTimeZone # A parser for POSIX-style TZ strings used in zoneinfo files and specified # by tzfile.5 and tzset.3. # # @private class PosixTimeZoneParser #:nodoc: # Initializes a new {PosixTimeZoneParser}. # # @param string_deduper [StringDeduper] a {StringDeduper} instance to use # to dedupe abbreviations. def initialize(string_deduper) @string_deduper = string_deduper end # Parses a POSIX-style TZ string. # # @param tz_string [String] the string to parse. # @return [Object] either a {TimezoneOffset} for a constantly applied # offset or an {AnnualRules} instance representing the rules. # @raise [InvalidPosixTimeZone] if `tz_string` is not a `String`. # @raise [InvalidPosixTimeZone] if `tz_string` is is not valid. def parse(tz_string) raise InvalidPosixTimeZone unless tz_string.kind_of?(String) return nil if tz_string.empty? s = StringScanner.new(tz_string) check_scan(s, /([^-+,\d<][^-+,\d]*) | <([^>]+)>/x) std_abbrev = @string_deduper.dedupe((s[1] || s[2]).untaint) check_scan(s, /([-+]?\d+)(?::(\d+)(?::(\d+))?)?/) std_offset = get_offset_from_hms(s[1], s[2], s[3]) if s.scan(/([^-+,\d<][^-+,\d]*) | <([^>]+)>/x) dst_abbrev = @string_deduper.dedupe((s[1] || s[2]).untaint) if s.scan(/([-+]?\d+)(?::(\d+)(?::(\d+))?)?/) dst_offset = get_offset_from_hms(s[1], s[2], s[3]) else # POSIX is negative for ahead of UTC. dst_offset = std_offset - 3600 end dst_difference = std_offset - dst_offset start_rule = parse_rule(s, 'start') end_rule = parse_rule(s, 'end') raise InvalidPosixTimeZone, "Expected the end of a POSIX-style time zone string but found '#{s.rest}'." if s.rest? if start_rule.is_always_first_day_of_year? && start_rule.transition_at == 0 && end_rule.is_always_last_day_of_year? && end_rule.transition_at == 86400 + dst_difference # Constant daylight savings time. # POSIX is negative for ahead of UTC. TimezoneOffset.new(-std_offset, dst_difference, dst_abbrev) else AnnualRules.new( TimezoneOffset.new(-std_offset, 0, std_abbrev), TimezoneOffset.new(-std_offset, dst_difference, dst_abbrev), start_rule, end_rule) end elsif !s.rest? # Constant standard time. # POSIX is negative for ahead of UTC. TimezoneOffset.new(-std_offset, 0, std_abbrev) else raise InvalidPosixTimeZone, "Expected the end of a POSIX-style time zone string but found '#{s.rest}'." end end private # Parses a rule. # # @param s [StringScanner] the `StringScanner` to read the rule from. # @param type [String] the type of rule (either `'start'` or `'end'`). # @raise [InvalidPosixTimeZone] if the rule is not valid. # @return [TransitionRule] the parsed rule. def parse_rule(s, type) check_scan(s, /,(?: (?: J(\d+) ) | (\d+) | (?: M(\d+)\.(\d)\.(\d) ) )/x) julian_day_of_year = s[1] absolute_day_of_year = s[2] month = s[3] week = s[4] day_of_week = s[5] if s.scan(/\//) check_scan(s, /([-+]?\d+)(?::(\d+)(?::(\d+))?)?/) transition_at = get_seconds_after_midnight_from_hms(s[1], s[2], s[3]) else transition_at = 7200 end begin if julian_day_of_year JulianDayOfYearTransitionRule.new(julian_day_of_year.to_i, transition_at) elsif absolute_day_of_year AbsoluteDayOfYearTransitionRule.new(absolute_day_of_year.to_i, transition_at) elsif week == '5' LastDayOfMonthTransitionRule.new(month.to_i, day_of_week.to_i, transition_at) else DayOfMonthTransitionRule.new(month.to_i, week.to_i, day_of_week.to_i, transition_at) end rescue ArgumentError => e raise InvalidPosixTimeZone, "Invalid #{type} rule in POSIX-style time zone string: #{e}" end end # Returns an offset in seconds from hh:mm:ss values. The value can be # negative. -02:33:12 would represent 2 hours, 33 minutes and 12 seconds # ahead of UTC. # # @param h [String] the hours. # @param m [String] the minutes. # @param s [String] the seconds. # @return [Integer] the offset. # @raise [InvalidPosixTimeZone] if the mm and ss values are greater than # 59. def get_offset_from_hms(h, m, s) h = h.to_i m = m.to_i s = s.to_i raise InvalidPosixTimeZone, "Invalid minute #{m} in offset for POSIX-style time zone string." if m > 59 raise InvalidPosixTimeZone, "Invalid second #{s} in offset for POSIX-style time zone string." if s > 59 magnitude = (h.abs * 60 + m) * 60 + s h < 0 ? -magnitude : magnitude end # Returns the seconds from midnight from hh:mm:ss values. Hours can exceed # 24 for a time on the following day. Hours can be negative to subtract # hours from midnight on the given day. -02:33:12 represents 22:33:12 on # the prior day. # # @param h [String] the hour. # @param m [String] the minutes past the hour. # @param s [String] the seconds past the minute. # @return [Integer] the number of seconds after midnight. # @raise [InvalidPosixTimeZone] if the mm and ss values are greater than # 59. def get_seconds_after_midnight_from_hms(h, m, s) h = h.to_i m = m.to_i s = s.to_i raise InvalidPosixTimeZone, "Invalid minute #{m} in time for POSIX-style time zone string." if m > 59 raise InvalidPosixTimeZone, "Invalid second #{s} in time for POSIX-style time zone string." if s > 59 (h * 3600) + m * 60 + s end # Scans for a pattern and raises an exception if the pattern does not # match the input. # # @param s [StringScanner] the `StringScanner` to scan. # @param pattern [Regexp] the pattern to match. # @return [String] the result of the scan. # @raise [InvalidPosixTimeZone] if the pattern does not match the input. def check_scan(s, pattern) result = s.scan(pattern) raise InvalidPosixTimeZone, "Expected '#{s.rest}' to match #{pattern} in POSIX-style time zone string." unless result result end end private_constant :PosixTimeZoneParser end end tzinfo-2.0.4/lib/tzinfo/data_sources/zoneinfo_reader.rb0000644000004100000410000005165213773021353023324 0ustar www-datawww-data# encoding: UTF-8 # frozen_string_literal: true module TZInfo # Use send as a workaround for erroneous 'wrong number of arguments' errors # with JRuby 9.0.5.0 when calling methods with Java implementations. See #114. send(:using, UntaintExt) if TZInfo.const_defined?(:UntaintExt) module DataSources # An {InvalidZoneinfoFile} exception is raised if an attempt is made to load # an invalid zoneinfo file. class InvalidZoneinfoFile < StandardError end # Reads compiled zoneinfo TZif (\0, 2 or 3) files. class ZoneinfoReader #:nodoc: # The year to generate transitions up to. # # @private GENERATE_UP_TO = Time.now.utc.year + 100 private_constant :GENERATE_UP_TO # Initializes a new {ZoneinfoReader}. # # @param posix_tz_parser [PosixTimeZoneParser] a {PosixTimeZoneParser} # instance to use to parse POSIX-style TZ strings. # @param string_deduper [StringDeduper] a {StringDeduper} instance to use # to dedupe abbreviations. def initialize(posix_tz_parser, string_deduper) @posix_tz_parser = posix_tz_parser @string_deduper = string_deduper end # Reads a zoneinfo structure from the given path. Returns either a # {TimezoneOffset} that is constantly observed or an `Array` # {TimezoneTransition}s. # # @param file_path [String] the path of a zoneinfo file. # @return [Object] either a {TimezoneOffset} or an `Array` of # {TimezoneTransition}s. # @raise [SecurityError] if safe mode is enabled and `file_path` is # tainted. # @raise [InvalidZoneinfoFile] if `file_path`` does not refer to a valid # zoneinfo file. def read(file_path) File.open(file_path, 'rb') { |file| parse(file) } end private # Translates an unsigned 32-bit integer (as returned by unpack) to signed # 32-bit. # # @param long [Integer] an unsigned 32-bit integer. # @return [Integer] {long} translated to signed 32-bit. def make_signed_int32(long) long >= 0x80000000 ? long - 0x100000000 : long end # Translates a pair of unsigned 32-bit integers (as returned by unpack, # most significant first) to a signed 64-bit integer. # # @param high [Integer] the most significant 32-bits. # @param low [Integer] the least significant 32-bits. # @return [Integer] {high} and {low} combined and translated to signed # 64-bit. def make_signed_int64(high, low) unsigned = (high << 32) | low unsigned >= 0x8000000000000000 ? unsigned - 0x10000000000000000 : unsigned end # Reads the given number of bytes from the given file and checks that the # correct number of bytes could be read. # # @param file [IO] the file to read from. # @param bytes [Integer] the number of bytes to read. # @return [String] the bytes that were read. # @raise [InvalidZoneinfoFile] if the number of bytes available didn't # match the number requested. def check_read(file, bytes) result = file.read(bytes) unless result && result.length == bytes raise InvalidZoneinfoFile, "Expected #{bytes} bytes reading '#{file.path}', but got #{result ? result.length : 0} bytes" end result end # Zoneinfo files don't include the offset from standard time (std_offset) # for DST periods. Derive the base offset (base_utc_offset) where DST is # observed from either the previous or next non-DST period. # # @param transitions [Array] an `Array` of transition hashes. # @param offsets [Array] an `Array` of offset hashes. # @return [Integer] the index of the offset to be used prior to the first # transition. def derive_offsets(transitions, offsets) # The first non-DST offset (if there is one) is the offset observed # before the first transition. Fall back to the first DST offset if # there are no non-DST offsets. first_non_dst_offset_index = offsets.index {|o| !o[:is_dst] } first_offset_index = first_non_dst_offset_index || 0 return first_offset_index if transitions.empty? # Determine the base_utc_offset of the next non-dst offset at each transition. base_utc_offset_from_next = nil transitions.reverse_each do |transition| offset = offsets[transition[:offset]] if offset[:is_dst] transition[:base_utc_offset_from_next] = base_utc_offset_from_next if base_utc_offset_from_next else base_utc_offset_from_next = offset[:observed_utc_offset] end end base_utc_offset_from_previous = first_non_dst_offset_index ? offsets[first_non_dst_offset_index][:observed_utc_offset] : nil defined_offsets = {} transitions.each do |transition| offset_index = transition[:offset] offset = offsets[offset_index] observed_utc_offset = offset[:observed_utc_offset] if offset[:is_dst] base_utc_offset_from_next = transition[:base_utc_offset_from_next] difference_to_previous = (observed_utc_offset - (base_utc_offset_from_previous || observed_utc_offset)).abs difference_to_next = (observed_utc_offset - (base_utc_offset_from_next || observed_utc_offset)).abs base_utc_offset = if difference_to_previous == 3600 base_utc_offset_from_previous elsif difference_to_next == 3600 base_utc_offset_from_next elsif difference_to_previous > 0 && difference_to_next > 0 difference_to_previous < difference_to_next ? base_utc_offset_from_previous : base_utc_offset_from_next elsif difference_to_previous > 0 base_utc_offset_from_previous elsif difference_to_next > 0 base_utc_offset_from_next else # No difference, assume a 1 hour offset from standard time. observed_utc_offset - 3600 end if !offset[:base_utc_offset] offset[:base_utc_offset] = base_utc_offset defined_offsets[offset] = offset_index elsif offset[:base_utc_offset] != base_utc_offset # An earlier transition has already derived a different # base_utc_offset. Define a new offset or reuse an existing identically # defined offset. new_offset = offset.dup new_offset[:base_utc_offset] = base_utc_offset offset_index = defined_offsets[new_offset] unless offset_index offsets << new_offset offset_index = offsets.length - 1 defined_offsets[new_offset] = offset_index end transition[:offset] = offset_index end else base_utc_offset_from_previous = observed_utc_offset end end first_offset_index end # Determines if the offset from a transition matches the offset from a # rule. This is a looser match than equality, not requiring that the # base_utc_offset and std_offset both match (which have to be derived for # transitions, but are known for rules. # # @param offset [TimezoneOffset] an offset from a transition. # @param rule_offset [TimezoneOffset] an offset from a rule. # @return [Boolean] whether the offsets match. def offset_matches_rule?(offset, rule_offset) offset.observed_utc_offset == rule_offset.observed_utc_offset && offset.dst? == rule_offset.dst? && offset.abbreviation == rule_offset.abbreviation end # Apply the rules from the TZ string when there were no defined # transitions. Checks for a matching offset. Returns the rules-based # constant offset or generates transitions from 1970 until 100 years into # the future (at the time of loading zoneinfo_reader.rb). # # @param file [IO] the file being processed. # @param first_offset [TimezoneOffset] the first offset included in the # file that would normally apply without the rules. # @param rules [Object] a {TimezoneOffset} specifying a constant offset or # {AnnualRules} instance specfying transitions. # @return [Object] either a {TimezoneOffset} or an `Array` of # {TimezoneTransition}s. # @raise [InvalidZoneinfoFile] if the first offset does not match the # rules. def apply_rules_without_transitions(file, first_offset, rules) if rules.kind_of?(TimezoneOffset) unless offset_matches_rule?(first_offset, rules) raise InvalidZoneinfoFile, "Constant offset POSIX-style TZ string does not match constant offset in file '#{file.path}'." end rules else transitions = 1970.upto(GENERATE_UP_TO).flat_map {|y| rules.transitions(y) } first_transition = transitions[0] unless offset_matches_rule?(first_offset, first_transition.previous_offset) # Not transitioning from the designated first offset. if offset_matches_rule?(first_offset, first_transition.offset) # Skip an unnecessary transition to the first offset. transitions.shift else # The initial offset doesn't match the ongoing rules. Replace the # previous offset of the first transition. transitions[0] = TimezoneTransition.new(first_transition.offset, first_offset, first_transition.timestamp_value) end end transitions end end # Finds an offset that is equivalent to the one specified in the given # `Array`. Matching is performed with {TimezoneOffset#==}. # # @param offsets [Array] an `Array` to search. # @param offset [TimezoneOffset] the offset to search for. # @return [TimezoneOffset] the matching offset from `offsets` or `nil` # if not found. def find_existing_offset(offsets, offset) offsets.find {|o| o == offset } end # Returns a new AnnualRules instance with standard and daylight savings # offsets replaced with equivalents from an array. This reduces the memory # requirement for loaded time zones by reusing offsets for rule-generated # transitions. # # @param offsets [Array] an `Array` to search for # equivalent offsets. # @param annual_rules [AnnualRules] the {AnnualRules} instance to check. # @return [AnnualRules] either a new {AnnualRules} instance with either # the {AnnualRules#std_offset std_offset} or {AnnualRules#dst_offset # dst_offset} replaced, or the original instance if no equivalent for # either {AnnualRules#std_offset std_offset} or {AnnualRules#dst_offset # dst_offset} could be found. def replace_with_existing_offsets(offsets, annual_rules) existing_std_offset = find_existing_offset(offsets, annual_rules.std_offset) existing_dst_offset = find_existing_offset(offsets, annual_rules.dst_offset) if existing_std_offset || existing_dst_offset AnnualRules.new(existing_std_offset || annual_rules.std_offset, existing_dst_offset || annual_rules.dst_offset, annual_rules.dst_start_rule, annual_rules.dst_end_rule) else annual_rules end end # Validates the offset indicated to be observed by the rules before the # first generated transition against the offset of the last defined # transition. # # Fix the last defined transition if it differ on just base/std offsets # (which are derived). Raise an error if the observed UTC offset or # abbreviations differ. # # @param file [IO] the file being processed. # @param last_defined [TimezoneTransition] the last defined transition in # the file. # @param first_rule_offset [TimezoneOffset] the offset the rules indicate # is observed prior to the first rules generated transition. # @return [TimezoneTransition] the last defined transition (either the # original instance or a replacement). # @raise [InvalidZoneinfoFile] if the offset of {last_defined} and # {first_rule_offset} do not match. def validate_and_fix_last_defined_transition_offset(file, last_defined, first_rule_offset) offset_of_last_defined = last_defined.offset if offset_of_last_defined == first_rule_offset last_defined else if offset_matches_rule?(offset_of_last_defined, first_rule_offset) # The same overall offset, but differing in the base or std # offset (which are derived). Correct by using the rule. TimezoneTransition.new(first_rule_offset, last_defined.previous_offset, last_defined.timestamp_value) else raise InvalidZoneinfoFile, "The first offset indicated by the POSIX-style TZ string did not match the final defined offset in file '#{file.path}'." end end end # Apply the rules from the TZ string when there were defined # transitions. Checks for a matching offset with the last transition. # Redefines the last transition if required and if the rules don't # specific a constant offset, generates transitions until 100 years into # the future (at the time of loading zoneinfo_reader.rb). # # @param file [IO] the file being processed. # @param transitions [Array] the defined transitions. # @param offsets [Array] the offsets used by the defined # transitions. # @param rules [Object] a {TimezoneOffset} specifying a constant offset or # {AnnualRules} instance specfying transitions. # @raise [InvalidZoneinfoFile] if the first offset does not match the # rules. # @raise [InvalidZoneinfoFile] if the previous offset of the first # generated transition does not match the offset of the last defined # transition. def apply_rules_with_transitions(file, transitions, offsets, rules) last_defined = transitions[-1] if rules.kind_of?(TimezoneOffset) transitions[-1] = validate_and_fix_last_defined_transition_offset(file, last_defined, rules) else last_year = last_defined.local_end_at.to_time.year if last_year <= GENERATE_UP_TO rules = replace_with_existing_offsets(offsets, rules) generated = rules.transitions(last_year).find_all do |t| t.timestamp_value > last_defined.timestamp_value && !offset_matches_rule?(last_defined.offset, t.offset) end generated += (last_year + 1).upto(GENERATE_UP_TO).flat_map {|y| rules.transitions(y) } unless generated.empty? transitions[-1] = validate_and_fix_last_defined_transition_offset(file, last_defined, generated[0].previous_offset) transitions.concat(generated) end end end end # Parses a zoneinfo file and returns either a {TimezoneOffset} that is # constantly observed or an `Array` of {TimezoneTransition}s. # # @param file [IO] the file to be read. # @return [Object] either a {TimezoneOffset} or an `Array` of # {TimezoneTransition}s. # @raise [InvalidZoneinfoFile] if the file is not a valid zoneinfo file. def parse(file) magic, version, ttisutccnt, ttisstdcnt, leapcnt, timecnt, typecnt, charcnt = check_read(file, 44).unpack('a4 a x15 NNNNNN') if magic != 'TZif' raise InvalidZoneinfoFile, "The file '#{file.path}' does not start with the expected header." end if version == '2' || version == '3' # Skip the first 32-bit section and read the header of the second 64-bit section file.seek(timecnt * 5 + typecnt * 6 + charcnt + leapcnt * 8 + ttisstdcnt + ttisutccnt, IO::SEEK_CUR) prev_version = version magic, version, ttisutccnt, ttisstdcnt, leapcnt, timecnt, typecnt, charcnt = check_read(file, 44).unpack('a4 a x15 NNNNNN') unless magic == 'TZif' && (version == prev_version) raise InvalidZoneinfoFile, "The file '#{file.path}' contains an invalid 64-bit section header." end using_64bit = true elsif version != '3' && version != '2' && version != "\0" raise InvalidZoneinfoFile, "The file '#{file.path}' contains a version of the zoneinfo format that is not currently supported." else using_64bit = false end unless leapcnt == 0 raise InvalidZoneinfoFile, "The file '#{file.path}' contains leap second data. TZInfo requires zoneinfo files that omit leap seconds." end transitions = if using_64bit timecnt.times.map do |i| high, low = check_read(file, 8).unpack('NN'.freeze) transition_time = make_signed_int64(high, low) {at: transition_time} end else timecnt.times.map do |i| transition_time = make_signed_int32(check_read(file, 4).unpack('N'.freeze)[0]) {at: transition_time} end end check_read(file, timecnt).unpack('C*'.freeze).each_with_index do |localtime_type, i| raise InvalidZoneinfoFile, "Invalid offset referenced by transition in file '#{file.path}'." if localtime_type >= typecnt transitions[i][:offset] = localtime_type end offsets = typecnt.times.map do |i| gmtoff, isdst, abbrind = check_read(file, 6).unpack('NCC'.freeze) gmtoff = make_signed_int32(gmtoff) isdst = isdst == 1 {observed_utc_offset: gmtoff, is_dst: isdst, abbr_index: abbrind} end abbrev = check_read(file, charcnt) if using_64bit # Skip to the POSIX-style TZ string. file.seek(ttisstdcnt + ttisutccnt, IO::SEEK_CUR) # + leapcnt * 8, but leapcnt is checked above and guaranteed to be 0. tz_string_start = check_read(file, 1) raise InvalidZoneinfoFile, "Expected newline starting POSIX-style TZ string in file '#{file.path}'." unless tz_string_start == "\n" tz_string = file.readline("\n").force_encoding(Encoding::UTF_8) raise InvalidZoneinfoFile, "Expected newline ending POSIX-style TZ string in file '#{file.path}'." unless tz_string.chomp!("\n") begin rules = @posix_tz_parser.parse(tz_string) rescue InvalidPosixTimeZone => e raise InvalidZoneinfoFile, "Failed to parse POSIX-style TZ string in file '#{file.path}': #{e}" end else rules = nil end # Derive the offsets from standard time (std_offset). first_offset_index = derive_offsets(transitions, offsets) offsets = offsets.map do |o| observed_utc_offset = o[:observed_utc_offset] base_utc_offset = o[:base_utc_offset] if base_utc_offset # DST offset with base_utc_offset derived by derive_offsets. std_offset = observed_utc_offset - base_utc_offset elsif o[:is_dst] # DST offset unreferenced by a transition (offset in use before the # first transition). No derived base UTC offset, so assume 1 hour # DST. base_utc_offset = observed_utc_offset - 3600 std_offset = 3600 else # Non-DST offset. base_utc_offset = observed_utc_offset std_offset = 0 end abbrev_start = o[:abbr_index] raise InvalidZoneinfoFile, "Abbreviation index is out of range in file '#{file.path}'." unless abbrev_start < abbrev.length abbrev_end = abbrev.index("\0", abbrev_start) raise InvalidZoneinfoFile, "Missing abbreviation null terminator in file '#{file.path}'." unless abbrev_end abbr = @string_deduper.dedupe(abbrev[abbrev_start...abbrev_end].force_encoding(Encoding::UTF_8).untaint) TimezoneOffset.new(base_utc_offset, std_offset, abbr) end first_offset = offsets[first_offset_index] if transitions.empty? if rules apply_rules_without_transitions(file, first_offset, rules) else first_offset end else previous_offset = first_offset previous_at = nil transitions = transitions.map do |t| offset = offsets[t[:offset]] at = t[:at] raise InvalidZoneinfoFile, "Transition at #{at} is not later than the previous transition at #{previous_at} in file '#{file.path}'." if previous_at && previous_at >= at tt = TimezoneTransition.new(offset, previous_offset, at) previous_offset = offset previous_at = at tt end apply_rules_with_transitions(file, transitions, offsets, rules) if rules transitions end end end private_constant :ZoneinfoReader end end tzinfo-2.0.4/lib/tzinfo/data_sources/data_timezone_info.rb0000644000004100000410000001024013773021353023775 0ustar www-datawww-data# encoding: UTF-8 # frozen_string_literal: true module TZInfo module DataSources # The base class for time zones defined as either a series of transitions # ({TransitionsDataTimezoneInfo}) or a constantly observed offset # ({ConstantOffsetDataTimezoneInfo}). # # @abstract Data sources return instances of {DataTimezoneInfo} subclasses. class DataTimezoneInfo < TimezoneInfo # @param timestamp [Timestamp] a {Timestamp} with a specified # {Timestamp#utc_offset utc_offset}. # @return [TimezonePeriod] the {TimezonePeriod} observed at the time # specified by `timestamp`. # @raise [ArgumentError] may be raised if `timestamp` is `nil` or does not # have a specified {Timestamp#utc_offset utc_offset}. def period_for(timestamp) raise_not_implemented('period_for') end # Returns an `Array` containing the {TimezonePeriod TimezonePeriods} that # could be observed at the local time specified by `local_timestamp`. The # results are are ordered by increasing UTC start date. An empty `Array` # is returned if no periods are found for the given local time. # # @param local_timestamp [Timestamp] a {Timestamp} representing a local # time - must have an unspecified {Timestamp#utc_offset utc_offset}. # @return [Array] an `Array` containing the # {TimezonePeriod TimezonePeriods} that could be observed at the local # time specified by `local_timestamp`. # @raise [ArgumentError] may be raised if `local_timestamp` is `nil`, or # has a specified {Timestamp#utc_offset utc_offset}. def periods_for_local(local_timestamp) raise_not_implemented('periods_for_local') end # Returns an `Array` of {TimezoneTransition} instances representing the # times where the UTC offset of the time zone changes. # # Transitions are returned up to a given {Timestamp} (`to_timestamp`). # # A from {Timestamp} may also be supplied using the `from_timestamp` # parameter. If `from_timestamp` is specified, only transitions from that # time onwards will be returned. # # Comparisons with `to_timestamp` are exclusive. Comparisons with # `from_timestamp` are inclusive. If a transition falls precisely on # `to_timestamp`, it will be excluded. If a transition falls on # `from_timestamp`, it will be included. # # Transitions returned are ordered by when they occur, from earliest to # latest. # # @param to_timestamp [Timestamp] a {Timestamp} with a specified # {Timestamp#utc_offset utc_offset}. Transitions are returned if they # occur before this time. # @param from_timestamp [Timestamp] an optional {Timestamp} with a # specified {Timestamp#utc_offset utc_offset}. If specified, transitions # are returned if they occur at or after this time. # @return [Array] an `Array` of {TimezoneTransition} # instances representing the times where the UTC offset of the time zone # changes. # @raise [ArgumentError] may be raised if `to_timestamp` is `nil` or does # not have a specified {Timestamp#utc_offset utc_offset}. # @raise [ArgumentError] may be raised if `from_timestamp` is specified # but does not have a specified {Timestamp#utc_offset utc_offset}. # @raise [ArgumentError] may be raised if `from_timestamp` is specified # but is not earlier than or at the same time as `to_timestamp`. def transitions_up_to(to_timestamp, from_timestamp = nil) raise_not_implemented('transitions_up_to') end # @return [DataTimezone] a new {DataTimezone} instance for the time zone # represented by this {DataTimezoneInfo}. def create_timezone DataTimezone.new(self) end private # Raises a {NotImplementedError} to indicate that the base class is # incorrectly being used directly. # # raise [NotImplementedError] always. def raise_not_implemented(method_name) raise NotImplementedError, "Subclasses must override #{method_name}" end end end end tzinfo-2.0.4/lib/tzinfo/data_sources/timezone_info.rb0000644000004100000410000000264713773021353023020 0ustar www-datawww-data# encoding: UTF-8 # frozen_string_literal: true module TZInfo module DataSources # Represents a time zone defined by a data source. # # @abstract Data sources return instances of {TimezoneInfo} subclasses. class TimezoneInfo # @return [String] the identifier of the time zone. attr_reader :identifier # Initializes a new TimezoneInfo. The passed in `identifier` instance will # be frozen. # # @param identifier [String] the identifier of the time zone. # @raise [ArgumentError] if `identifier` is `nil`. def initialize(identifier) raise ArgumentError, 'identifier must be specified' unless identifier @identifier = identifier.freeze end # @return [String] the internal object state as a programmer-readable # `String`. def inspect "#<#{self.class}: #@identifier>" end # @return [Timezone] a new {Timezone} instance for the time zone # represented by this {TimezoneInfo}. def create_timezone raise_not_implemented('create_timezone') end private # Raises a {NotImplementedError}. # # @param method_name [String] the name of the method that must be # overridden. # @raise NotImplementedError always. def raise_not_implemented(method_name) raise NotImplementedError, "Subclasses must override #{method_name}" end end end end tzinfo-2.0.4/lib/tzinfo/data_sources/ruby_data_source.rb0000644000004100000410000001172413773021353023501 0ustar www-datawww-data# encoding: UTF-8 # frozen_string_literal: true module TZInfo # Use send as a workaround for erroneous 'wrong number of arguments' errors # with JRuby 9.0.5.0 when calling methods with Java implementations. See #114. send(:using, UntaintExt) if TZInfo.const_defined?(:UntaintExt) module DataSources # A {TZInfoDataNotFound} exception is raised if the tzinfo-data gem could # not be found (i.e. `require 'tzinfo/data'` failed) when selecting the Ruby # data source. class TZInfoDataNotFound < StandardError end # A DataSource implementation that loads data from the set of Ruby modules # included in the tzinfo-data gem. # # TZInfo will use {RubyDataSource} by default if the tzinfo-data gem # is available on the load path. It can also be selected by calling # {DataSource.set} as follows: # # TZInfo::DataSource.set(:ruby) class RubyDataSource < DataSource # (see DataSource#data_timezone_identifiers) attr_reader :data_timezone_identifiers # (see DataSource#linked_timezone_identifiers) attr_reader :linked_timezone_identifiers # (see DataSource#country_codes) attr_reader :country_codes # Initializes a new {RubyDataSource} instance. # # @raise [TZInfoDataNotFound] if the tzinfo-data gem could not be found # (i.e. `require 'tzinfo/data'` failed). def initialize super begin require('tzinfo/data') rescue LoadError raise TZInfoDataNotFound, "The tzinfo-data gem could not be found (require 'tzinfo/data' failed)." end if TZInfo::Data.const_defined?(:LOCATION) # Format 2 @base_path = File.join(TZInfo::Data::LOCATION, 'tzinfo', 'data') else # Format 1 data_file = File.join('', 'tzinfo', 'data.rb') path = $".reverse_each.detect {|p| p.end_with?(data_file) } if path @base_path = File.join(File.dirname(path), 'data').untaint else @base_path = 'tzinfo/data' end end require_index('timezones') require_index('countries') @data_timezone_identifiers = Data::Indexes::Timezones.data_timezones @linked_timezone_identifiers = Data::Indexes::Timezones.linked_timezones @countries = Data::Indexes::Countries.countries @country_codes = @countries.keys.sort!.freeze end # (see DataSource#to_s) def to_s "Ruby DataSource: #{version_info}" end # (see DataSource#inspect) def inspect "#" end protected # Returns a {TimezoneInfo} instance for the given time zone identifier. # The result will either be a {ConstantOffsetDataTimezoneInfo}, a # {TransitionsDataTimezoneInfo} or a {LinkedTimezoneInfo} depending on the # type of time zone. # # @param identifier [String] A time zone identifier. # @return [TimezoneInfo] a {TimezoneInfo} instance for the given time zone # identifier. # @raise [InvalidTimezoneIdentifier] if the time zone is not found or the # identifier is invalid. def load_timezone_info(identifier) valid_identifier = validate_timezone_identifier(identifier) split_identifier = valid_identifier.gsub(/-/, '__m__').gsub(/\+/, '__p__').split('/') begin require_definition(split_identifier) m = Data::Definitions split_identifier.each {|part| m = m.const_get(part) } m.get rescue LoadError, NameError => e raise InvalidTimezoneIdentifier, "#{e.message.encode(Encoding::UTF_8)} (loading #{valid_identifier})" end end # (see DataSource#load_country_info) def load_country_info(code) lookup_country_info(@countries, code) end private # Requires a zone definition by its identifier (split on /). # # @param identifier [Array] the component parts of a time zone # identifier (split on /). This must have already been validated. def require_definition(identifier) require_data(*(['definitions'] + identifier)) end # Requires an index by its name. # # @param name [String] an index name. def require_index(name) require_data(*['indexes', name]) end # Requires a file from tzinfo/data. # # @param file [Array] a relative path to a file to be required. def require_data(*file) require(File.join(@base_path, *file)) end # @return [String] a `String` containing TZInfo::Data version infomation # for inclusion in the #to_s and #inspect output. def version_info # The TZInfo::Data::VERSION constant is only available from v1.2014.8 # onwards. "tzdb v#{TZInfo::Data::Version::TZDATA}#{TZInfo::Data.const_defined?(:VERSION) ? ", tzinfo-data v#{TZInfo::Data::VERSION}" : ''}" end end end end tzinfo-2.0.4/lib/tzinfo/data_sources.rb0000644000004100000410000000022713773021353020143 0ustar www-datawww-data# encoding: UTF-8 module TZInfo # {DataSource} implementations and classes used by {DataSource} # implementations. module DataSources end end tzinfo-2.0.4/lib/tzinfo/transition_rule.rb0000644000004100000410000004063013773021353020712 0ustar www-datawww-data# encoding: UTF-8 # frozen_string_literal: true module TZInfo # Base class for rules definining the transition between standard and daylight # savings time. # # @abstract # @private class TransitionRule #:nodoc: # Returns the number of seconds after midnight local time on the day # identified by the rule at which the transition occurs. Can be negative to # denote a time on the prior day. Can be greater than or equal to 86,400 to # denote a time of the following day. # # @return [Integer] the time in seconds after midnight local time at which # the transition occurs. attr_reader :transition_at # Initializes a new {TransitionRule}. # # @param transition_at [Integer] the time in seconds after midnight local # time at which the transition occurs. # @raise [ArgumentError] if `transition_at` is not an `Integer`. def initialize(transition_at) raise ArgumentError, 'Invalid transition_at' unless transition_at.kind_of?(Integer) @transition_at = transition_at end # Calculates the time of the transition from a given offset on a given year. # # @param offset [TimezoneOffset] the current offset at the time the rule # will transition. # @param year [Integer] the year in which the transition occurs (local # time). # @return [TimestampWithOffset] the time at which the transition occurs. def at(offset, year) day = get_day(offset, year) TimestampWithOffset.set_timezone_offset(Timestamp.for(day + @transition_at), offset) end # Determines if this {TransitionRule} is equal to another instance. # # @param r [Object] the instance to test for equality. # @return [Boolean] `true` if `r` is a {TransitionRule} with the same # {transition_at} as this {TransitionRule}, otherwise `false`. def ==(r) r.kind_of?(TransitionRule) && @transition_at == r.transition_at end alias eql? == # @return [Integer] a hash based on {hash_args} (defaulting to # {transition_at}). def hash hash_args.hash end protected # @return [Array] an `Array` of parameters that will influence the output of # {hash}. def hash_args [@transition_at] end end private_constant :TransitionRule # A base class for transition rules that activate based on an integer day of # the year. # # @abstract # @private class DayOfYearTransitionRule < TransitionRule #:nodoc: # Initializes a new {DayOfYearTransitionRule}. # # @param day [Integer] the day of the year on which the transition occurs. # The precise meaning is defined by subclasses. # @param transition_at [Integer] the time in seconds after midnight local # time at which the transition occurs. # @raise [ArgumentError] if `transition_at` is not an `Integer`. # @raise [ArgumentError] if `day` is not an `Integer`. def initialize(day, transition_at) super(transition_at) raise ArgumentError, 'Invalid day' unless day.kind_of?(Integer) @seconds = day * 86400 end # Determines if this {DayOfYearTransitionRule} is equal to another instance. # # @param r [Object] the instance to test for equality. # @return [Boolean] `true` if `r` is a {DayOfYearTransitionRule} with the # same {transition_at} and day as this {DayOfYearTransitionRule}, # otherwise `false`. def ==(r) super(r) && r.kind_of?(DayOfYearTransitionRule) && @seconds == r.seconds end alias eql? == protected # @return [Integer] the day multipled by the number of seconds in a day. attr_reader :seconds # (see TransitionRule#hash_args) def hash_args [@seconds] + super end end private_constant :DayOfYearTransitionRule # Defines transitions that occur on the zero-based nth day of the year. # # Day 0 is 1 January. # # Leap days are counted. Day 59 will be 29 February on a leap year and 1 March # on a non-leap year. Day 365 will be 31 December on a leap year and 1 January # the following year on a non-leap year. # # @private class AbsoluteDayOfYearTransitionRule < DayOfYearTransitionRule #:nodoc: # Initializes a new {AbsoluteDayOfYearTransitionRule}. # # @param day [Integer] the zero-based day of the year on which the # transition occurs (0 to 365 inclusive). # @param transition_at [Integer] the time in seconds after midnight local # time at which the transition occurs. # @raise [ArgumentError] if `transition_at` is not an `Integer`. # @raise [ArgumentError] if `day` is not an `Integer`. # @raise [ArgumentError] if `day` is less than 0 or greater than 365. def initialize(day, transition_at = 0) super(day, transition_at) raise ArgumentError, 'Invalid day' unless day >= 0 && day <= 365 end # @return [Boolean] `true` if the day specified by this transition is the # first in the year (a day number of 0), otherwise `false`. def is_always_first_day_of_year? seconds == 0 end # @return [Boolean] `false`. def is_always_last_day_of_year? false end # Determines if this {AbsoluteDayOfYearTransitionRule} is equal to another # instance. # # @param r [Object] the instance to test for equality. # @return [Boolean] `true` if `r` is a {AbsoluteDayOfYearTransitionRule} # with the same {transition_at} and day as this # {AbsoluteDayOfYearTransitionRule}, otherwise `false`. def ==(r) super(r) && r.kind_of?(AbsoluteDayOfYearTransitionRule) end alias eql? == protected # Returns a `Time` representing midnight local time on the day specified by # the rule for the given offset and year. # # @param offset [TimezoneOffset] the current offset at the time of the # transition. # @param year [Integer] the year in which the transition occurs. # @return [Time] midnight local time on the day specified by the rule for # the given offset and year. def get_day(offset, year) Time.new(year, 1, 1, 0, 0, 0, offset.observed_utc_offset) + seconds end # (see TransitionRule#hash_args) def hash_args [AbsoluteDayOfYearTransitionRule] + super end end # Defines transitions that occur on the one-based nth Julian day of the year. # # Leap days are not counted. Day 1 is 1 January. Day 60 is always 1 March. # Day 365 is always 31 December. # # @private class JulianDayOfYearTransitionRule < DayOfYearTransitionRule #:nodoc: # The 60 days in seconds. LEAP = 60 * 86400 private_constant :LEAP # The length of a non-leap year in seconds. YEAR = 365 * 86400 private_constant :YEAR # Initializes a new {JulianDayOfYearTransitionRule}. # # @param day [Integer] the one-based Julian day of the year on which the # transition occurs (1 to 365 inclusive). # @param transition_at [Integer] the time in seconds after midnight local # time at which the transition occurs. # @raise [ArgumentError] if `transition_at` is not an `Integer`. # @raise [ArgumentError] if `day` is not an `Integer`. # @raise [ArgumentError] if `day` is less than 1 or greater than 365. def initialize(day, transition_at = 0) super(day, transition_at) raise ArgumentError, 'Invalid day' unless day >= 1 && day <= 365 end # @return [Boolean] `true` if the day specified by this transition is the # first in the year (a day number of 1), otherwise `false`. def is_always_first_day_of_year? seconds == 86400 end # @return [Boolean] `true` if the day specified by this transition is the # last in the year (a day number of 365), otherwise `false`. def is_always_last_day_of_year? seconds == YEAR end # Determines if this {JulianDayOfYearTransitionRule} is equal to another # instance. # # @param r [Object] the instance to test for equality. # @return [Boolean] `true` if `r` is a {JulianDayOfYearTransitionRule} with # the same {transition_at} and day as this # {JulianDayOfYearTransitionRule}, otherwise `false`. def ==(r) super(r) && r.kind_of?(JulianDayOfYearTransitionRule) end alias eql? == protected # Returns a `Time` representing midnight local time on the day specified by # the rule for the given offset and year. # # @param offset [TimezoneOffset] the current offset at the time of the # transition. # @param year [Integer] the year in which the transition occurs. # @return [Time] midnight local time on the day specified by the rule for # the given offset and year. def get_day(offset, year) # Returns 1 March on non-leap years. leap = Time.new(year, 2, 29, 0, 0, 0, offset.observed_utc_offset) diff = seconds - LEAP diff += 86400 if diff >= 0 && leap.mday == 29 leap + diff end # (see TransitionRule#hash_args) def hash_args [JulianDayOfYearTransitionRule] + super end end private_constant :JulianDayOfYearTransitionRule # A base class for rules that transition on a particular day of week of a # given week (subclasses specify which week of the month). # # @abstract # @private class DayOfWeekTransitionRule < TransitionRule #:nodoc: # Initializes a new {DayOfWeekTransitionRule}. # # @param month [Integer] the month of the year when the transition occurs. # @param day_of_week [Integer] the day of the week when the transition # occurs. 0 is Sunday, 6 is Saturday. # @param transition_at [Integer] the time in seconds after midnight local # time at which the transition occurs. # @raise [ArgumentError] if `transition_at` is not an `Integer`. # @raise [ArgumentError] if `month` is not an `Integer`. # @raise [ArgumentError] if `month` is less than 1 or greater than 12. # @raise [ArgumentError] if `day_of_week` is not an `Integer`. # @raise [ArgumentError] if `day_of_week` is less than 0 or greater than 6. def initialize(month, day_of_week, transition_at) super(transition_at) raise ArgumentError, 'Invalid month' unless month.kind_of?(Integer) && month >= 1 && month <= 12 raise ArgumentError, 'Invalid day_of_week' unless day_of_week.kind_of?(Integer) && day_of_week >= 0 && day_of_week <= 6 @month = month @day_of_week = day_of_week end # @return [Boolean] `false`. def is_always_first_day_of_year? false end # @return [Boolean] `false`. def is_always_last_day_of_year? false end # Determines if this {DayOfWeekTransitionRule} is equal to another # instance. # # @param r [Object] the instance to test for equality. # @return [Boolean] `true` if `r` is a {DayOfWeekTransitionRule} with the # same {transition_at}, month and day of week as this # {DayOfWeekTransitionRule}, otherwise `false`. def ==(r) super(r) && r.kind_of?(DayOfWeekTransitionRule) && @month == r.month && @day_of_week == r.day_of_week end alias eql? == protected # @return [Integer] the month of the year (1 to 12). attr_reader :month # @return [Integer] the day of the week (0 to 6 for Sunday to Monday). attr_reader :day_of_week # (see TransitionRule#hash_args) def hash_args [@month, @day_of_week] + super end end private_constant :DayOfWeekTransitionRule # A rule that transitions on the nth occurrence of a particular day of week # of a calendar month. # # @private class DayOfMonthTransitionRule < DayOfWeekTransitionRule #:nodoc: # Initializes a new {DayOfMonthTransitionRule}. # # @param month [Integer] the month of the year when the transition occurs. # @param week [Integer] the week of the month when the transition occurs (1 # to 4). # @param day_of_week [Integer] the day of the week when the transition # occurs. 0 is Sunday, 6 is Saturday. # @param transition_at [Integer] the time in seconds after midnight local # time at which the transition occurs. # @raise [ArgumentError] if `transition_at` is not an `Integer`. # @raise [ArgumentError] if `month` is not an `Integer`. # @raise [ArgumentError] if `month` is less than 1 or greater than 12. # @raise [ArgumentError] if `week` is not an `Integer`. # @raise [ArgumentError] if `week` is less than 1 or greater than 4. # @raise [ArgumentError] if `day_of_week` is not an `Integer`. # @raise [ArgumentError] if `day_of_week` is less than 0 or greater than 6. def initialize(month, week, day_of_week, transition_at = 0) super(month, day_of_week, transition_at) raise ArgumentError, 'Invalid week' unless week.kind_of?(Integer) && week >= 1 && week <= 4 @offset_start = (week - 1) * 7 + 1 end # Determines if this {DayOfMonthTransitionRule} is equal to another # instance. # # @param r [Object] the instance to test for equality. # @return [Boolean] `true` if `r` is a {DayOfMonthTransitionRule} with the # same {transition_at}, month, week and day of week as this # {DayOfMonthTransitionRule}, otherwise `false`. def ==(r) super(r) && r.kind_of?(DayOfMonthTransitionRule) && @offset_start == r.offset_start end alias eql? == protected # @return [Integer] the day the week starts on for a month starting on a # Sunday. attr_reader :offset_start # Returns a `Time` representing midnight local time on the day specified by # the rule for the given offset and year. # # @param offset [TimezoneOffset] the current offset at the time of the # transition. # @param year [Integer] the year in which the transition occurs. # @return [Time] midnight local time on the day specified by the rule for # the given offset and year. def get_day(offset, year) candidate = Time.new(year, month, @offset_start, 0, 0, 0, offset.observed_utc_offset) diff = day_of_week - candidate.wday if diff < 0 candidate + (7 + diff) * 86400 elsif diff > 0 candidate + diff * 86400 else candidate end end # (see TransitionRule#hash_args) def hash_args [@offset_start] + super end end private_constant :DayOfMonthTransitionRule # A rule that transitions on the last occurrence of a particular day of week # of a calendar month. # # @private class LastDayOfMonthTransitionRule < DayOfWeekTransitionRule #:nodoc: # Initializes a new {LastDayOfMonthTransitionRule}. # # @param month [Integer] the month of the year when the transition occurs. # @param day_of_week [Integer] the day of the week when the transition # occurs. 0 is Sunday, 6 is Saturday. # @param transition_at [Integer] the time in seconds after midnight local # time at which the transition occurs. # @raise [ArgumentError] if `transition_at` is not an `Integer`. # @raise [ArgumentError] if `month` is not an `Integer`. # @raise [ArgumentError] if `month` is less than 1 or greater than 12. # @raise [ArgumentError] if `day_of_week` is not an `Integer`. # @raise [ArgumentError] if `day_of_week` is less than 0 or greater than 6. def initialize(month, day_of_week, transition_at = 0) super(month, day_of_week, transition_at) end # Determines if this {LastDayOfMonthTransitionRule} is equal to another # instance. # # @param r [Object] the instance to test for equality. # @return [Boolean] `true` if `r` is a {LastDayOfMonthTransitionRule} with # the same {transition_at}, month and day of week as this # {LastDayOfMonthTransitionRule}, otherwise `false`. def ==(r) super(r) && r.kind_of?(LastDayOfMonthTransitionRule) end alias eql? == protected # Returns a `Time` representing midnight local time on the day specified by # the rule for the given offset and year. # # @param offset [TimezoneOffset] the current offset at the time of the # transition. # @param year [Integer] the year in which the transition occurs. # @return [Time] midnight local time on the day specified by the rule for # the given offset and year. def get_day(offset, year) next_month = month + 1 if next_month == 13 year += 1 next_month = 1 end candidate = Time.new(year, next_month, 1, 0, 0, 0, offset.observed_utc_offset) - 86400 diff = candidate.wday - day_of_week if diff < 0 candidate - (diff + 7) * 86400 elsif diff > 0 candidate - diff * 86400 else candidate end end end private_constant :LastDayOfMonthTransitionRule end tzinfo-2.0.4/lib/tzinfo.rb0000644000004100000410000000526113773021353015472 0ustar www-datawww-data# encoding: UTF-8 # frozen_string_literal: true # The top level module for TZInfo. module TZInfo end # Object#untaint is a deprecated no-op in Ruby >= 2.7 and will be removed in # 3.2. Add a refinement to either silence the warning, or supply the method if # needed. if !Object.new.respond_to?(:untaint) || RUBY_VERSION =~ /\A(\d+)\.(\d+)(?:\.|\z)/ && ($1 == '2' && $2.to_i >= 7 || $1.to_i >= 3) require_relative 'tzinfo/untaint_ext' end require_relative 'tzinfo/version' require_relative 'tzinfo/string_deduper' require_relative 'tzinfo/timestamp' require_relative 'tzinfo/with_offset' require_relative 'tzinfo/datetime_with_offset' require_relative 'tzinfo/time_with_offset' require_relative 'tzinfo/timestamp_with_offset' require_relative 'tzinfo/timezone_offset' require_relative 'tzinfo/timezone_transition' require_relative 'tzinfo/transition_rule' require_relative 'tzinfo/annual_rules' require_relative 'tzinfo/data_sources' require_relative 'tzinfo/data_sources/timezone_info' require_relative 'tzinfo/data_sources/data_timezone_info' require_relative 'tzinfo/data_sources/linked_timezone_info' require_relative 'tzinfo/data_sources/constant_offset_data_timezone_info' require_relative 'tzinfo/data_sources/transitions_data_timezone_info' require_relative 'tzinfo/data_sources/country_info' require_relative 'tzinfo/data_sources/posix_time_zone_parser' require_relative 'tzinfo/data_sources/zoneinfo_reader' require_relative 'tzinfo/data_source' require_relative 'tzinfo/data_sources/ruby_data_source' require_relative 'tzinfo/data_sources/zoneinfo_data_source' require_relative 'tzinfo/timezone_period' require_relative 'tzinfo/offset_timezone_period' require_relative 'tzinfo/transitions_timezone_period' require_relative 'tzinfo/timezone' require_relative 'tzinfo/info_timezone' require_relative 'tzinfo/data_timezone' require_relative 'tzinfo/linked_timezone' require_relative 'tzinfo/timezone_proxy' require_relative 'tzinfo/country' require_relative 'tzinfo/country_timezone' require_relative 'tzinfo/format2' require_relative 'tzinfo/format2/country_definer' require_relative 'tzinfo/format2/country_index_definer' require_relative 'tzinfo/format2/country_index_definition' require_relative 'tzinfo/format2/timezone_definer' require_relative 'tzinfo/format2/timezone_definition' require_relative 'tzinfo/format2/timezone_index_definer' require_relative 'tzinfo/format2/timezone_index_definition' require_relative 'tzinfo/format1' require_relative 'tzinfo/format1/country_definer' require_relative 'tzinfo/format1/country_index_definition' require_relative 'tzinfo/format1/timezone_definer' require_relative 'tzinfo/format1/timezone_definition' require_relative 'tzinfo/format1/timezone_index_definition' tzinfo-2.0.4/.yardopts0000644000004100000410000000015113773021353014725 0ustar www-datawww-data--markup markdown --no-private --protected --readme README.md lib/**/*.rb - CHANGES.md LICENSE README.md tzinfo-2.0.4/CHANGES.md0000644000004100000410000011740713773021353014466 0ustar www-datawww-data# Changes ## Version 2.0.4 - 16-Dec-2020 * Fixed an incorrect InvalidTimezoneIdentifier exception raised when loading a zoneinfo file that includes rules specifying an additional transition to the final defined offset (for example, Africa/Casablanca in version 2018e of the Time Zone Database). #123. ## Version 2.0.3 - 8-Nov-2020 * Added support for handling "slim" format zoneinfo files that are produced by default by zic version 2020b and later. The POSIX-style TZ string is now used calculate DST transition times after the final defined transition in the file. #120. * Fixed `TimeWithOffset#getlocal` returning a `TimeWithOffset` with the `timezone_offset` still assigned when called with an offset argument on JRuby 9.3. * Rubinius is no longer supported. ## Version 2.0.2 - 2-Apr-2020 * Fixed 'wrong number of arguments' errors when running on JRuby 9.0. #114. * Fixed warnings when running on Ruby 2.8. #113. ## Version 2.0.1 - 24-Dec-2019 * Fixed "SecurityError: Insecure operation - require" exceptions when loading data with recent Ruby releases in safe mode. #100. * Fixed warnings when running on Ruby 2.7. #109. * Added a `TZInfo::Timezone#=~` method that performs a regex match on the time zone identifier. #99. * Added a `TZInfo::Country#=~` method that performs a regex match on the country code. ## Version 2.0.0 - 26-Dec-2018 ### Added * `to_local` and `period_for` instance methods have been added to `TZInfo::Timezone`. These are similar to `utc_to_local` and `period_for_utc`, but take the UTC offset of the given time into account. * `abbreviation`, `dst?`, `base_utc_offset` and `observed_utc_offset` instance methods have been added to `TZInfo::Timezone`, returning the abbreviation, whether daylight savings time is in effect and the UTC offset of the time zone at a specified time. * A `TZInfo::Timestamp` class has been added. It can be used with `TZInfo::Timezone` in place of a `Time` or `DateTime`. * `local_time`, `local_datetime` and `local_timestamp` instance methods have been added to `TZInfo::Timezone`. These methods construct local `Time`, `DateTime` and `TZInfo::Timestamp` instances with the correct UTC offset and abbreviation for the time zone. * Support for a (yet to be released) version 2 of tzinfo-data has been added, in addition to support for version 1. The new version will remove the (no longer needed) `DateTime` parameters from transition times, reduce memory consumption and improve the efficiency of loading timezone and country indexes. * A `TZInfo::VERSION` constant has been added, indicating the TZInfo version number. ### Changed * The minimum supported Ruby versions are now Ruby MRI 1.9.3, JRuby 1.7 (in 1.9 or later mode) and Rubinius 3. * Local times are now returned using the correct UTC offset (instead of using UTC). #49 and #52. * Local times are returned as instances of `TimeWithOffset`, `DateTimeWithOffset` or `TZInfo::TimestampWithOffset`. These classes subclass `Time`, `DateTime` and `TZInfo::Timestamp` respectively. They override the default behaviour of the base classes to return information about the observed offset at the indicated time. For example, the zone abbreviation is returned when using the `%Z` directive with `strftime`. * The `transitions_up_to`, `offsets_up_to` and `strftime` instance methods of `TZInfo::Timezone` now take the UTC offsets of given times into account (instead of ignoring them as was previously the case). * The `TZInfo::TimezonePeriod` class has been split into two subclasses: `TZInfo::OffsetTimezonePeriod` and `TZInfo::TransitionsTimezonePeriod`. `TZInfo::OffsetTimezonePeriod` is returned for time zones that only have a single offset. `TZInfo::TransitionsTimezonePeriod` is returned for periods that start or end with a transition. * `TZInfo::TimezoneOffset#abbreviation`, `TZInfo::TimezonePeriod#abbreviation` and `TZInfo::TimezonePeriod#zone_identifier` now return frozen `String` instances instead of instances of `Symbol`. * The `utc_offset` and `utc_total_offset` attributes of `TZInfo::TimezonePeriod` and `TZInfo::TimezoneOffset` have been renamed `base_utc_offset` and `observed_utc_offset` respectively. The former names have been retained as aliases. * `TZInfo::Timezone.get`, `TZInfo::Timezone.get_proxy` and `TZInfo::Country.get` can now be used with strings having any encoding. Previously, only encodings that are directly comparable with UTF-8 were supported. * The requested identifier is included in `TZInfo::InvalidTimezoneIdentifier` exception messages. * The requested country code is included in `TZInfo::InvalidCountryCode` exception messages. * The full range of transitions is now loaded from zoneinfo files. Zoneinfo files produced with version 2014c of the `zic` tool contain an initial transition `2**63` seconds before the epoch. Zoneinfo files produced with version 2014d or later of `zic` contain an initial transition `2**59` seconds before the epoch. These transitions would previously have been ignored, but are now returned in methods such as `TZInfo::Timezone#transitions_up_to`. * The `TZInfo::RubyDataSource` and `TZInfo::ZoneinfoDataSource` classes have been moved into a new `TZInfo::DataSources` module. Code that is setting `TZInfo::ZoneinfoDataSource.search_path` or `TZInfo::ZoneinfoDataSource.alternate_iso3166_tab_search_path` will need to be updated accordingly. * The `TZInfo::InvalidZoneinfoDirectory` and `TZInfo::ZoneinfoDirectoryNotFound` exception classes raised by `TZInfo::DataSources::ZoneinfoDataSource` have been moved into the `TZInfo::DataSources` module. * Setting the data source to `:ruby` or instantiating `TZInfo::DataSources::RubyDataSource` will now immediately raise a `TZInfo::DataSources::TZInfoDataNotFound` exception if `require 'tzinfo/data'` fails. Previously, a failure would only occur later when accessing an index or loading a timezone or country. * The `DEFAULT_SEARCH_PATH` and `DEFAULT_ALTERNATE_ISO3166_TAB_SEARCH_PATH` constants of `TZInfo::DataSources::ZoneinfoDataSource` have been made private. * The `TZInfo::Country.data_source`, `TZInfo::DataSource.create_default_data_source`, `TZInfo::DataSources::ZoneinfoDataSource.process_search_path`, `TZInfo::Timezone.get_proxies` and `TZInfo::Timezone.data_source` methods have been made private. * The performance of loading zoneinfo files and the associated indexes has been improved. * Memory use has been decreased by deduplicating `String` instances when loading country and time zone data. * The dependency on the deprecated thread_safe gem as been removed and replaced by concurrent-ruby. * The Info classes used to return time zone and country information from `TZInfo::DataSource` implementations have been moved into the `TZInfo::DataSources` module. * The `TZInfo::TransitionDataTimezoneInfo` class has been removed and replaced with `TZInfo::DataSources::TransitionsDataTimezoneInfo` and `TZInfo::DataSources::ConstantOffsetDataTimezoneInfo`. `TZInfo::DataSources::TransitionsDataTimezoneInfo` is constructed with an `Array` of `TZInfo::TimezoneTransition` instances representing times when the offset changes. `TZInfo::DataSources::ConstantOffsetDataTimezoneInfo` is constructed with a `TZInfo::TimezoneOffset` instance representing the offset constantly observed in a time zone. * The `TZInfo::DataSource#timezone_identifiers` method should no longer be overridden in custom data source implementations. The implementation in the base class now calculates a result from `TZInfo::DataSource#data_timezone_identifiers` and `TZInfo::DataSource#linked_timezone_identifiers`. * The results of the `TZInfo::DataSources::RubyDataSource` `to_s` and `inspect` methods now include the time zone database and tzinfo-data versions. ### Removed * Methods of `TZInfo::Timezone` that accept time arguments no longer allow `Integer` timestamp values. `Time`, `DateTime` or `TZInfo::Timestamp` values or objects that respond to `to_i`, `subsec` and optionally `utc_offset` must be used instead. * The `%:::z` format directive can now only be used with `TZInfo::Timezone#strftime` if it is supported by `Time#strftime` on the runtime platform. * Using `TZInfo::Timezone.new(identifier)` and `TZInfo::Country.new(code)` to obtain a specific `TZInfo::Timezone` or `TZInfo::Country` will no longer work. `TZInfo::Timezone.get(identifier)` and `TZInfo::Country.get(code)` should be used instead. * The `TZInfo::TimeOrDateTime` class has been removed. * The `valid_for_utc?`, `utc_after_start?`, `utc_before_end?`, `valid_for_local?`, `local_after_start?` and `local_before_end?` instance methods of `TZInfo::TimezonePeriod` have been removed. Comparisons can be performed with the results of the `starts_at`, `ends_at`, `local_starts_at` and `local_ends_at` methods instead. * The `to_local` and `to_utc` instance methods of `TZInfo::TimezonePeriod` and `TZInfo::TimezoneOffset` have been removed. Conversions should be performed using the `TZInfo::Timezone` class instead. * The `TZInfo::TimezonePeriod#utc_total_offset_rational` method has been removed. Equivalent information can be obtained using the `TZInfo::TimezonePeriod#observed_utc_offset` method. * The `datetime`, `time`, `local_end`, `local_end_time`, `local_start` and `local_start_time` instance methods of `TZInfo::TimezoneTransition` have been removed. The `at`, `local_end_at` and `local_start_at` methods should be used instead and the result (a `TZInfo::TimestampWithOffset`) converted to either a `DateTime` or `Time` by calling `to_datetime` or `to_time` on the result. * The `us_zones` and `us_zone_identifiers` class methods of `TZInfo::Timezone` have been removed. `TZInfo::Country.get('US').zones` and `TZInfo::Country.get('US').zone_identifiers` should be used instead. ## Version 1.2.9 - 16-Dec-2020 * Fixed an incorrect InvalidTimezoneIdentifier exception raised when loading a zoneinfo file that includes rules specifying an additional transition to the final defined offset (for example, Africa/Casablanca in version 2018e of the Time Zone Database). #123. ## Version 1.2.8 - 8-Nov-2020 * Added support for handling "slim" format zoneinfo files that are produced by default by zic version 2020b and later. The POSIX-style TZ string is now used calculate DST transition times after the final defined transition in the file. The 64-bit section is now always used regardless of whether Time has support for 64-bit times. #120. * Rubinius is no longer supported. ## Version 1.2.7 - 2-Apr-2020 * Fixed 'wrong number of arguments' errors when running on JRuby 9.0. #114. * Fixed warnings when running on Ruby 2.8. #112. ## Version 1.2.6 - 24-Dec-2019 * `Timezone#strftime('%s', time)` will now return the correct number of seconds since the epoch. #91. * Removed the unused `TZInfo::RubyDataSource::REQUIRE_PATH` constant. * Fixed "SecurityError: Insecure operation - require" exceptions when loading data with recent Ruby releases in safe mode. * Fixed warnings when running on Ruby 2.7. #106 and #111. ## Version 1.2.5 - 4-Feb-2018 * Support recursively (deep) freezing `Country` and `Timezone` instances. #80. * Allow negative daylight savings time offsets to be derived when reading from zoneinfo files. The utc_offset and std_offset are now derived correctly for Europe/Dublin in the 2018a and 2018b releases of the Time Zone Database. ## Version 1.2.4 - 26-Oct-2017 * Ignore the leapseconds file that is included in zoneinfo directories installed with version 2017c and later of the Time Zone Database. ## Version 1.2.3 - 25-Mar-2017 * Reduce the number of `String` objects allocated when loading zoneinfo files. #54. * Make `Timezone#friendly_identifier` compatible with frozen string literals. * Improve the algorithm for deriving the `utc_offset` from zoneinfo files. This now correctly handles Pacific/Apia switching from one side of the International Date Line to the other whilst observing daylight savings time. #66. * Fix an `UnknownTimezone` exception when calling transitions_up_to or offsets_up_to on a `TimezoneProxy` instance obtained from `Timezone.get_proxy`. * Allow the Factory zone to be obtained from the Zoneinfo data source. * Ignore the /usr/share/zoneinfo/timeconfig symlink included in Slackware distributions. #64. * Fix `Timezone#strftime` handling of `%Z` expansion when `%Z` is prefixed with more than one percent. #31. * Support expansion of `%z`, `%:z`, `%::z` and `%:::z` to the UTC offset of the time zone in `Timezone#strftime`. #31 and #67. ## Version 1.2.2 - 8-Aug-2014 * Fix an error with duplicates being returned by `Timezone#all_country_zones` and `Timezone#all_country_zone_identifiers` when used with tzinfo-data v1.2014.6 or later. * Use the zone1970.tab file for country timezone data if it is found in the zoneinfo directory (and fallback to zone.tab if not). zone1970.tab was added in tzdata 2014f. zone.tab is now deprecated. ## Version 1.2.1 - 1-Jun-2014 * Support zoneinfo files generated with zic version 2014c and later. * On platforms that only support positive 32-bit timestamps, ensure that conversions are accurate from the epoch instead of just from the first transition after the epoch. * Minor documentation improvements. ## Version 1.2.0 - 26-May-2014 * Raise the minimum supported Ruby version to 1.8.7. * Support loading system zoneinfo data on FreeBSD, OpenBSD and Solaris. Resolves #15. * Add `canonical_identifier` and `canonical_zone` methods to `Timezone`. Resolves #16. * Add a link to a `DataSourceNotFound` help page in the `TZInfo::DataSourceNotFound` exception message. * Load iso3166.tab and zone.tab files as UTF-8. * Fix `Timezone#local_to_utc` returning local `Time` instances on systems using UTC as the local time zone. Resolves #13. * Fix `==` methods raising an exception when passed an instance of a different class by making `<=>` return `nil` if passed a non-comparable argument. * Eliminate `require 'rational'` warnings. Resolves #10. * Eliminate "assigned but unused variable - info" warnings. Resolves #11. * Switch to minitest v5 for unit tests. Resolves #18. ## Version 1.1.0 - 25-Sep-2013 * TZInfo is now thread safe. `ThreadSafe::Cache` is now used instead of `Hash` to cache `Timezone` and `Country` instances returned by `Timezone.get` and `Country.get`. The tzinfo gem now depends on thread_safe ~> 0.1. * Added a `transitions_up_to` method to `Timezone` that returns a list of the times where the UTC offset of the timezone changes. * Added an `offsets_up_to` method to `Timezone` that returns the set of offsets that have been observed in a defined timezone. * Fixed a "can't modify frozen String" error when loading a `Timezone` from a zoneinfo file using an identifier `String` that is both tainted and frozen. Resolves #3. * Support TZif3 format zoneinfo files (now produced by zic from tzcode version 2013e onwards). * Support using YARD to generate documentation (added a .yardopts file). * Ignore the +VERSION file included in the zoneinfo directory on Mac OS X. * Added a note to the documentation concerning 32-bit zoneinfo files (as included with Mac OS X). ## Version 1.0.1 - 22-Jun-2013 * Fix a test case failure when tests are run from a directory that contains a dot in the path (issue #29751). ## Version 1.0.0 - 2-Jun-2013 * Allow TZInfo to be used with different data sources instead of just the built-in Ruby module data files. * Include a data source that allows TZInfo to load data from the binary zoneinfo files produced by zic and included with many Linux and Unix-like distributions. * Remove the definition and index Ruby modules from TZInfo and move them into a separate TZInfo::Data library (available as the tzinfo-data gem). * Default to using the TZInfo::Data library as the data source if it is installed, otherwise use zoneinfo files instead. * Preserve the nanoseconds of local timezone Time objects when performing conversions (issue #29705). * Don't add the tzinfo lib directory to the search path when requiring 'tzinfo'. The tzinfo lib directory must now be in the search path before 'tzinfo' is required. * Add `utc_start_time`, `utc_end_time`, `local_start_time` and `local_end_time` instance methods to `TimezonePeriod`. These return an identical value as the existing `utc_start`, `utc_end`, `local_start` and `local_end` methods, but return `Time` instances instead of `DateTime`. * Make the `start_transition`, `end_transition` and `offset` properties of `TimezonePeriod` protected. To access properties of the period, callers should use other `TimezonePeriod` instance methods instead (issue #7655). ## Version 0.3.58 (tzdata v2020d) - 8-Nov-2020 * Updated to tzdata version 2020d (https://mm.icann.org/pipermail/tz-announce/2020-October/000062.html). ## Version 0.3.57 (tzdata v2020a) - 17-May-2020 * Updated to tzdata version 2020a (). ## Version 0.3.56 (tzdata v2019c) - 1-Nov-2019 * Updated to tzdata version 2019c (). ## Version 0.3.55 (tzdata v2018g) - 27-Oct-2018 * Updated to tzdata version 2018g (). ## Version 0.3.54 (tzdata v2018d) - 25-Mar-2018 * Updated to tzdata version 2018d (). ## Version 0.3.53 (tzdata v2017b) - 23-Mar-2017 * Updated to tzdata version 2017b (). ## Version 0.3.52 (tzdata v2016h) - 28-Oct-2016 * Updated to tzdata version 2016h (). ## Version 0.3.51 (tzdata v2016f) - 5-Jul-2016 * Updated to tzdata version 2016f (). ## Version 0.3.50 (tzdata v2016e) - 14-Jun-2016 * Updated to tzdata version 2016e (). ## Version 0.3.49 (tzdata v2016d) - 18-Apr-2016 * Updated to tzdata version 2016d (). ## Version 0.3.48 (tzdata v2016c) - 23-Mar-2016 * Updated to tzdata version 2016c (). ## Version 0.3.47 (tzdata v2016b) - 15-Mar-2016 * Updated to tzdata version 2016b (). ## Version 0.3.46 (tzdata v2015g) - 2-Dec-2015 * From version 2015e, the IANA time zone database uses non-ASCII characters in country names. Backport the encoding handling from TZInfo::Data to allow TZInfo 0.3.x to support Ruby 1.9 (which would otherwise fail with an invalid byte sequence error when loading the countries index). Resolves #41. ## Version 0.3.45 (tzdata v2015g) - 3-Oct-2015 * Updated to tzdata version 2015g (). ## Version 0.3.44 (tzdata v2015d) - 24-Apr-2015 * Updated to tzdata version 2015d (). ## Version 0.3.43 (tzdata v2015a) - 31-Jan-2015 * Updated to tzdata version 2015a (). ## Version 0.3.42 (tzdata v2014i) - 23-Oct-2014 * Updated to tzdata version 2014i (). ## Version 0.3.41 (tzdata v2014f) - 8-Aug-2014 * Updated to tzdata version 2014f (). ## Version 0.3.40 (tzdata v2014e) - 10-Jul-2014 * Updated to tzdata version 2014e (). ## Version 0.3.39 (tzdata v2014a) - 9-Mar-2014 * Updated to tzdata version 2014a (). ## Version 0.3.38 (tzdata v2013g) - 8-Oct-2013 * Updated to tzdata version 2013g (). ## Version 0.3.37 (tzdata v2013b) - 11-Mar-2013 * Updated to tzdata version 2013b (). ## Version 0.3.36 (tzdata v2013a) - 3-Mar-2013 * Updated to tzdata version 2013a (). * Fix `TimezoneTransitionInfo#eql?` incorrectly returning false when running on Ruby 2.0. * Change `eql?` and `==` implementations to test the class of the passed in object instead of checking individual properties with `respond_to?`. ## Version 0.3.35 (tzdata v2012i) - 4-Nov-2012 * Updated to tzdata version 2012i (). ## Version 0.3.34 (tzdata v2012h) - 27-Oct-2012 * Updated to tzdata version 2012h (). ## Version 0.3.33 (tzdata v2012c) - 8-Apr-2012 * Updated to tzdata version 2012c (). ## Version 0.3.32 (tzdata v2012b) - 4-Mar-2012 * Updated to tzdata version 2012b (). ## Version 0.3.31 (tzdata v2011n) - 6-Nov-2011 * Updated to tzdata version 2011n (). ## Version 0.3.30 (tzdata v2011k) - 29-Sep-2011 * Updated to tzdata version 2011k (). ## Version 0.3.29 (tzdata v2011h) - 27-Jun-2011 * Updated to tzdata version 2011h (). * Allow the default value of the `local_to_utc` and `period_for_local` `dst` parameter to be specified globally with a `Timezone.default_dst` attribute. Thanks to Kurt Werle for the suggestion and patch. ## Version 0.3.28 (tzdata v2011g) - 13-Jun-2011 * Add support for Ruby 1.9.3 (trunk revision 31668 and later). Thanks to Aaron Patterson for reporting the problems running on the new version. Closes #29233. ## Version 0.3.27 (tzdata v2011g) - 26-Apr-2011 * Updated to tzdata version 2011g (). ## Version 0.3.26 (tzdata v2011e) - 2-Apr-2011 * Updated to tzdata version 2011e (). ## Version 0.3.25 (tzdata v2011d) - 14-Mar-2011 * Updated to tzdata version 2011d (). ## Version 0.3.24 (tzdata v2010o) - 15-Jan-2011 * Updated to tzdata version 2010o (). ## Version 0.3.23 (tzdata v2010l) - 19-Aug-2010 * Updated to tzdata version 2010l (). ## Version 0.3.22 (tzdata v2010j) - 29-May-2010 * Corrected file permissions issue with 0.3.21 release. ## Version 0.3.21 (tzdata v2010j) - 28-May-2010 * Updated to tzdata version 2010j (). * Change invalid timezone check to exclude characters not used in timezone identifiers and avoid 'character class has duplicated range' warnings with Ruby 1.9.2. * Ruby 1.9.2 has deprecated `require 'rational'`, but older versions of Ruby need rational to be required. Require rational only when the Rational module has not already been loaded. * Remove circular requires (now a warning in Ruby 1.9.2). Instead of using requires in each file for dependencies, `tzinfo.rb` now requires all tzinfo files. If you were previously requiring files within the tzinfo directory (e.g. `require 'tzinfo/timezone'`), then you will now have to `require 'tzinfo'` instead. ## Version 0.3.20 (tzdata v2010i) - 19-Apr-2010 * Updated to tzdata version 2010i (). ## Version 0.3.19 (tzdata v2010h) - 5-Apr-2010 * Updated to tzdata version 2010h (). ## Version 0.3.18 (tzdata v2010g) - 29-Mar-2010 * Updated to tzdata version 2010g (). ## Version 0.3.17 (tzdata v2010e) - 8-Mar-2010 * Updated to tzdata version 2010e (). ## Version 0.3.16 (tzdata v2009u) - 5-Jan-2010 * Support the use of '-' to denote '0' as an offset in the tz data files. Used for the first time in the SAVE field in tzdata v2009u. * Updated to tzdata version 2009u (). ## Version 0.3.15 (tzdata v2009p) - 26-Oct-2009 * Updated to tzdata version 2009p (). * Added a description to the gem spec. * List test files in test_files instead of files in the gem spec. ## Version 0.3.14 (tzdata v2009l) - 19-Aug-2009 * Updated to tzdata version 2009l (). * Include current directory in load path to allow running tests on Ruby 1.9.2, which doesn't include it by default any more. ## Version 0.3.13 (tzdata v2009f) - 15-Apr-2009 * Updated to tzdata version 2009f (). * Untaint the timezone module filename after validation to allow use with `$SAFE == 1` (e.g. under mod_ruby). Thanks to Dmitry Borodaenko for the suggestion. Closes #25349. ## Version 0.3.12 (tzdata v2008i) - 12-Nov-2008 * Updated to tzdata version 2008i (). ## Version 0.3.11 (tzdata v2008g) - 7-Oct-2008 * Updated to tzdata version 2008g (). * Support Ruby 1.9.0-5. `Rational.new!` has now been removed in Ruby 1.9. Only use `Rational.new!` if it is available (it is preferable in Ruby 1.8 for performance reasons). Thanks to Jeremy Kemper and Pratik Naik for reporting this. Closes #22312. * Apply a patch from Pratik Naik to replace assert calls that have been deprecated in the Ruby svn trunk. Closes #22308. ## Version 0.3.10 (tzdata v2008f) - 16-Sep-2008 * Updated to tzdata version 2008f (). ## Version 0.3.9 (tzdata v2008c) - 27-May-2008 * Updated to tzdata version 2008c (). * Support loading timezone data in the latest trunk versions of Ruby 1.9. `Rational.new!` is now private, so call it using `Rational.send :new!` instead. Thanks to Jeremy Kemper and Pratik Naik for spotting this. Closes #19184. * Prevent warnings from being output when running Ruby with the -v or -w command line options. Thanks to Paul McMahon for the patch. Closes #19719. ## Version 0.3.8 (tzdata v2008b) - 24-Mar-2008 * Updated to tzdata version 2008b (). * Support loading timezone data in Ruby 1.9.0. Use `DateTime.new!` if it is available instead of `DateTime.new0` when constructing transition times. `DateTime.new!` was added in Ruby 1.8.6. `DateTime.new0` was removed in Ruby 1.9.0. Thanks to Joshua Peek for reporting this. Closes #17606. * Modify some of the equality test cases to cope with the differences between Ruby 1.8.6 and Ruby 1.9.0. ## Version 0.3.7 (tzdata v2008a) - 10-Mar-2008 * Updated to tzdata version 2008a (). ## Version 0.3.6 (tzdata v2007k) - 1-Jan-2008 * Updated to tzdata version 2007k (). * Removed deprecated RubyGems autorequire option. ## Version 0.3.5 (tzdata v2007h) - 1-Oct-2007 * Updated to tzdata version 2007h (). ## Version 0.3.4 (tzdata v2007g) - 21-Aug-2007 * Updated to tzdata version 2007g (). ## Version 0.3.3 (tzdata v2006p) - 27-Nov-2006 * Updated to tzdata version 2006p (). ## Version 0.3.2 (tzdata v2006n) - 11-Oct-2006 * Updated to tzdata version 2006n (). Note that this release of tzdata removes the country Serbia and Montenegro (CS) and replaces it with separate Serbia (RS) and Montenegro (ME) entries. ## Version 0.3.1 (tzdata v2006j) - 21-Aug-2006 * Remove colon from case statements to avoid warning in Ruby 1.8.5. #5198. * Use temporary variable to avoid dynamic string warning from rdoc. * Updated to tzdata version 2006j (). ## Version 0.3.0 (tzdata v2006g) - 17-Jul-2006 * New timezone data format. Timezone data now occupies less space on disk and takes less memory once loaded. #4142, #4144. * Timezone data is defined in modules rather than classes. `Timezone` instances returned by `Timezone.get` are no longer instances of data classes, but are instead instances of new `DataTimezone` and `LinkedTimezone` classes. * `Timezone` instances can now be used with `Marshal.dump` and `Marshal.load`. #4240. * Added a `Timezone.get_proxy` method that returns a `TimezoneProxy` object for a given identifier. * Country index data is now defined in a single module that is independent of the `Country` class implementation. * `Country` instances can now be used with `Marshal.dump` and `Marshal.load`. #4240. * `Country` has a new `zone_info` method that returns `CountryTimezone` objects containing additional information (latitude, longitude and a description) relating to each `Timezone`. #4140. * Time zones within a `Country` are now returned in an order that makes geographic sense. * The zdumptest utility now checks local to utc conversions in addition to utc to local conversions. * `eql?` method defined on `Country` and `Timezone` that is equivalent to `==`. * The `==` method of `Timezone` no longer raises an exception when passed an object with no identifier method. * The `==` method of `Country` no longer raises an exception when passed an object with no code method. * `hash` method defined on `Country` that returns the hash of the code. * `hash` method defined on `Timezone` that returns the hash of the identifier. * Miscellaneous API documentation corrections and improvements. * Timezone definition and indexes are now excluded from rdoc (the contents were previously ignored with `#:nodoc:` anyway). * Removed no longer needed `#:nodoc:` directives from timezone data files (which are now excluded from the rdoc build). * Installation of the gem now causes rdoc API documentation to be generated. #4905. * When optimizing transitions to generate zone definitions, check the UTC and standard offsets separately rather than just the total offset to UTC. Fixes an incorrect abbreviation issue with Europe/London, Europe/Dublin and Pacific/Auckland. * Eliminated unnecessary `.nil?` calls to give a minor performance gain. * `Timezone.all` and `Timezone.all_identifiers` now return all the `Timezone` instances/identifiers rather than just those associated with countries. #4146. * Added `all_data_zones`, `all_data_zone_identifiers`, `all_linked_zones` and `all_linked_zone_identifiers` class methods to `Timezone`. * Added a `strftime` method to `Timezone` that converts a time in UTC to local time and then returns it formatted. `%Z` is replaced with the timezone abbreviation for the given time (for example, EST or EDT). #4143. * Fix escaping of quotes in `TZDataParser`. This affected country names and descriptions of time zones within countries. ## Version 0.2.2 (tzdata v2006g) - 17-May-2006 * Use class-scoped instance variables to store the Timezone identifier and singleton instance. Loading a linked zone no longer causes the parent zone's identifier to be changed. The instance method of a linked zone class also now returns an instance of the linked zone class rather than the parent class. #4502. * The zdumptest utility now compares the TZInfo zone identifier with the zdump zone identifier. * The zdumptestall utility now exits if not supplied with enough parameters. * Updated to tzdata version 2006g (). ## Version 0.2.1 (tzdata v2006d) - 17-Apr-2006 * Fix a performance issue caused in 0.2.0 with `Timezone.local_to_utc`. Conversions performed on `TimeOrDateTime` instances passed to `<=>` are now cached as originally intended. Thanks to Michael Smedberg for spotting this. * Fix a performance issue with the `local_to_utc` period search algorithm originally implemented in 0.1.0. The condition that was supposed to cause the search to terminate when enough periods had been found was only being evaluated in a small subset of cases. Thanks to Michael Smedberg and Jamis Buck for reporting this. * Added abbreviation as an alias for `TimezonePeriod.zone_identifier`. * Updated to tzdata version 2006d (). * Ignore any offset in `DateTime` instances passed in (as is already done for `Time` instances). All of the following now refer to the same UTC time (15:40 on 17 April 2006). Previously, the `DateTime` in the second line would have been interpreted as 20:40. ```ruby tz.utc_to_local(DateTime.new(2006, 4, 17, 15, 40, 0)) tz.utc_to_local(DateTime.new(2006, 4, 17, 15, 40, 0).new_offset(Rational(5, 24))) tz.utc_to_local(Time.utc(2006, 4, 17, 15, 40, 0)) tz.utc_to_local(Time.local(2006, 4, 17, 15, 40, 0)) ``` ## Version 0.2.0 (tzdata v2006c) - 3-Apr-2006 * Use timestamps rather than `DateTime` objects in zone files for times between 1970 and 2037 (the range of `Time`). * Don't convert passed in `Time` objects to `DateTime` in most cases (provides a substantial performance improvement). * Allow integer timestamps (time in seconds since 1970-1-1) to be used as well as `Time` and `DateTime` objects in all public methods that take times as parameters. * Tool to compare TZInfo conversions with output from zdump. * `TZDataParser` zone generation algorithm rewritten. Now based on the zic code. TZInfo is now 100% compatible with zic/zdump output. * Riyadh Solar Time zones now included again (generation time has been reduced with `TZDataParser` changes). * Use binary mode when writing zone and country files to get Unix (\n) new lines. * Omit unnecessary quotes in zone identifier symbols. * Omit the final transition to DST if there is a prior transition in the last year processed to standard time. * Updated to tzdata version 2006c (). ## Version 0.1.2 (tzdata v2006a) - 5-Feb-2006 * Add lib directory to the load path when tzinfo is required. Makes it easier to use tzinfo gem when unpacked to vendor directory in rails. * Updated to tzdata version 2006a (). * `build_tz_classes` rake task now handles running svn add and svn delete as new time zones and countries are added and old ones are removed. * Return a better error when attempting to use a `Timezone` instance that was constructed with `Timezone.new(nil)`. This will occur when using Rails' `composed_of`. When the timezone identifier in the database is null, attempting to use the `Timezone` will now result in an `UnknownTimezone` exception rather than a `NameError`. ## Version 0.1.1 (tzdata v2005q) - 18-Dec-2005 * Time zones that are defined by a single unbounded period (e.g. UTC) now work again. * Updated to tzdata version 2005q. ## Version 0.1.0 (tzdata v2005n) - 27-Nov-2005 * `period_for_local` and `local_to_utc` now allow resolution of ambiguous times (e.g. when switching from daylight savings to standard time). The behaviour of these methods when faced with an ambiguous local time has now changed. If you are using these methods you should check the documentation. Thanks to Cliff Matthews for suggesting this change. * Added `require 'date'` to `timezone.rb` (date isn't loaded by default in all environments). * Use rake to build packages and documentation. * License file is now included in gem distribution. * Dates in definitions stored as Astronomical Julian Day numbers rather than as civil dates (improves performance creating `DateTime` instances). * Added options to `TZDataParser` to allow generation of specific zones and countries. * Moved `TimezonePeriod` class to `timezone_period.rb`. * New `TimezonePeriodList` class to store `TimezonePeriod` instances for a timezone and perform searches for periods. * Time zones are now defined using blocks. `TimezonePeriod` instances are only created when they are needed. Thanks to Jamis Buck for the suggestion. * Add options to `TZDataParser` to allow exclusion of specific zones and countries. * Exclude the Riyadh Solar Time zones. The rules are only for 1987 to 1989 and take a long time to generate and process. Riyadh Solar Time is no longer observed. * The last `TimezonePeriod` for each `Timezone` is now written out with an unbounded rather than arbitrary end time. * Construct the `Rational` offset in `TimezonePeriod` once when the `TimezonePeriod` is constructed rather than each time it is needed. * `Timezone` and `Country` now keep a cache of loaded instances to avoid running `require` which can be slow on some platforms. * Updated to tzdata version 2005n. ## Version 0.0.4 (tzdata v2005m) - 18-Sep-2005 * Removed debug output accidentally included in the previous release. * Fixed a bug in the generation of friendly zone identifiers (was inserting apostrophes into UTC, GMT, etc). * Fixed `Country` `<=>` operator (was comparing non-existent attribute) * Fixed `Timezone.period_for_local` error when period not found. * Added test cases for `Timezone`, `TimezoneProxy`, `TimezonePeriod`, `Country` and some selected time zones. ## Version 0.0.3 (tzdata v2005m) - 17-Sep-2005 * Reduced visibility of some methods added in `Timezone#setup` and `Country#setup`. * Added `name` method to `Timezone` (returns the identifier). * Added `friendly_identifier` method to `Timezone`. Returns a more friendly version of the identifier. * Added `to_s` method to `Timezone`. Returns the friendly identifier. * Added `==` and `<=>` operators to `Timezone` (compares identifiers). * `Timezone` now includes `Comparable`. * Added `to_s` method to `Country`. * Added `==` and `<=>` operators to `Country` (compares ISO 3166 country codes). * `Country` now includes `Comparable`. * New `TimezoneProxy` class that behaves the same as a `Timezone` but doesn't actually load in its definition until it is actually required. * Modified `Timezone` and `Country` methods that return `Timezone` instances to return `TimezoneProxy` instances instead. This makes these methods much quicker. In Ruby on Rails, you can now show a drop-down list of all time zones using the Rails `time_zone_select` helper method: ```ruby <%= time_zone_select 'user', 'time_zone', TZInfo::Timezone.all.sort, :model => TZInfo::Timezone %> ``` ## Version 0.0.2 (tzdata v2005m) - 13-Sep-2005 * `Country` and `Timezone` data is now loaded into class rather than instance variables. This makes `Timezone` links more efficient and saves memory if creating specific `Timezone` and `Country` classes directly. * `TimezonePeriod` `zone_identifier` is now defined as a symbol to save memory (was previously a string). * `TimezonePeriod` `zone_identifier`s that were previously `''` are now `:Unknown`. * `Timezone` and `Country` instances can now be returned using `Timezone.new(identifier)` and `Country.new(identifier)`. When passed an identifier, the `new` method calls `get` to return an instance of the specified timezone or country. * Added new class methods to `Timezone` to return sets of zones and identifiers. Thanks to Scott Barron of Lunchbox Software for the suggestions in his article about using TZInfo with Rails () ## Version 0.0.1 (tzdata v2005m) - 29-Aug-2005 * First release.