Hello community,

here is the log from the commit of package rubygem-tzinfo for openSUSE:Factory 
checked in at 2020-12-11 20:16:24
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/rubygem-tzinfo (Old)
 and      /work/SRC/openSUSE:Factory/.rubygem-tzinfo.new.2328 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "rubygem-tzinfo"

Fri Dec 11 20:16:24 2020 rev:24 rq:854708 version:2.0.3

Changes:
--------
--- /work/SRC/openSUSE:Factory/rubygem-tzinfo/rubygem-tzinfo.changes    
2020-05-11 13:40:11.636971802 +0200
+++ /work/SRC/openSUSE:Factory/.rubygem-tzinfo.new.2328/rubygem-tzinfo.changes  
2020-12-11 20:16:25.564652442 +0100
@@ -1,0 +2,14 @@
+Fri Dec 11 03:21:47 UTC 2020 - Manuel Schnitzer <[email protected]>
+
+- updated to version 2.0.3
+
+  * 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.
+
+-------------------------------------------------------------------

Old:
----
  tzinfo-2.0.2.gem

New:
----
  tzinfo-2.0.3.gem

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ rubygem-tzinfo.spec ++++++
--- /var/tmp/diff_new_pack.64Ss6k/_old  2020-12-11 20:16:26.424652799 +0100
+++ /var/tmp/diff_new_pack.64Ss6k/_new  2020-12-11 20:16:26.428652800 +0100
@@ -24,7 +24,7 @@
 #
 
 Name:           rubygem-tzinfo
-Version:        2.0.2
+Version:        2.0.3
 Release:        0
 %define mod_name tzinfo
 %define mod_full_name %{mod_name}-%{version}

++++++ tzinfo-2.0.2.gem -> tzinfo-2.0.3.gem ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/CHANGES.md new/CHANGES.md
--- old/CHANGES.md      2020-04-02 21:54:30.000000000 +0200
+++ new/CHANGES.md      2020-11-08 14:08:04.000000000 +0100
@@ -1,5 +1,17 @@
 # Changes
 
+## 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.
@@ -162,6 +174,16 @@
   `TZInfo::Country.get('US').zone_identifiers` should be used instead.
 
 
+## 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.
@@ -302,6 +324,30 @@
   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
+  (<https://mm.icann.org/pipermail/tz-announce/2020-April/000058.html>).
+
+
+## Version 0.3.56 (tzdata v2019c) - 1-Nov-2019
+
+* Updated to tzdata version 2019c
+  (<https://mm.icann.org/pipermail/tz-announce/2019-September/000057.html>).
+
+
+## Version 0.3.55 (tzdata v2018g) - 27-Oct-2018
+
+* Updated to tzdata version 2018g
+  (<https://mm.icann.org/pipermail/tz-announce/2018-October/000052.html>).
+
+
 ## Version 0.3.54 (tzdata v2018d) - 25-Mar-2018
 
 * Updated to tzdata version 2018d
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/README.md new/README.md
--- old/README.md       2020-04-02 21:54:30.000000000 +0200
+++ new/README.md       2020-11-08 14:08:04.000000000 +0100
@@ -1,6 +1,6 @@
 # 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/tzinfo/tzinfo?logo=travis)](https://travis-ci.org/tzinfo/tzinfo)
 [![AppVeyor 
Build](https://img.shields.io/appveyor/build/philr/tzinfo?logo=appveyor)](https://ci.appveyor.com/project/philr/tzinfo)
+[![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.
@@ -368,8 +368,8 @@
 
 ## Compatibility
 
-TZInfo v2.0.0 requires a minimum of Ruby MRI 1.9.3, JRuby 1.7 (in 1.9 mode or
-later) or Rubinius 3.
+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
Binary files old/checksums.yaml.gz and new/checksums.yaml.gz differ
Binary files old/checksums.yaml.gz.sig and new/checksums.yaml.gz.sig differ
Binary files old/data.tar.gz.sig and new/data.tar.gz.sig differ
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/lib/tzinfo/annual_rules.rb 
new/lib/tzinfo/annual_rules.rb
--- old/lib/tzinfo/annual_rules.rb      1970-01-01 01:00:00.000000000 +0100
+++ new/lib/tzinfo/annual_rules.rb      2020-11-08 14:08:04.000000000 +0100
@@ -0,0 +1,71 @@
+# 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<TimezoneTransition>] 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
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/lib/tzinfo/data_sources/posix_time_zone_parser.rb 
new/lib/tzinfo/data_sources/posix_time_zone_parser.rb
--- old/lib/tzinfo/data_sources/posix_time_zone_parser.rb       1970-01-01 
01:00:00.000000000 +0100
+++ new/lib/tzinfo/data_sources/posix_time_zone_parser.rb       2020-11-08 
14:08:04.000000000 +0100
@@ -0,0 +1,181 @@
+# 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
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/lib/tzinfo/data_sources/zoneinfo_data_source.rb 
new/lib/tzinfo/data_sources/zoneinfo_data_source.rb
--- old/lib/tzinfo/data_sources/zoneinfo_data_source.rb 2020-04-02 
21:54:30.000000000 +0200
+++ new/lib/tzinfo/data_sources/zoneinfo_data_source.rb 2020-11-08 
14:08:04.000000000 +0100
@@ -237,7 +237,10 @@
         @timezone_identifiers = load_timezone_identifiers.freeze
         @countries = load_countries(iso3166_tab_path, zone_tab_path).freeze
         @country_codes = @countries.keys.sort!.freeze
-        @zoneinfo_reader = ZoneinfoReader.new(ConcurrentStringDeduper.new)
+
+        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
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/lib/tzinfo/data_sources/zoneinfo_reader.rb 
new/lib/tzinfo/data_sources/zoneinfo_reader.rb
--- old/lib/tzinfo/data_sources/zoneinfo_reader.rb      2020-04-02 
21:54:30.000000000 +0200
+++ new/lib/tzinfo/data_sources/zoneinfo_reader.rb      2020-11-08 
14:08:04.000000000 +0100
@@ -14,11 +14,20 @@
 
     # 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
-      #   when deduping abbreviations.
-      def initialize(string_deduper)
+      #   to dedupe abbreviations.
+      def initialize(posix_tz_parser, string_deduper)
+        @posix_tz_parser = posix_tz_parser
         @string_deduper = string_deduper
       end
 
@@ -163,6 +172,168 @@
         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<TimezoneOffset>] 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<TimezoneOffset>] 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<TimezoneTransition>] the defined transitions.
+      # @param offsets [Array<TimezoneOffset>] 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 {|t| 
t.timestamp_value > last_defined.timestamp_value } +
+              (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.
       #
@@ -171,7 +342,7 @@
       #   {TimezoneTransition}s.
       # @raise [InvalidZoneinfoFile] if the file is not a valid zoneinfo file.
       def parse(file)
-        magic, version, ttisgmtcnt, ttisstdcnt, leapcnt, timecnt, typecnt, 
charcnt =
+        magic, version, ttisutccnt, ttisstdcnt, leapcnt, timecnt, typecnt, 
charcnt =
           check_read(file, 44).unpack('a4 a x15 NNNNNN')
 
         if magic != 'TZif'
@@ -180,11 +351,11 @@
 
         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 + 
ttisgmtcnt + ttisstdcnt, IO::SEEK_CUR)
+          file.seek(timecnt * 5 + typecnt * 6 + charcnt + leapcnt * 8 + 
ttisstdcnt + ttisutccnt, IO::SEEK_CUR)
 
           prev_version = version
 
-          magic, version, ttisgmtcnt, ttisstdcnt, leapcnt, timecnt, typecnt, 
charcnt =
+          magic, version, ttisutccnt, ttisstdcnt, leapcnt, timecnt, typecnt, 
charcnt =
             check_read(file, 44).unpack('a4 a x15 NNNNNN')
 
           unless magic == 'TZif' && (version == prev_version)
@@ -229,6 +400,23 @@
 
         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)
 
@@ -266,12 +454,16 @@
 
 
         if transitions.empty?
-          first_offset
+          if rules
+            apply_rules_without_transitions(file, first_offset, rules)
+          else
+            first_offset
+          end
         else
           previous_offset = first_offset
           previous_at = nil
 
-          transitions.map do |t|
+          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
@@ -280,6 +472,9 @@
             previous_at = at
             tt
           end
+
+          apply_rules_with_transitions(file, transitions, offsets, rules) if 
rules
+          transitions
         end
       end
     end
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/lib/tzinfo/time_with_offset.rb 
new/lib/tzinfo/time_with_offset.rb
--- old/lib/tzinfo/time_with_offset.rb  2020-04-02 21:54:30.000000000 +0200
+++ new/lib/tzinfo/time_with_offset.rb  2020-11-08 14:08:04.000000000 +0100
@@ -46,6 +46,22 @@
     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}.
     #
@@ -124,5 +140,15 @@
         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
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/lib/tzinfo/transition_rule.rb 
new/lib/tzinfo/transition_rule.rb
--- old/lib/tzinfo/transition_rule.rb   1970-01-01 01:00:00.000000000 +0100
+++ new/lib/tzinfo/transition_rule.rb   2020-11-08 14:08:04.000000000 +0100
@@ -0,0 +1,455 @@
+# 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
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/lib/tzinfo/version.rb new/lib/tzinfo/version.rb
--- old/lib/tzinfo/version.rb   2020-04-02 21:54:30.000000000 +0200
+++ new/lib/tzinfo/version.rb   2020-11-08 14:08:04.000000000 +0100
@@ -3,5 +3,5 @@
 
 module TZInfo
   # The TZInfo version number.
-  VERSION = '2.0.2'
+  VERSION = '2.0.3'
 end
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/lib/tzinfo.rb new/lib/tzinfo.rb
--- old/lib/tzinfo.rb   2020-04-02 21:54:30.000000000 +0200
+++ new/lib/tzinfo.rb   2020-11-08 14:08:04.000000000 +0100
@@ -25,6 +25,8 @@
 
 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'
@@ -35,6 +37,7 @@
 
 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'
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/metadata new/metadata
--- old/metadata        2020-04-02 21:54:30.000000000 +0200
+++ new/metadata        2020-11-08 14:08:04.000000000 +0100
@@ -1,7 +1,7 @@
 --- !ruby/object:Gem::Specification
 name: tzinfo
 version: !ruby/object:Gem::Version
-  version: 2.0.2
+  version: 2.0.3
 platform: ruby
 authors:
 - Philip Ross
@@ -29,7 +29,7 @@
   J3Zn/kSTjTekiaspyGbczC3PUaeJNxr+yCvR4sk71Xmk/GaKKGOHedJ1uj/LAXrA
   MR0mpl7b8zCg0PFC1J73uw==
   -----END CERTIFICATE-----
-date: 2020-04-02 00:00:00.000000000 Z
+date: 2020-11-08 00:00:00.000000000 Z
 dependencies:
 - !ruby/object:Gem::Dependency
   name: concurrent-ruby
@@ -60,6 +60,7 @@
 - LICENSE
 - README.md
 - lib/tzinfo.rb
+- lib/tzinfo/annual_rules.rb
 - lib/tzinfo/country.rb
 - lib/tzinfo/country_timezone.rb
 - lib/tzinfo/data_source.rb
@@ -68,6 +69,7 @@
 - lib/tzinfo/data_sources/country_info.rb
 - lib/tzinfo/data_sources/data_timezone_info.rb
 - lib/tzinfo/data_sources/linked_timezone_info.rb
+- lib/tzinfo/data_sources/posix_time_zone_parser.rb
 - lib/tzinfo/data_sources/ruby_data_source.rb
 - lib/tzinfo/data_sources/timezone_info.rb
 - lib/tzinfo/data_sources/transitions_data_timezone_info.rb
@@ -101,6 +103,7 @@
 - lib/tzinfo/timezone_period.rb
 - lib/tzinfo/timezone_proxy.rb
 - lib/tzinfo/timezone_transition.rb
+- lib/tzinfo/transition_rule.rb
 - lib/tzinfo/transitions_timezone_period.rb
 - lib/tzinfo/untaint_ext.rb
 - lib/tzinfo/version.rb
@@ -108,7 +111,12 @@
 homepage: https://tzinfo.github.io
 licenses:
 - MIT
-metadata: {}
+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.3
+  homepage_uri: https://tzinfo.github.io
+  source_code_uri: https://github.com/tzinfo/tzinfo/tree/v2.0.3
 post_install_message: 
 rdoc_options:
 - "--title"
@@ -128,7 +136,7 @@
     - !ruby/object:Gem::Version
       version: '0'
 requirements: []
-rubygems_version: 3.1.2
+rubygems_version: 3.1.4
 signing_key: 
 specification_version: 4
 summary: Time Zone Library
Binary files old/metadata.gz.sig and new/metadata.gz.sig differ
_______________________________________________
openSUSE Commits mailing list -- [email protected]
To unsubscribe, email [email protected]
List Netiquette: https://en.opensuse.org/openSUSE:Mailing_list_netiquette
List Archives: 
https://lists.opensuse.org/archives/list/[email protected]

Reply via email to