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 -[](https://rubygems.org/gems/tzinfo) [](https://travis-ci.org/tzinfo/tzinfo) [](https://ci.appveyor.com/project/philr/tzinfo) +[](https://rubygems.org/gems/tzinfo) [](https://travis-ci.com/github/tzinfo/tzinfo) [](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]
