class TZInfo::DataSources::ZoneinfoDataSource

inaccurate.
2038-01-19 03:14:07. Any queries falling after this time may be
Database, 64-bit zoneinfo files only include future transitions up to
It is also worth noting that as of the 2017c release of the IANA Time Zone
file.
file. If it returns ‘“TZif2”` or `“TZif3”` then you have a 64-bit zoneinfo
If the last line returns `“TZif\x00”`, then you have a 32-bit zoneinfo
File.open(File.join(dir, zone_identifier), ’r’) {|f| f.read(5) }
dir = TZInfo::DataSource.get.zoneinfo_dir
TZInfo::DataSource.set(:zoneinfo)
zone you want to test for ‘zone_identifier`):
data, you can run the following code (substituting the identifier of the
To check whether your zoneinfo files contain 32-bit or 64-bit transition
to at least 10.8.4) still uses 32-bit zoneinfo files.
Most modern platforms include 64-bit zoneinfo files. However, Mac OS X (up
20:45:52 to 2038-01-19 03:14:07 may be inaccurate.
on your system, then any queries falling outside of the range 1901-12-13
zoneinfo files that use 64-bit integers. If you have 32-bit zoneinfo files
32-bit integers for transition timestamps. Later versions of zic produce
that were released prior to February 2006 created zoneinfo files that used
Please note that versions of the ’zic’ tool (used to build zoneinfo files)
TZInfo::DataSource.set(:zoneinfo, directory, iso3166_path)
path to the iso3166.tab file to {TZInfo::DataSource.set}:
iso3166.tab index file from a separate location, pass the directory and
To load zoneinfo files from a particular directory, but load the
TZInfo::DataSource.set(:zoneinfo, directory)
{TZInfo::DataSource.set}:
To load zoneinfo files from a particular directory, pass the directory to
TZInfo::DataSource.set(:zoneinfo)
{TZInfo::DataSource.set} as follows:
To have TZInfo load the system zoneinfo files, call
iso3166.tab and zone1970.tab or zone.tab index files.
containing compiled “TZif” version 3 (or earlier) files in addition to
A DataSource implementation that loads data from a ‘zoneinfo’ directory

def alternate_iso3166_tab_search_path

Returns:
  • (Array) - an `Array` of paths to check in order to
def alternate_iso3166_tab_search_path
  @@alternate_iso3166_tab_search_path
end

def alternate_iso3166_tab_search_path=(alternate_iso3166_tab_search_path)

Parameters:
  • alternate_iso3166_tab_search_path (Object) -- either `nil` or a
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

def data_timezone_identifiers

Returns:
  • (Array) - a frozen `Array` of all the available time zone
def data_timezone_identifiers
  @timezone_identifiers
end

def dms_to_rational(sign, degrees, minutes, seconds = nil)

Returns:
  • (Rational) - the result of converting from degrees, minutes and

Parameters:
  • seconds (String) -- the number of seconds (optional).
  • minutes (String) -- the number of minutes.
  • degrees (String) -- the number of degrees.
  • sign (String) -- `'-'` or `'+'`.
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

def enum_timezones(dir, exclude = [], &block)

Other tags:
    Yieldparam: path - the path of a time zone file as an

Other tags:
    Yield: - the path of each time zone file found is passed to

Parameters:
  • exclude (Array) -- file names to exclude when scanning
  • dir (Array) -- the directory to enumerate as an `Array` of
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)
      RubyCoreSupport.untaint(entry)
      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

def find_zoneinfo_dir

Returns:
  • (Array) - an `Array` containing the iso3166.tab and
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

def initialize(zoneinfo_dir = nil, alternate_iso3166_tab_path = nil)

Raises:
  • (ZoneinfoDirectoryNotFound) - if no valid directory can be found
  • (InvalidZoneinfoDirectory) - if the iso3166.tab and zone1970.tab or

Parameters:
  • alternate_iso3166_tab_path (String) -- an optional path to the
  • zoneinfo_dir (String) -- an optional path to a directory to use as
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

def inspect

(see DataSource#inspect)
def inspect
  "#<#{self.class}: #{@zoneinfo_dir}>"
end

def linked_timezone_identifiers

Returns:
  • (Array) - an empty `Array`.
def linked_timezone_identifiers
  [].freeze
end

def load_countries(iso3166_tab_path, zone_tab_path)

Returns:
  • (Hash) - a mapping from ISO 3166-1 alpha-2

Parameters:
  • zone_tab_path (String) -- the path to the zone.tab file.
  • iso3166_tab_path (String) -- the path to the iso3166.tab file.
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

def load_country_info(code)

(see DataSource#load_country_info)
def load_country_info(code)
  lookup_country_info(@countries, code)
end

def load_timezone_identifiers

Returns:
  • (Array) - an `Array` containing all the time zone
def load_timezone_identifiers
  index = []
  enum_timezones([], EXCLUDED_FILENAMES) do |identifier|
    index << identifier.join('/').freeze
  end
  index.sort!
end

def load_timezone_info(identifier)

Raises:
  • (InvalidTimezoneIdentifier) - if the time zone is not found, the

Returns:
  • (TimezoneInfo) - a {TimezoneInfo} instance for the given time zone

Parameters:
  • identifier (String) -- A time zone identifier.
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

def process_search_path(path, default)

Returns:
  • (Array) - the processed path.

Parameters:
  • default (Array) -- the default value.
  • path (Object) -- either `nil` or a list of paths to check as
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

def resolve_tab_path(zoneinfo_path, standard_names, tab_name)

Returns:
  • (String) - the path to the tab file.

Parameters:
  • tab_name (String) -- the alternate name for the tab file to check in
  • standard_names (Array) -- the standard names for the tab
  • zoneinfo_path (String) -- the path to a zoneinfo directory.
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

def search_path

Returns:
  • (Array) - an `Array` of directories to check in order to
def search_path
  @@search_path
end

def search_path=(search_path)

Parameters:
  • search_path (Object) -- either `nil` or a list of directories to
def search_path=(search_path)
  @@search_path = process_search_path(search_path, DEFAULT_SEARCH_PATH)
end

def to_s

(see DataSource#to_s)
def to_s
  "Zoneinfo DataSource: #{@zoneinfo_dir}"
end

def validate_zoneinfo_dir(path, iso3166_tab_path = nil)

Returns:
  • (Array) - an `Array` containing the iso3166.tab and

Parameters:
  • iso3166_tab_path (String) -- an optional path to an external
  • path (String) -- the path to a possible zoneinfo directory.
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