class TZInfo::DataSources::PosixTimeZoneParser

:nodoc:
@private
by tzfile.5 and tzset.3.
A parser for POSIX-style TZ strings used in zoneinfo files and specified

def check_scan(s, pattern)

Raises:
  • (InvalidPosixTimeZone) - if the pattern does not match the input.

Returns:
  • (String) - the result of the scan.

Parameters:
  • pattern (Regexp) -- the pattern to match.
  • s (StringScanner) -- the `StringScanner` to scan.
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

def get_offset_from_hms(h, m, s)

Raises:
  • (InvalidPosixTimeZone) - if the mm and ss values are greater than

Returns:
  • (Integer) - the offset.

Parameters:
  • s (String) -- the seconds.
  • m (String) -- the minutes.
  • h (String) -- the hours.
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

def get_seconds_after_midnight_from_hms(h, m, s)

Raises:
  • (InvalidPosixTimeZone) - if the mm and ss values are greater than

Returns:
  • (Integer) - the number of seconds after midnight.

Parameters:
  • s (String) -- the seconds past the minute.
  • m (String) -- the minutes past the hour.
  • h (String) -- the hour.
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

def initialize(string_deduper)

Parameters:
  • string_deduper (StringDeduper) -- a {StringDeduper} instance to use

Other tags:
    Private: -
def initialize(string_deduper)
  @string_deduper = string_deduper
end

def parse(tz_string)

Raises:
  • (InvalidPosixTimeZone) - if `tz_string` is is not valid.
  • (InvalidPosixTimeZone) - if `tz_string` is not a `String`.

Returns:
  • (Object) - either a {TimezoneOffset} for a constantly applied

Parameters:
  • tz_string (String) -- the string to parse.
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(RubyCoreSupport.untaint(s[1] || s[2]))
  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(RubyCoreSupport.untaint(s[1] || s[2]))
    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

def parse_rule(s, type)

Returns:
  • (TransitionRule) - the parsed rule.

Raises:
  • (InvalidPosixTimeZone) - if the rule is not valid.

Parameters:
  • type (String) -- the type of rule (either `'start'` or `'end'`).
  • s (StringScanner) -- the `StringScanner` to read the rule from.
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