class TZInfo::DataSource

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.

Public Class Methods

get() click to toggle source

@return [DataSource] the currently selected source of data.

# File lib/tzinfo/data_source.rb, line 42
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
new() click to toggle source

Initializes a new {DataSource} instance. Typically only called via subclasses of {DataSource}.

# File lib/tzinfo/data_source.rb, line 166
def initialize
  @timezones = Concurrent::Map.new
end
set(data_source_or_type, *args) click to toggle source

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<Object>] 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}.
# File lib/tzinfo/data_source.rb, line 127
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 Class Methods

create_default_data_source() click to toggle source

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.

# File lib/tzinfo/data_source.rb, line 145
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

Public Instance Methods

country_codes() click to toggle source

Returns a frozen ‘Array` of all the available ISO 3166-1 alpha-2 country codes. The identifiers are sorted according to `String#<=>`.

@return [Array<String>] a frozen ‘Array` of all the available ISO 3166-1

alpha-2 country codes.
# File lib/tzinfo/data_source.rb, line 246
def country_codes
  raise_invalid_data_source('country_codes')
end
data_timezone_identifiers() click to toggle source

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<String>] a frozen ‘Array` of all the available time zone

identifiers for data time zones.
# File lib/tzinfo/data_source.rb, line 218
def data_timezone_identifiers
  raise_invalid_data_source('data_timezone_identifiers')
end
eager_load!() click to toggle source

Loads all timezone and country data into memory.

This may be desirable in production environments to improve copy-on-write performance and to avoid flushing the constant cache every time a new timezone or country is loaded from {DataSources::RubyDataSource}.

# File lib/tzinfo/data_source.rb, line 255
def eager_load!
  timezone_identifiers.each {|identifier| load_timezone_info(identifier) }
  country_codes.each {|code| load_country_info(code) }
  nil
end
get_country_info(code) click to toggle source

@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.
# File lib/tzinfo/data_source.rb, line 237
def get_country_info(code)
  load_country_info(code)
end
get_timezone_info(identifier) click to toggle source

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.
# File lib/tzinfo/data_source.rb, line 184
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
inspect() click to toggle source

@return [String] the internal object state as a programmer-readable

`String`.
# File lib/tzinfo/data_source.rb, line 268
def inspect
  "#<#{self.class}>"
end
linked_timezone_identifiers() click to toggle source

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<String>] a frozen ‘Array` of all the available time zone

identifiers that are links to other time zones.
# File lib/tzinfo/data_source.rb, line 228
def linked_timezone_identifiers
  raise_invalid_data_source('linked_timezone_identifiers')
end
timezone_identifiers() click to toggle source

@return [Array<String>] a frozen ‘Array“ of all the available time zone

identifiers. The identifiers are sorted according to `String#<=>`.
# File lib/tzinfo/data_source.rb, line 204
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
to_s() click to toggle source

@return [String] a description of the {DataSource}.

# File lib/tzinfo/data_source.rb, line 262
def to_s
  "Default DataSource"
end

Protected Instance Methods

load_country_info(code) click to toggle source

@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.
# File lib/tzinfo/data_source.rb, line 294
def load_country_info(code)
  raise_invalid_data_source('load_country_info')
end
load_timezone_info(identifier) click to toggle source

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.
# File lib/tzinfo/data_source.rb, line 285
def load_timezone_info(identifier)
  raise_invalid_data_source('load_timezone_info')
end
lookup_country_info(hash, code, encoding = Encoding::UTF_8) click to toggle source

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`.

# File lib/tzinfo/data_source.rb, line 337
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
timezone_identifier_encoding() click to toggle source

@return [Encoding] the ‘Encoding` used by the `String` instances returned

by {data_timezone_identifiers} and {linked_timezone_identifiers}.
# File lib/tzinfo/data_source.rb, line 300
def timezone_identifier_encoding
  Encoding::UTF_8
end
validate_timezone_identifier(identifier) click to toggle source

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}.
# File lib/tzinfo/data_source.rb, line 315
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

Private Instance Methods

build_timezone_identifiers() click to toggle source

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<String>] an ‘Array` containing all valid time zone

identifiers.
# File lib/tzinfo/data_source.rb, line 366
def build_timezone_identifiers
  data = data_timezone_identifiers
  linked = linked_timezone_identifiers
  linked.empty? ? data : (data + linked).sort!.freeze
end
find_timezone_identifier(identifier) click to toggle source

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:

# File lib/tzinfo/data_source.rb, line 382
def find_timezone_identifier(identifier)

  result = timezone_identifiers.bsearch {|i| i >= identifier }
  result == identifier ? result : nil
end
raise_invalid_data_source(method_name) click to toggle source

Raises {InvalidDataSource} to indicate that a method has not been overridden by a particular data source implementation.

@raise [InvalidDataSource] always.

# File lib/tzinfo/data_source.rb, line 352
def raise_invalid_data_source(method_name)
  raise InvalidDataSource, "#{method_name} not defined"
end
try_with_encoding(string, encoding) { |string| ... } click to toggle source

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`.

# File lib/tzinfo/data_source.rb, line 436
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