Hello community,

here is the log from the commit of package rubygem-tzinfo for openSUSE:Factory 
checked in at 2017-04-11 09:33:14
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/rubygem-tzinfo (Old)
 and      /work/SRC/openSUSE:Factory/.rubygem-tzinfo.new (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "rubygem-tzinfo"

Tue Apr 11 09:33:14 2017 rev:18 rq:482693 version:1.2.3

Changes:
--------
--- /work/SRC/openSUSE:Factory/rubygem-tzinfo/rubygem-tzinfo.changes    
2014-10-18 09:10:14.000000000 +0200
+++ /work/SRC/openSUSE:Factory/.rubygem-tzinfo.new/rubygem-tzinfo.changes       
2017-04-11 09:33:19.889363911 +0200
@@ -1,0 +2,26 @@
+Sun Mar 26 04:36:44 UTC 2017 - co...@suse.com
+
+- updated to version 1.2.3
+ see installed CHANGES.md
+
+  Version 1.2.3 - 25-Mar-2017
+  ---------------------------
+  
+  * Reduce the number of String objects allocated when loading zoneinfo files.
+    #54.
+  * Make Timezone#friendly_identifier compatible with frozen string literals.
+  * Improve the algorithm for deriving the utc_offset from zoneinfo files. This
+    now correctly handles Pacific/Apia switching from one side of the
+    International Date Line to the other whilst observing daylight savings 
time.
+    #66.
+  * Fix an UnknownTimezone exception when calling transitions_up_to or
+    offsets_up_to on a TimezoneProxy instance obtained from Timezone.get_proxy.
+  * Allow the Factory zone to be obtained from the Zoneinfo data source.
+  * Ignore the /usr/share/zoneinfo/timeconfig symlink included in Slackware
+    distributions. #64.
+  * Fix Timezone#strftime handling of %Z expansion when %Z is prefixed with 
more
+    than one percent. #31.
+  * Support expansion of %z, %:z, %::z and %:::z to the UTC offset of the time
+    zone in Timezone#strftime. #31 and #67.
+
+-------------------------------------------------------------------

Old:
----
  tzinfo-1.2.2.gem

New:
----
  tzinfo-1.2.3.gem

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

Other differences:
------------------
++++++ rubygem-tzinfo.spec ++++++
--- /var/tmp/diff_new_pack.Ul8goM/_old  2017-04-11 09:33:20.833230578 +0200
+++ /var/tmp/diff_new_pack.Ul8goM/_new  2017-04-11 09:33:20.833230578 +0200
@@ -1,7 +1,7 @@
 #
 # spec file for package rubygem-tzinfo
 #
-# Copyright (c) 2014 SUSE LINUX Products GmbH, Nuernberg, Germany.
+# Copyright (c) 2017 SUSE LINUX GmbH, Nuernberg, Germany.
 #
 # All modifications and additions to the file contributed by third parties
 # remain the property of their copyright owners, unless otherwise agreed
@@ -16,8 +16,15 @@
 #
 
 
+#
+# This file was generated with a gem2rpm.yml and not just plain gem2rpm.
+# All sections marked as MANUAL, license headers, summaries and descriptions
+# can be maintained in that file. Please consult this file before editing any
+# of those fields
+#
+
 Name:           rubygem-tzinfo
-Version:        1.2.2
+Version:        1.2.3
 Release:        0
 %define mod_name tzinfo
 %define mod_full_name %{mod_name}-%{version}

++++++ tzinfo-1.2.2.gem -> tzinfo-1.2.3.gem ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/CHANGES.md new/CHANGES.md
--- old/CHANGES.md      2014-08-08 19:46:45.000000000 +0200
+++ new/CHANGES.md      2017-03-25 19:37:26.000000000 +0100
@@ -1,3 +1,24 @@
+Version 1.2.3 - 25-Mar-2017
+---------------------------
+
+* Reduce the number of String objects allocated when loading zoneinfo files.
+  #54.
+* Make Timezone#friendly_identifier compatible with frozen string literals.
+* Improve the algorithm for deriving the utc_offset from zoneinfo files. This
+  now correctly handles Pacific/Apia switching from one side of the
+  International Date Line to the other whilst observing daylight savings time.
+  #66.
+* Fix an UnknownTimezone exception when calling transitions_up_to or
+  offsets_up_to on a TimezoneProxy instance obtained from Timezone.get_proxy.
+* Allow the Factory zone to be obtained from the Zoneinfo data source.
+* Ignore the /usr/share/zoneinfo/timeconfig symlink included in Slackware
+  distributions. #64.
+* Fix Timezone#strftime handling of %Z expansion when %Z is prefixed with more
+  than one percent. #31.
+* Support expansion of %z, %:z, %::z and %:::z to the UTC offset of the time
+  zone in Timezone#strftime. #31 and #67.
+
+
 Version 1.2.2 - 8-Aug-2014
 --------------------------
 
@@ -92,6 +113,106 @@
   use other TimezonePeriod instance methods instead (issue #7655).
 
 
+Version 0.3.53 (tzdata v2017b) - 23-Mar-2017
+--------------------------------------------
+
+* Updated to tzdata version 2017b
+  (https://mm.icann.org/pipermail/tz-announce/2017-March/000046.html).
+
+
+Version 0.3.52 (tzdata v2016h) - 28-Oct-2016
+--------------------------------------------
+
+* Updated to tzdata version 2016h
+  (https://mm.icann.org/pipermail/tz-announce/2016-October/000042.html).
+
+
+Version 0.3.51 (tzdata v2016f) - 5-Jul-2016
+-------------------------------------------
+
+* Updated to tzdata version 2016f
+  (https://mm.icann.org/pipermail/tz-announce/2016-July/000040.html).
+
+
+Version 0.3.50 (tzdata v2016e) - 14-Jun-2016
+--------------------------------------------
+
+* Updated to tzdata version 2016e
+  (https://mm.icann.org/pipermail/tz-announce/2016-June/000039.html).
+
+
+Version 0.3.49 (tzdata v2016d) - 18-Apr-2016
+--------------------------------------------
+
+* Updated to tzdata version 2016d
+  (https://mm.icann.org/pipermail/tz-announce/2016-April/000038.html).
+
+
+Version 0.3.48 (tzdata v2016c) - 23-Mar-2016
+--------------------------------------------
+
+* Updated to tzdata version 2016c
+  (https://mm.icann.org/pipermail/tz-announce/2016-March/000037.html).
+
+
+Version 0.3.47 (tzdata v2016b) - 15-Mar-2016
+--------------------------------------------
+
+* Updated to tzdata version 2016b
+  (https://mm.icann.org/pipermail/tz-announce/2016-March/000036.html).
+
+
+Version 0.3.46 (tzdata v2015g) - 2-Dec-2015
+-------------------------------------------
+
+* From version 2015e, the IANA time zone database uses non-ASCII characters in
+  country names. Backport the encoding handling from TZInfo::Data to allow
+  TZInfo 0.3.x to support Ruby 1.9 (which would otherwise fail with an invalid
+  byte sequence error when loading the countries index). Resolves #41.
+
+
+Version 0.3.45 (tzdata v2015g) - 3-Oct-2015
+-------------------------------------------
+
+* Updated to tzdata version 2015g
+  (http://mm.icann.org/pipermail/tz-announce/2015-October/000034.html).
+
+
+Version 0.3.44 (tzdata v2015d) - 24-Apr-2015
+--------------------------------------------
+
+* Updated to tzdata version 2015d
+  (http://mm.icann.org/pipermail/tz-announce/2015-April/000031.html).
+
+
+Version 0.3.43 (tzdata v2015a) - 31-Jan-2015
+--------------------------------------------
+
+* Updated to tzdata version 2015a
+  (http://mm.icann.org/pipermail/tz-announce/2015-January/000028.html).
+
+
+Version 0.3.42 (tzdata v2014i) - 23-Oct-2014
+--------------------------------------------
+
+* Updated to tzdata version 2014i
+  (http://mm.icann.org/pipermail/tz-announce/2014-October/000026.html).
+
+
+Version 0.3.41 (tzdata v2014f) - 8-Aug-2014
+-------------------------------------------
+
+* Updated to tzdata version 2014f
+  (http://mm.icann.org/pipermail/tz-announce/2014-August/000023.html).
+
+
+Version 0.3.40 (tzdata v2014e) - 10-Jul-2014
+--------------------------------------------
+
+* Updated to tzdata version 2014e
+  (http://mm.icann.org/pipermail/tz-announce/2014-June/000022.html).
+
+
 Version 0.3.39 (tzdata v2014a) - 9-Mar-2014
 -------------------------------------------
 
@@ -177,7 +298,7 @@
 
 
  Version 0.3.28 (tzdata v2011g) - 13-Jun-2011
---------------------------------------------=
+---------------------------------------------
 
 * Add support for Ruby 1.9.3 (trunk revision 31668 and later). Thanks to 
   Aaron Patterson for reporting the problems running on the new version.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/LICENSE new/LICENSE
--- old/LICENSE 2014-08-08 19:46:45.000000000 +0200
+++ new/LICENSE 2017-03-25 19:37:26.000000000 +0100
@@ -1,4 +1,4 @@
-Copyright (c) 2005-2014 Philip Ross
+Copyright (c) 2005-2017 Philip Ross
 
 Permission is hereby granted, free of charge, to any person obtaining a copy 
of 
 this software and associated documentation files (the "Software"), to deal in 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/Rakefile new/Rakefile
--- old/Rakefile        2014-08-08 19:46:45.000000000 +0200
+++ new/Rakefile        2017-03-25 19:37:26.000000000 +0100
@@ -21,7 +21,10 @@
   private :orig_sh
 
   def sh(*cmd, &block)
-    if cmd.first =~ /\A__tar_with_owner__ -?([zjcvf]+)(.*)\z/
+    if cmd[0] == '__tar_with_owner__' && cmd[1] =~ /\A-?[zjcvf]+\z/
+      opts = cmd[1]
+      cmd = ['tar', 'c', '--owner', '0', '--group', '0', 
"#{opts.start_with?('-') ? '' : '-'}#{opts.gsub('c', '')}"] + cmd.drop(2)
+    elsif cmd.first =~ /\A__tar_with_owner__ -?([zjcvf]+)(.*)\z/
       opts = $1
       args = $2
       cmd[0] = "tar c --owner 0 --group 0 -#{opts.gsub('c', '')}#{args}"    
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/timezone.rb new/lib/tzinfo/timezone.rb
--- old/lib/tzinfo/timezone.rb  2014-08-08 19:46:45.000000000 +0200
+++ new/lib/tzinfo/timezone.rb  2017-03-25 19:37:26.000000000 +0100
@@ -236,13 +236,9 @@
       elsif parts.length == 1        
         parts[0]
       else
-        if skip_first_part
-          result = ''
-        else
-          result = parts[0] + ' - '
-        end
-        
-        parts[1, parts.length - 1].reverse_each {|part|
+        prefix = skip_first_part ? nil : "#{parts[0]} - "
+
+        parts = parts.drop(1).map do |part|
           part.gsub!(/_/, ' ')
           
           if part.index(/[a-z]/)
@@ -254,13 +250,11 @@
             # Missing an apostrophe if two consecutive upper case characters.
             part.gsub!(/([A-Z])([A-Z])/, '\1\'\2')
           end
-          
-          result << part
-          result << ', '
-        }
-        
-        result.slice!(result.length - 2, 2)
-        result
+
+          part
+        end
+
+        "#{prefix}#{parts.reverse.join(', ')}"
       end
     end
     
@@ -559,22 +553,60 @@
     
     alias :current_time_and_period :current_period_and_time
 
-    # Converts a time in UTC to local time and returns it as a string 
-    # according to the given format. The formatting is identical to 
-    # Time.strftime and DateTime.strftime, except %Z is replaced with the
-    # timezone abbreviation for the specified time (for example, EST or EDT).  
      
+    # Converts a time in UTC to local time and returns it as a string according
+    # to the given format.
+    #
+    # The formatting is identical to Time.strftime and DateTime.strftime, 
except
+    # %Z and %z are replaced with the timezone abbreviation (for example, EST 
or
+    # EDT) and offset for the specified Timezone and time.
+    #
+    # The offset can be formatted as follows:
+    #
+    # - %z - hour and minute (e.g. +0500)
+    # - %:z - hour and minute separated with a colon (e.g. +05:00)
+    # - %::z - hour minute and second separated with colons (e.g. +05:00:00)
+    # - %:::z - hour only (e.g. +05)
+    #
+    # Timezone#strftime currently handles the replacement of %z. From TZInfo
+    # version 2.0.0, %z will be passed to Time#strftime and DateTime#strftime
+    # instead. Some of the formatting options may cease to be available
+    # depending on the version of Ruby in use (for example, %:::z is only
+    # supported by Time#strftime from MRI version 2.0.0 onwards.)
     def strftime(format, utc = Time.now.utc)      
       period = period_for_utc(utc)
       local = period.to_local(utc)      
       local = Time.at(local).utc unless local.kind_of?(Time) || 
local.kind_of?(DateTime)
       abbreviation = period.abbreviation.to_s.gsub(/%/, '%%')
       
-      format = format.gsub(/(.?)%Z/) do
-        if $1 == '%'
-          # return %%Z so the real strftime treats it as a literal %Z too
-          '%%Z'
-        else
+      format = format.gsub(/%(%*)(Z|:*z)/) do
+        if $1.length.odd?
+          # Escaped literal percent or series of percents. Pass on to 
strftime.          
+          "#$1%#$2"
+        elsif $2 == "Z"
           "#$1#{abbreviation}"
+        else
+          m, s = period.utc_total_offset.divmod(60)
+          h, m = m.divmod(60)
+          case $2.length
+          when 1
+            "#$1#{'%+03d%02d' % [h,m]}"
+          when 2
+            "#$1#{'%+03d:%02d' % [h,m]}"
+          when 3
+            "#$1#{'%+03d:%02d:%02d' % [h,m,s]}"
+          when 4
+            "#$1#{'%+03d' % [h]}"
+          else # more than 3 colons - not a valid option
+            # Passing the invalid format string through to Time#strftime or
+            # DateTime#strtime would normally result in it being returned in 
the
+            # result. However, with Ruby 1.8.7 on Windows (as tested with Ruby
+            # 1.8.7-p374 from http://rubyinstaller.org/downloads/archives), 
this
+            # causes Time#strftime to always return an empty string (e.g.
+            # Time.now.strftime('a %::::z b') returns '').
+            #
+            # Escape the percent to force it to be evaluated as a literal.
+            "#$1%%#$2"
+          end
         end
       end
       
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/lib/tzinfo/timezone_offset.rb 
new/lib/tzinfo/timezone_offset.rb
--- old/lib/tzinfo/timezone_offset.rb   2014-08-08 19:46:45.000000000 +0200
+++ new/lib/tzinfo/timezone_offset.rb   2017-03-25 19:37:26.000000000 +0100
@@ -34,6 +34,8 @@
     
     # Converts a UTC Time, DateTime or integer timestamp to local time, based 
on 
     # the offset of this period.
+    #
+    # Deprecation warning: this method will be removed in TZInfo version 2.0.0.
     def to_local(utc)
       TimeOrDateTime.wrap(utc) {|wrapped|
         wrapped + @utc_total_offset
@@ -42,6 +44,8 @@
     
     # Converts a local Time, DateTime or integer timestamp to UTC, based on the
     # offset of this period.
+    #
+    # Deprecation warning: this method will be removed in TZInfo version 2.0.0.
     def to_utc(local)
       TimeOrDateTime.wrap(local) {|wrapped|
         wrapped - @utc_total_offset
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/lib/tzinfo/timezone_period.rb 
new/lib/tzinfo/timezone_period.rb
--- old/lib/tzinfo/timezone_period.rb   2014-08-08 19:46:45.000000000 +0200
+++ new/lib/tzinfo/timezone_period.rb   2017-03-25 19:37:26.000000000 +0100
@@ -126,45 +126,62 @@
     end
     
     # true if this period is valid for the given UTC DateTime; otherwise false.
+    #
+    # Deprecation warning: this method will be removed in TZInfo version 2.0.0.
     def valid_for_utc?(utc)
       utc_after_start?(utc) && utc_before_end?(utc) 
     end
     
     # true if the given UTC DateTime is after the start of the period 
     # (inclusive); otherwise false.
+    #
+    # Deprecation warning: this method will be removed in TZInfo version 2.0.0.
     def utc_after_start?(utc)
       !@start_transition || @start_transition.at <= utc
     end
     
     # true if the given UTC DateTime is before the end of the period 
     # (exclusive); otherwise false.
+    #
+    # Deprecation warning: this method will be removed in TZInfo version 2.0.0.
     def utc_before_end?(utc)
       !@end_transition || @end_transition.at > utc
     end
     
-    # true if this period is valid for the given local DateTime; otherwise 
false.
+    # true if this period is valid for the given local DateTime; otherwise
+    # false.
+    #
+    # Deprecation warning: this method will be removed in TZInfo version 2.0.0.
     def valid_for_local?(local)      
       local_after_start?(local) && local_before_end?(local) 
     end
     
     # true if the given local DateTime is after the start of the period 
     # (inclusive); otherwise false.
+    #
+    # Deprecation warning: this method will be removed in TZInfo version 2.0.0.
     def local_after_start?(local)
       !@start_transition || @start_transition.local_start_at <= local
     end
     
     # true if the given local DateTime is before the end of the period 
     # (exclusive); otherwise false.
+    #
+    # Deprecation warning: this method will be removed in TZInfo version 2.0.0.
     def local_before_end?(local)
       !@end_transition || @end_transition.local_end_at > local
     end
     
     # Converts a UTC DateTime to local time based on the offset of this period.
+    #
+    # Deprecation warning: this method will be removed in TZInfo version 2.0.0.
     def to_local(utc)
       @offset.to_local(utc)
     end
     
     # Converts a local DateTime to UTC based on the offset of this period.
+    #
+    # Deprecation warning: this method will be removed in TZInfo version 2.0.0.
     def to_utc(local)
       @offset.to_utc(local)
     end
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/lib/tzinfo/timezone_proxy.rb 
new/lib/tzinfo/timezone_proxy.rb
--- old/lib/tzinfo/timezone_proxy.rb    2014-08-08 19:46:45.000000000 +0200
+++ new/lib/tzinfo/timezone_proxy.rb    2017-03-25 19:37:26.000000000 +0100
@@ -38,6 +38,35 @@
       real_timezone.periods_for_local(local)
     end
     
+    # Returns an Array of TimezoneTransition instances representing the times
+    # where the UTC offset of the timezone changes.
+    #
+    # Transitions are returned up to a given date and time up to a given date
+    # and time (to).
+    #
+    # A from date and time may also be supplied using the from parameter. If
+    # from is not nil, only transitions from that date and time onwards will be
+    # returned.
+    #
+    # Comparisons with to are exclusive. Comparisons with from are inclusive.
+    # If a transition falls precisely on to, it will be excluded. If a
+    # transition falls on from, it will be included.
+    #
+    # Transitions returned are ordered by when they occur, from earliest to
+    # latest.
+    #
+    # to and from can be specified using either a Time, DateTime, Time or
+    # Timestamp.
+    #
+    # If from is specified and to is not greater than from, then an
+    # ArgumentError exception is raised.
+    #
+    # ArgumentError is raised if to is nil or of either to or from are
+    # Timestamps with unspecified offsets.
+    def transitions_up_to(to, from = nil)
+      real_timezone.transitions_up_to(to, from)
+    end
+
     # Returns the canonical zone for this Timezone.
     def canonical_zone
       real_timezone.canonical_zone
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/lib/tzinfo/zoneinfo_data_source.rb 
new/lib/tzinfo/zoneinfo_data_source.rb
--- old/lib/tzinfo/zoneinfo_data_source.rb      2014-08-08 19:46:45.000000000 
+0200
+++ new/lib/tzinfo/zoneinfo_data_source.rb      2017-03-25 19:37:26.000000000 
+0100
@@ -349,9 +349,9 @@
       # localtime current local timezone (may be a link).
       # posix, posixrules and right are directories containing other versions 
of the zoneinfo files.
       # src is a directory containing the tzdata source included on Solaris.
-      # Factory is the compiled in default timezone.
+      # timeconfig is a symlink included on Slackware.
       
-      enum_timezones(nil, ['+VERSION', 'localtime', 'posix', 'posixrules', 
'right', 'src', 'Factory']) do |identifier|
+      enum_timezones(nil, ['+VERSION', 'localtime', 'posix', 'posixrules', 
'right', 'src', 'timeconfig']) do |identifier|
         index << identifier
       end
       
@@ -432,7 +432,7 @@
             
             file_is_5_column = true if column5
             
-            zone_tab << [codes.split(','), zone_identifier, latitude, 
longitude, column4, column5]
+            zone_tab << [codes.split(','.freeze), zone_identifier, latitude, 
longitude, column4, column5]
           end
         end
       end
@@ -480,7 +480,7 @@
     def dms_to_rational(sign, degrees, minutes, seconds = nil)
       result = degrees.to_i + Rational(minutes.to_i, 60)
       result += Rational(seconds.to_i, 3600) if seconds
-      result = -result if sign == '-'
+      result = -result if sign == '-'.freeze
       result
     end
   end
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/lib/tzinfo/zoneinfo_timezone_info.rb 
new/lib/tzinfo/zoneinfo_timezone_info.rb
--- old/lib/tzinfo/zoneinfo_timezone_info.rb    2014-08-08 19:46:45.000000000 
+0200
+++ new/lib/tzinfo/zoneinfo_timezone_info.rb    2017-03-25 19:37:26.000000000 
+0100
@@ -56,39 +56,109 @@
         result
       end
       
-      # Zoneinfo doesn't include the offset from standard time (std_offset).
-      # Derive the missing offsets by looking at changes in the total UTC
-      # offset.
+      # Zoneinfo files don't include the offset from standard time (std_offset)
+      # for DST periods. Derive the base offset (utc_offset) where DST is
+      # observed from either the previous or next non-DST period.
       #
-      # This will be run through forwards and then backwards by the parse 
-      # method.
-      def derive_offsets(transitions, offsets)      
-        previous_offset = nil
+      # Returns the index of the offset to be used prior to the first
+      # transition.
+      def derive_offsets(transitions, offsets)
+        # The first non-DST offset (if there is one) is the offset observed
+        # before the first transition. Fallback to the first DST offset if 
there
+        # are no non-DST offsets.
+        first_non_dst_offset_index = offsets.index {|o| !o[:is_dst] }
+        first_offset_index = first_non_dst_offset_index || 0
+        return first_offset_index if transitions.empty?
+
+        # Determine the utc_offset of the next non-dst offset at each 
transition.
+        utc_offset_from_next = nil
+
+        transitions.reverse_each do |transition|
+          offset = offsets[transition[:offset]]
+          if offset[:is_dst]
+            transition[:utc_offset_from_next] = utc_offset_from_next if 
utc_offset_from_next
+          else
+            utc_offset_from_next = offset[:utc_total_offset]
+          end
+        end
 
-        transitions.each do |t|
-          offset = offsets[t[:offset]]
+        utc_offset_from_previous = first_non_dst_offset_index ? 
offsets[first_non_dst_offset_index][:utc_total_offset] : nil
+        defined_offsets = {}
+
+        transitions.each do |transition|
+          offset_index = transition[:offset]
+          offset = offsets[offset_index]
+          utc_total_offset = offset[:utc_total_offset]
+
+          if offset[:is_dst]
+            utc_offset_from_next = transition[:utc_offset_from_next]
+
+            difference_to_previous = utc_total_offset - 
(utc_offset_from_previous || utc_total_offset)
+            difference_to_next = utc_total_offset - (utc_offset_from_next || 
utc_total_offset)
+
+            utc_offset = if difference_to_previous > 0 && difference_to_next > 0
+              difference_to_previous < difference_to_next ? 
utc_offset_from_previous : utc_offset_from_next
+            elsif difference_to_previous > 0
+              utc_offset_from_previous
+            elsif difference_to_next > 0
+              utc_offset_from_next
+            else # difference_to_previous <= 0 && difference_to_next <= 0
+              # DST, but the either the offset has stayed the same or decreased
+              # relative to both the previous and next used base utc offset, or
+              # there are no non-DST offsets. Assume a 1 hour offset from base.
+              utc_total_offset - 3600
+            end
 
-          if !offset[:std_offset] && offset[:is_dst] && previous_offset
-            difference = offset[:utc_total_offset] - 
previous_offset[:utc_total_offset]
-            
-            if previous_offset[:is_dst]
-              if previous_offset[:std_offset]
-                std_offset = previous_offset[:std_offset] + difference
-              else
-                std_offset = nil
+            if !offset[:utc_offset]
+              offset[:utc_offset] = utc_offset
+              defined_offsets[offset] = offset_index
+            elsif offset[:utc_offset] != utc_offset
+              # An earlier transition has already derived a different
+              # utc_offset. Define a new offset or reuse an existing 
identically
+              # defined offset.
+              new_offset = offset.dup
+              new_offset[:utc_offset] = utc_offset
+
+              offset_index = defined_offsets[new_offset]
+
+              unless offset_index
+                offsets << new_offset
+                offset_index = offsets.length - 1
+                defined_offsets[new_offset] = offset_index
               end
-            else
-              std_offset = difference
-            end
-            
-            if std_offset && std_offset > 0
-              offset[:std_offset] = std_offset
-              offset[:utc_offset] = offset[:utc_total_offset] - std_offset
+
+              transition[:offset] = offset_index
             end
+          else
+            utc_offset_from_previous = utc_total_offset
           end
-          
-          previous_offset = offset
         end
+
+        first_offset_index
+      end
+
+      # Defines an offset for the timezone based on the given index and offset
+      # Hash.
+      def define_offset(index, offset)
+        utc_total_offset = offset[:utc_total_offset]
+        utc_offset = offset[:utc_offset]
+
+        if utc_offset
+          # DST offset with base utc_offset derived by derive_offsets.
+          std_offset = utc_total_offset - utc_offset
+        elsif offset[:is_dst]
+          # DST offset unreferenced by a transition (offset in use before the
+          # first transition). No derived base UTC offset, so assume 1 hour
+          # DST.
+          utc_offset = utc_total_offset - 3600
+          std_offset = 3600
+        else
+          # Non-DST offset.
+          utc_offset = utc_total_offset
+          std_offset = 0
+        end
+
+        offset index, utc_offset, std_offset, offset[:abbr].untaint.to_sym
       end
       
       # Parses a zoneinfo file and intializes the DataTimezoneInfo structures.
@@ -127,27 +197,27 @@
         transitions = []
         
         if using_64bit
-          (0...timecnt).each do |i|
-            high, low = check_read(file, 8).unpack('NN')
+          timecnt.times do |i|
+            high, low = check_read(file, 8).unpack('NN'.freeze)
             transition_time = make_signed_int64(high, low)
             transitions << {:at => transition_time}          
           end
         else
-          (0...timecnt).each do |i|
-            transition_time = make_signed_int32(check_read(file, 
4).unpack('N')[0])
+          timecnt.times do |i|
+            transition_time = make_signed_int32(check_read(file, 
4).unpack('N'.freeze)[0])
             transitions << {:at => transition_time}          
           end
         end
         
-        (0...timecnt).each do |i|
-          localtime_type = check_read(file, 1).unpack('C')[0]
+        timecnt.times do |i|
+          localtime_type = check_read(file, 1).unpack('C'.freeze)[0]
           transitions[i][:offset] = localtime_type
         end
         
         offsets = []
         
-        (0...typecnt).each do |i|
-          gmtoff, isdst, abbrind = check_read(file, 6).unpack('NCC')
+        typecnt.times do |i|
+          gmtoff, isdst, abbrind = check_read(file, 6).unpack('NCC'.freeze)
           gmtoff = make_signed_int32(gmtoff)
           isdst = isdst == 1
           offset = {:utc_total_offset => gmtoff, :is_dst => isdst, :abbr_index 
=> abbrind}
@@ -179,33 +249,12 @@
         end
         
         # Derive the offsets from standard time (std_offset).
-        derive_offsets(transitions, offsets)
-        derive_offsets(transitions.reverse, offsets)
-        
-        # Assign anything left a standard offset of one hour
-        offsets.each do |o|
-          if !o[:std_offset] && o[:is_dst]
-            o[:std_offset] = 3600
-            o[:utc_offset] = o[:utc_total_offset] - 3600
-          end
-        end
-        
-        # Find the first non-dst offset. This is used as the offset for the 
time
-        # before the first transition.
-        first = nil
-        offsets.each_with_index do |o, i|
-          if !o[:is_dst]
-            first = i
-            break
-          end
-        end
+        first_offset_index = derive_offsets(transitions, offsets)
         
-        if first
-          offset first, offsets[first][:utc_offset], 
offsets[first][:std_offset], offsets[first][:abbr].untaint.to_sym
-        end
+        define_offset(first_offset_index, offsets[first_offset_index])
         
         offsets.each_with_index do |o, i|
-          offset i, o[:utc_offset], o[:std_offset], o[:abbr].untaint.to_sym 
unless i == first
+          define_offset(i, o) unless i == first_offset_index
         end
 
         if !using_64bit && !RubyCoreSupport.time_supports_negative
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/metadata new/metadata
--- old/metadata        2014-08-08 19:46:45.000000000 +0200
+++ new/metadata        2017-03-25 19:37:26.000000000 +0100
@@ -1,7 +1,7 @@
 --- !ruby/object:Gem::Specification
 name: tzinfo
 version: !ruby/object:Gem::Version
-  version: 1.2.2
+  version: 1.2.3
 platform: ruby
 authors:
 - Philip Ross
@@ -12,7 +12,7 @@
   -----BEGIN CERTIFICATE-----
   MIIDdDCCAlygAwIBAgIBATANBgkqhkiG9w0BAQUFADBAMRIwEAYDVQQDDAlwaGls
   LnJvc3MxFTATBgoJkiaJk/IsZAEZFgVnbWFpbDETMBEGCgmSJomT8ixkARkWA2Nv
-  bTAeFw0xMzA5MjUyMTA0NTNaFw0xNDA5MjUyMTA0NTNaMEAxEjAQBgNVBAMMCXBo
+  bTAeFw0xNjEwMjAxOTMyMDZaFw0xNzEwMjAxOTMyMDZaMEAxEjAQBgNVBAMMCXBo
   aWwucm9zczEVMBMGCgmSJomT8ixkARkWBWdtYWlsMRMwEQYKCZImiZPyLGQBGRYD
   Y29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAkZzB+qfhmyY+XRvU
   u310LMTGsTkR4/8JFCMF0YeQX6ZKmLr1fKzF3At1+DlI+v0t/G2FS6Dic0V3l8MK
@@ -23,14 +23,14 @@
   TcNLCQIDAQABo3kwdzAJBgNVHRMEAjAAMAsGA1UdDwQEAwIEsDAdBgNVHQ4EFgQU
   D5nzO9/MG4B6ygch/Pv6PF9Q5x8wHgYDVR0RBBcwFYETcGhpbC5yb3NzQGdtYWls
   LmNvbTAeBgNVHRIEFzAVgRNwaGlsLnJvc3NAZ21haWwuY29tMA0GCSqGSIb3DQEB
-  BQUAA4IBAQAKZJXA++aLjISMKZea4PmXuH93YbMxoyBby3SRfwvLh7cBMEiCy5fu
-  xYR46qa9ixC6JyVuxAWA2AGHLOqabKkq6AxntqIk1OAnZGBNRuCnLYzSx+6YDjaY
-  ZcAmqPdS0Adj+1lNc+MgHiMrMLimNO4Cur4w4zYNZFvQan78WtLnwiaYPM2Tke1B
-  UVjGvQVkM6gVIVH3937au2iHpJAehbhkEbgM02knNemiNwi58j7pMS9MhelxJxdz
-  fs7XSYlwQp0zY7PFSMwJeBpQFDBnShcweRQ+0QdUUS4FHrwfXex0QsXp9UROUX+4
-  6BVw9ZDNFnDH4PQjZGbdwanB7kzm+TEi
+  BQUAA4IBAQBM+pMz41DnLx/Edg6cZe7JYFeXXQmVeltwDEefCa4cXxfLTsR6m7vW
+  aBxCCJ62qrfe2dF1d8lp5X94nAmG8FyzSH4Gt8Ul69zOLw31E5XkT2bDcBTzWwcf
+  OmYp+4rBeXWVwf76baYDNrJyFBp42cuj3vQBOQ2mJcwjeBldyUFVxElq93ISpN+2
+  xSO5T8UfFZWHwv9H9cGhQnInu/hpl/vFcz5LM/l1CODRITfEbNUlr6Lb4JLxm58y
+  sB3eS05Xw5lTvyhTICdMJIRk5jPPk3Sv/H1G7urfugkdEqT66FO+pgBnC9o7HvXN
+  E2bpXUbNbgEUfOfgi7vQ9NLDfb+3Brxl
   -----END CERTIFICATE-----
-date: 2014-08-08 00:00:00.000000000 Z
+date: 2017-03-25 00:00:00.000000000 Z
 dependencies:
 - !ruby/object:Gem::Dependency
   name: thread_safe
@@ -185,7 +185,7 @@
       version: '0'
 requirements: []
 rubyforge_project: 
-rubygems_version: 2.2.2
+rubygems_version: 2.6.11
 signing_key: 
 specification_version: 4
 summary: Daylight savings aware timezone library
Binary files old/metadata.gz.sig and new/metadata.gz.sig differ
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/test/tc_country.rb new/test/tc_country.rb
--- old/test/tc_country.rb      2014-08-08 19:46:45.000000000 +0200
+++ new/test/tc_country.rb      2017-03-25 19:37:26.000000000 +0100
@@ -47,7 +47,7 @@
     Country.get('GB')
   
     safe_test do
-      code = 'GB'.taint
+      code = 'GB'.dup.taint
       assert(code.tainted?)
       country = Country.get(code)
       assert_equal('GB', country.code)
@@ -59,14 +59,14 @@
     Country.get('GB')
   
     safe_test do
-      country = Country.get('GB'.taint.freeze)
+      country = Country.get('GB'.dup.taint.freeze)
       assert_equal('GB', country.code)
     end
   end
   
   def test_get_tainted_not_previously_loaded
     safe_test do
-      code = 'GB'.taint
+      code = 'GB'.dup.taint
       assert(code.tainted?)
       country = Country.get(code)
       assert_equal('GB', country.code)
@@ -76,7 +76,7 @@
   
   def test_get_tainted_and_frozen_not_previously_loaded
     safe_test do
-      country = Country.get('GB'.taint.freeze)
+      country = Country.get('GB'.dup.taint.freeze)
       assert_equal('GB', country.code)
     end
   end
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/test/tc_ruby_data_source.rb 
new/test/tc_ruby_data_source.rb
--- old/test/tc_ruby_data_source.rb     2014-08-08 19:46:45.000000000 +0200
+++ new/test/tc_ruby_data_source.rb     2017-03-25 19:37:26.000000000 +0100
@@ -56,7 +56,7 @@
 
   def test_load_timezone_info_tainted
     safe_test do
-      identifier = 'Europe/Amsterdam'.taint
+      identifier = 'Europe/Amsterdam'.dup.taint
       assert(identifier.tainted?)
       info = @data_source.load_timezone_info(identifier)
       assert_equal('Europe/Amsterdam', info.identifier)
@@ -66,7 +66,7 @@
   
   def test_load_timezone_info_tainted_and_frozen
     safe_test do
-      info = @data_source.load_timezone_info('Europe/Amsterdam'.taint.freeze)
+      info = 
@data_source.load_timezone_info('Europe/Amsterdam'.dup.taint.freeze)
       assert_equal('Europe/Amsterdam', info.identifier)
     end
   end
@@ -120,7 +120,7 @@
   
   def test_load_country_info_tainted
     safe_test do
-      code = 'NL'.taint
+      code = 'NL'.dup.taint
       assert(code.tainted?)
       info = @data_source.load_country_info(code)
       assert_equal('NL', info.code)
@@ -130,7 +130,7 @@
   
   def test_load_country_info_tainted_and_frozen
     safe_test do
-      info = @data_source.load_country_info('NL'.taint.freeze)
+      info = @data_source.load_country_info('NL'.dup.taint.freeze)
       assert_equal('NL', info.code)
     end
   end
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/test/tc_timezone.rb new/test/tc_timezone.rb
--- old/test/tc_timezone.rb     2014-08-08 19:46:45.000000000 +0200
+++ new/test/tc_timezone.rb     2017-03-25 19:37:26.000000000 +0100
@@ -243,7 +243,7 @@
     Timezone.get('Europe/Andorra')
   
     safe_test do
-      identifier = 'Europe/Andorra'.taint
+      identifier = 'Europe/Andorra'.dup.taint
       assert(identifier.tainted?)
       tz = Timezone.get(identifier)
       assert_equal('Europe/Andorra', tz.identifier)
@@ -255,14 +255,14 @@
     Timezone.get('Europe/Andorra')
   
     safe_test do
-      tz = Timezone.get('Europe/Andorra'.taint.freeze)
+      tz = Timezone.get('Europe/Andorra'.dup.taint.freeze)
       assert_equal('Europe/Andorra', tz.identifier)
     end
   end
   
   def test_get_tainted_not_previously_loaded
     safe_test do
-      identifier = 'Europe/Andorra'.taint
+      identifier = 'Europe/Andorra'.dup.taint
       assert(identifier.tainted?)
       tz = Timezone.get(identifier)
       assert_equal('Europe/Andorra', tz.identifier)
@@ -272,7 +272,7 @@
   
   def test_get_tainted_and_frozen_not_previously_loaded
     safe_test do
-      tz = Timezone.get('Europe/Amsterdam'.taint.freeze)
+      tz = Timezone.get('Europe/Amsterdam'.dup.taint.freeze)
       assert_equal('Europe/Amsterdam', tz.identifier)
     end
   end
@@ -436,6 +436,13 @@
     assert_equal('UTC', TestTimezone.new('UTC').friendly_identifier)
   end
   
+  if defined?(Encoding)
+    def test_friendly_identifier_non_binary_encoding
+      refute_equal(Encoding::ASCII_8BIT, 
TestTimezone.new('Europe/Paris').friendly_identifier(true).encoding)
+      refute_equal(Encoding::ASCII_8BIT, 
TestTimezone.new('Europe/Paris').friendly_identifier(false).encoding)
+    end
+  end
+
   def test_to_s
     assert_equal('Europe - Paris', TestTimezone.new('Europe/Paris').to_s)
     assert_equal('America - Knox, Indiana', 
TestTimezone.new('America/Indiana/Knox').to_s)
@@ -1246,26 +1253,35 @@
   
   def test_strftime_datetime
     tz = Timezone.get('Europe/London')
-    assert_equal('23:12:02 BST', tz.strftime('%H:%M:%S %Z', DateTime.new(2006, 
7, 15, 22, 12, 2)))
-    assert_equal('BST', tz.strftime('%Z', DateTime.new(2006, 7, 15, 22, 12, 
2)))
-    assert_equal('%ZBST', tz.strftime('%%Z%Z', DateTime.new(2006, 7, 15, 22, 
12, 2)))
-    assert_equal('BST BST', tz.strftime('%Z %Z', DateTime.new(2006, 7, 15, 22, 
12, 2)))
+    dt = DateTime.new(2006, 7, 15, 22, 12, 2)
+    assert_equal('23:12:02 BST', tz.strftime('%H:%M:%S %Z', dt))
+    assert_equal('BST', tz.strftime('%Z', dt))
+    assert_equal('%ZBST', tz.strftime('%%Z%Z', dt))
+    assert_equal('BST BST', tz.strftime('%Z %Z', dt))
+    assert_equal('BST %Z %BST %%Z %%BST', tz.strftime('%Z %%Z %%%Z %%%%Z 
%%%%%Z', dt))
+    assert_equal('+0100 +01:00 +01:00:00 +01 %::::z', tz.strftime('%z %:z %::z 
%:::z %::::z', dt))
   end
   
   def test_strftime_time
     tz = Timezone.get('Europe/London')
-    assert_equal('23:12:02 BST', tz.strftime('%H:%M:%S %Z', Time.utc(2006, 7, 
15, 22, 12, 2)))
-    assert_equal('BST', tz.strftime('%Z', Time.utc(2006, 7, 15, 22, 12, 2)))
-    assert_equal('%ZBST', tz.strftime('%%Z%Z', Time.utc(2006, 7, 15, 22, 12, 
2)))
-    assert_equal('BST BST', tz.strftime('%Z %Z', Time.utc(2006, 7, 15, 22, 12, 
2)))
+    t = Time.utc(2006, 7, 15, 22, 12, 2)
+    assert_equal('23:12:02 BST', tz.strftime('%H:%M:%S %Z', t))
+    assert_equal('BST', tz.strftime('%Z', t))
+    assert_equal('%ZBST', tz.strftime('%%Z%Z', t))
+    assert_equal('BST BST', tz.strftime('%Z %Z', t))
+    assert_equal('BST %Z %BST %%Z %%BST', tz.strftime('%Z %%Z %%%Z %%%%Z 
%%%%%Z', t))
+    assert_equal('+0100 +01:00 +01:00:00 +01 %::::z', tz.strftime('%z %:z %::z 
%:::z %::::z', t))
   end
   
   def test_strftime_int
     tz = Timezone.get('Europe/London')
-    assert_equal('23:12:02 BST', tz.strftime('%H:%M:%S %Z', Time.utc(2006, 7, 
15, 22, 12, 2).to_i))
-    assert_equal('BST', tz.strftime('%Z', Time.utc(2006, 7, 15, 22, 12, 
2).to_i))
-    assert_equal('%ZBST', tz.strftime('%%Z%Z', Time.utc(2006, 7, 15, 22, 12, 
2).to_i))
-    assert_equal('BST BST', tz.strftime('%Z %Z', Time.utc(2006, 7, 15, 22, 12, 
2).to_i))
+    i = Time.utc(2006, 7, 15, 22, 12, 2).to_i
+    assert_equal('23:12:02 BST', tz.strftime('%H:%M:%S %Z', i))
+    assert_equal('BST', tz.strftime('%Z', i))
+    assert_equal('%ZBST', tz.strftime('%%Z%Z', i))
+    assert_equal('BST BST', tz.strftime('%Z %Z', i))
+    assert_equal('BST %Z %BST %%Z %%BST', tz.strftime('%Z %%Z %%%Z %%%%Z 
%%%%%Z', i))
+    assert_equal('+0100 +01:00 +01:00:00 +01 %::::z', tz.strftime('%z %:z %::z 
%:::z %::::z', i))
   end
   
   def test_get_missing_data_source
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/test/tc_timezone_proxy.rb 
new/test/tc_timezone_proxy.rb
--- old/test/tc_timezone_proxy.rb       2014-08-08 19:46:45.000000000 +0200
+++ new/test/tc_timezone_proxy.rb       2017-03-25 19:37:26.000000000 +0100
@@ -5,45 +5,59 @@
 class TCTimezoneProxy < Minitest::Test
   def test_not_exist
     proxy = TimezoneProxy.new('Nothing/Special')
+    t = Time.utc(2006,1,1,0,0,0)
     assert_equal('Nothing/Special', proxy.identifier)
-    assert_raises(InvalidTimezoneIdentifier) { proxy.now }
+    assert_equal('Nothing/Special', proxy.name)
+    assert_equal('Nothing - Special', proxy.friendly_identifier)
+    assert_equal('Nothing - Special', proxy.to_s)
+
+    assert_raises(InvalidTimezoneIdentifier) { proxy.canonical_identifier }
+    assert_raises(InvalidTimezoneIdentifier) { proxy.canonical_zone }
     assert_raises(InvalidTimezoneIdentifier) { proxy.current_period }
     assert_raises(InvalidTimezoneIdentifier) { proxy.current_period_and_time }
     assert_raises(InvalidTimezoneIdentifier) { proxy.current_time_and_period }
-    assert_raises(InvalidTimezoneIdentifier) { 
proxy.utc_to_local(DateTime.new(2006,1,1,0,0,0)) }
-    assert_raises(InvalidTimezoneIdentifier) { 
proxy.local_to_utc(DateTime.new(2006,1,1,0,0,0)) }
-    assert_raises(InvalidTimezoneIdentifier) { 
proxy.period_for_utc(DateTime.new(2006,1,1,0,0,0)) }
-    assert_raises(InvalidTimezoneIdentifier) { 
proxy.period_for_local(DateTime.new(2006,1,1,0,0,0)) }
-    assert_raises(InvalidTimezoneIdentifier) { proxy.canonical_identifier }
-    assert_raises(InvalidTimezoneIdentifier) { proxy.canonical_zone }
+    assert_raises(InvalidTimezoneIdentifier) { proxy.local_to_utc(t) }
+    assert_raises(InvalidTimezoneIdentifier) { proxy.now }
+    assert_raises(InvalidTimezoneIdentifier) { proxy.offsets_up_to(t) }
+    assert_raises(InvalidTimezoneIdentifier) { proxy.period_for_local(t) }
+    assert_raises(InvalidTimezoneIdentifier) { proxy.period_for_utc(t) }
+    assert_raises(InvalidTimezoneIdentifier) { proxy.periods_for_local(t) }
+    assert_raises(InvalidTimezoneIdentifier) { proxy.strftime('%Z', t) }
+    assert_raises(InvalidTimezoneIdentifier) { proxy.transitions_up_to(t) }
+    assert_raises(InvalidTimezoneIdentifier) { proxy.utc_to_local(t) }
   end
   
   def test_valid
     proxy = TimezoneProxy.new('Europe/London')
-    assert_equal('Europe/London', proxy.identifier)
-    
-    assert_nothing_raised { proxy.now }
+    real = Timezone.get('Europe/London')
+
+    t1 = Time.utc(2005,8,1,0,0,0)
+    t2 = Time.utc(2004,8,1,0,0,0)
+
+    assert_equal(real.canonical_identifier, proxy.canonical_identifier)
+    assert_same(real.canonical_zone, proxy.canonical_zone)
     assert_nothing_raised { proxy.current_period }
     assert_nothing_raised { proxy.current_period_and_time }
     assert_nothing_raised { proxy.current_time_and_period }
-    
-    real = Timezone.get('Europe/London')
-    
-    assert_equal(real.utc_to_local(DateTime.new(2005,8,1,0,0,0)), 
proxy.utc_to_local(DateTime.new(2005,8,1,0,0,0)))
-    assert_equal(real.local_to_utc(DateTime.new(2005,8,1,0,0,0)), 
proxy.local_to_utc(DateTime.new(2005,8,1,0,0,0)))
-    assert_equal(real.period_for_utc(DateTime.new(2005,8,1,0,0,0)), 
proxy.period_for_utc(DateTime.new(2005,8,1,0,0,0)))
-    assert_equal(real.period_for_local(DateTime.new(2005,8,1,0,0,0)), 
proxy.period_for_local(DateTime.new(2005,8,1,0,0,0)))
-    assert_equal(real.identifier, proxy.identifier)
-    assert_equal(real.name, proxy.name)
-    assert_equal(real.to_s, proxy.to_s)
     assert_equal(real.friendly_identifier(true), 
proxy.friendly_identifier(true))
     assert_equal(real.friendly_identifier(false), 
proxy.friendly_identifier(false))
     assert_equal(real.friendly_identifier, proxy.friendly_identifier)
-    assert_equal(real.canonical_identifier, proxy.canonical_identifier)
-    assert_same(real.canonical_zone, proxy.canonical_zone)
-    
-    assert_equal('Europe/London', proxy.identifier)
-    
+    assert_equal(real.identifier, proxy.identifier)
+    assert_equal(real.local_to_utc(t1), proxy.local_to_utc(t1))
+    assert_equal(real.name, proxy.name)
+    assert_nothing_raised { proxy.now }
+    assert_equal(real.offsets_up_to(t1), proxy.offsets_up_to(t1))
+    assert_equal(real.offsets_up_to(t1, t2), proxy.offsets_up_to(t1, t2))
+    assert_equal(real.period_for_local(t1), proxy.period_for_local(t1))
+    assert_equal(real.period_for_utc(t1), proxy.period_for_utc(t1))
+    assert_equal(real.periods_for_local(t1), proxy.periods_for_local(t1))
+    assert_equal(real.strftime('%Z', t1), proxy.strftime('%Z', t1))
+    assert_equal(real.to_s, proxy.to_s)
+    assert_equal(real.transitions_up_to(t1), proxy.transitions_up_to(t1))
+    assert_equal(real.transitions_up_to(t1, t2), proxy.transitions_up_to(t1, 
t2))
+    assert_equal(real.utc_to_local(t1), proxy.utc_to_local(t1))
+
+
     assert(real == proxy)
     assert(proxy == real)
     assert_equal(0, real <=> proxy)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/test/tc_zoneinfo_data_source.rb 
new/test/tc_zoneinfo_data_source.rb
--- old/test/tc_zoneinfo_data_source.rb 2014-08-08 19:46:45.000000000 +0200
+++ new/test/tc_zoneinfo_data_source.rb 2017-03-25 19:37:26.000000000 +0100
@@ -395,6 +395,41 @@
     end
   end
   
+  def test_load_timezone_info_ignored_timeconfig_symlink
+    # Slackware includes a symlink named timeconfig that points at 
/usr/sbin/timeconfig.
+
+    Dir.mktmpdir('tzinfo_test_target') do |target_dir|
+      target_path = File.join(target_dir, 'timeconfig')
+
+      File.open(target_path, 'w') do |f|
+        f.write("#!/bin/sh\n")
+        f.write("#\n")
+        f.write('# timeconfig         Slackware Linux timezone configuration 
utility.\n')
+      end
+
+      Dir.mktmpdir('tzinfo_test') do |dir|
+        FileUtils.touch(File.join(dir, 'zone.tab'))
+        FileUtils.touch(File.join(dir, 'iso3166.tab'))
+        FileUtils.cp(File.join(@data_source.zoneinfo_dir, 'EST'), 
File.join(dir, 'EST'))
+
+        symlink_path = File.join(dir, 'timeconfig')
+        begin
+          FileUtils.ln_s(target_path, symlink_path)
+        rescue NotImplementedError, Errno::EACCES
+          # Symlinks not supported on this platform, or permission denied
+          # (administrative rights are required on Windows). Copy instead.
+          FileUtils.cp(target_path, symlink_path)
+        end
+
+        data_source = ZoneinfoDataSource.new(dir)
+
+        assert_raises(InvalidTimezoneIdentifier) do
+          data_source.load_timezone_info('timeconfig')
+        end
+      end
+    end
+  end
+
   def test_load_timezone_info_nil
     assert_raises(InvalidTimezoneIdentifier) do
       @data_source.load_timezone_info(nil)
@@ -453,8 +488,9 @@
         
         begin
           FileUtils.ln_s(outside_file, file)
-        rescue NotImplementedError
-          # Symlinks not supported on this platform - skip test
+        rescue NotImplementedError, Errno::EACCES
+          # Symlinks not supported on this platform, or permission denied
+          # (administrative rights are required on Windows). Skip test.
           return
         end
         
@@ -478,8 +514,9 @@
       
       begin  
         FileUtils.ln_s(File.join(File.expand_path(dir), 'EST'), link)      
-      rescue NotImplementedError
-        # Symlinks not supported on this platform - skip test
+      rescue NotImplementedError, Errno::EACCES
+        # Symlinks not supported on this platform, or permission denied
+        # (administrative rights are required on Windows). Skip test.
         return
       end
       
@@ -505,8 +542,9 @@
       
       begin
         FileUtils.ln_s('../outside', link)
-      rescue NotImplementedError
-        # Symlinks not supported on this platform - skip test
+      rescue NotImplementedError, Errno::EACCES
+        # Symlinks not supported on this platform, or permission denied
+        # (administrative rights are required on Windows). Skip test.
         return
       end
       
@@ -541,8 +579,9 @@
       subdir_link = File.join(subdir, 'Link')
       begin
         FileUtils.ln_s('../Subdir/EST', subdir_link)
-      rescue NotImplementedError
-        # Symlinks not supported on this platform - skip test
+      rescue NotImplementedError, Errno::EACCES
+        # Symlinks not supported on this platform, or permission denied
+        # (administrative rights are required on Windows). Skip test.
         return
       end
       
@@ -613,7 +652,7 @@
 
   def test_load_timezone_info_tainted
     safe_test do
-      identifier = 'Europe/Amsterdam'.taint
+      identifier = 'Europe/Amsterdam'.dup.taint
       assert(identifier.tainted?)
       info = @data_source.load_timezone_info(identifier)
       assert_equal('Europe/Amsterdam', info.identifier)
@@ -623,7 +662,7 @@
   
   def test_load_timezone_info_tainted_and_frozen
     safe_test do
-      info = @data_source.load_timezone_info('Europe/Amsterdam'.taint.freeze)
+      info = 
@data_source.load_timezone_info('Europe/Amsterdam'.dup.taint.freeze)
       assert_equal('Europe/Amsterdam', info.identifier)
     end
   end
@@ -653,11 +692,11 @@
        
     entries = entries.collect {|file| file[directory.length + 
File::SEPARATOR.length, file.length - directory.length - 
File::SEPARATOR.length]}
 
-    # Exclude right (with leapseconds) and posix (copy) directories; .tab 
files; localtime, posixrules and Factory zones
+    # Exclude right (with leapseconds) and posix (copy) directories; .tab 
files; localtime and posixrules files.
     entries = entries.select do |file| 
       file !~ /\A(posix|right)\// &&
         file !~ /\.tab\z/ &&
-        !%w(localtime posixrules Factory).include?(file)
+        !%w(localtime posixrules).include?(file)
     end
     
     entries.sort
@@ -714,6 +753,38 @@
     end
   end
   
+  def test_timezone_identifiers_ignored_timeconfig_symlink
+    # Slackware includes a symlink named timeconfig that points at 
/usr/sbin/timeconfig.
+
+    Dir.mktmpdir('tzinfo_test_target') do |target_dir|
+      target_path = File.join(target_dir, 'timeconfig')
+
+      File.open(target_path, 'w') do |f|
+        f.write("#!/bin/sh\n")
+        f.write("#\n")
+        f.write('# timeconfig         Slackware Linux timezone configuration 
utility.\n')
+      end
+
+      Dir.mktmpdir('tzinfo_test') do |dir|
+        FileUtils.touch(File.join(dir, 'zone.tab'))
+        FileUtils.touch(File.join(dir, 'iso3166.tab'))
+        FileUtils.cp(File.join(@data_source.zoneinfo_dir, 'EST'), 
File.join(dir, 'EST'))
+
+        symlink_path = File.join(dir, 'timeconfig')
+        begin
+          FileUtils.ln_s(target_path, symlink_path)
+        rescue NotImplementedError, Errno::EACCES
+          # Symlinks not supported on this platform, or permission denied
+          # (administrative rights are required on Windows). Copy instead.
+          FileUtils.cp(target_path, symlink_path)
+        end
+
+        data_source = ZoneinfoDataSource.new(dir)
+        assert_array_same_items(['EST'], data_source.timezone_identifiers)
+      end
+    end
+  end
+
   def test_timezone_identifiers_ignored_src_directory
     # Solaris includes a src directory containing the source timezone data 
files
     # from the tzdata distribution. These should be ignored.
@@ -768,7 +839,7 @@
   
   def test_load_country_info_tainted
     safe_test do
-      code = 'NL'.taint
+      code = 'NL'.dup.taint
       assert(code.tainted?)
       info = @data_source.load_country_info(code)
       assert_equal('NL', info.code)
@@ -778,7 +849,7 @@
   
   def test_load_country_info_tainted_and_frozen
     safe_test do
-      info = @data_source.load_country_info('NL'.taint.freeze)
+      info = @data_source.load_country_info('NL'.dup.taint.freeze)
       assert_equal('NL', info.code)
     end
   end
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/test/tc_zoneinfo_timezone_info.rb 
new/test/tc_zoneinfo_timezone_info.rb
--- old/test/tc_zoneinfo_timezone_info.rb       2014-08-08 19:46:45.000000000 
+0200
+++ new/test/tc_zoneinfo_timezone_info.rb       2017-03-25 19:37:26.000000000 
+0100
@@ -286,6 +286,17 @@
     end
   end
   
+  def test_load_no_offsets
+    offsets = []
+    transitions = [{:at => Time.utc(2000, 12, 31), :offset_index => 0}]
+
+    tzif_test(offsets, transitions) do |path, format|
+      assert_raises(InvalidZoneinfoFile) do
+        ZoneinfoTimezoneInfo.new('Zone', path)
+      end
+    end
+  end
+
   def test_load_invalid_offset_index
     offsets = [{:gmtoff => -0, :isdst => false, :abbrev => 'LMT'}]
     transitions = [{:at => Time.utc(2000, 12, 31), :offset_index => 2}]
@@ -707,27 +718,39 @@
     end
   end
     
-  def test_load_starts_all_same_dst_offset
+  def test_load_starts_only_dst_transition_with_lmt
     # The zoneinfo files don't include the offset from standard time, so this
     # has to be derived by looking at changes in the total UTC offset.
-    #
-    # If there are no changes in the UTC offset (ignoring the first offset, 
-    # which is usually local mean time), then a value of 1 hour is used as the
-    # standard time offset.
-  
+
     offsets = [
       {:gmtoff => 3542, :isdst => false, :abbrev => 'LMT'},
-      {:gmtoff => 7200, :isdst => true,  :abbrev => 'XDDT'}]
-      
-    transitions = [
-      {:at => Time.utc(2000, 1, 1), :offset_index => 1}]
-  
-    tzif_test(offsets, transitions) do |path, format|     
-      info = ZoneinfoTimezoneInfo.new('Zone/DoubleDaylight', path)
-      assert_equal('Zone/DoubleDaylight', info.identifier)
-  
-      assert_period(:LMT,  3542,    0, false,                  nil, 
Time.utc(2000, 1, 1), info)
-      assert_period(:XDDT, 3600, 3600,  true, Time.utc(2000, 1, 1),            
      nil, info)
+      {:gmtoff => 7200, :isdst => true,  :abbrev => 'XDT'}]
+
+    transitions = [{:at => Time.utc(2000, 1, 1), :offset_index => 1}]
+
+    tzif_test(offsets, transitions) do |path, format|
+      info = ZoneinfoTimezoneInfo.new('Zone/OnlyDST', path)
+      assert_equal('Zone/OnlyDST', info.identifier)
+
+      assert_period(:LMT, 3542,    0, false,                  nil, 
Time.utc(2000, 1, 1), info)
+      assert_period(:XDT, 3542, 3658,  true, Time.utc(2000, 1, 1),             
     nil, info)
+    end
+  end
+
+  def test_load_starts_only_dst_transition_without_lmt
+    # The zoneinfo files don't include the offset from standard time, so this
+    # has to be derived by looking at changes in the total UTC offset.
+
+    offsets = [{:gmtoff => 7200, :isdst => true,  :abbrev => 'XDT'}]
+
+    transitions = [{:at => Time.utc(2000, 1, 1), :offset_index => 0}]
+
+    tzif_test(offsets, transitions) do |path, format|
+      info = ZoneinfoTimezoneInfo.new('Zone/OnlyDST', path)
+      assert_equal('Zone/OnlyDST', info.identifier)
+
+      assert_period(:XDT, 3600, 3600, true,                  nil, 
Time.utc(2000, 1, 1), info)
+      assert_period(:XDT, 3600, 3600, true, Time.utc(2000, 1, 1),              
    nil, info)
     end
   end
   
@@ -756,6 +779,297 @@
       assert_period(:XDT,     0, 3600,  true, Time.utc(2000, 2, 1), nil, info)
     end
   end
+
+  def test_load_apia_international_dateline_change
+    # The zoneinfo files don't include the offset from standard time, so this
+    # has to be derived by looking at changes in the total UTC offset.
+
+    # Pacific/Apia moved across the International Date Line whilst observing
+    # daylight savings time.
+
+    offsets = [
+      {:gmtoff =>  45184, :isdst => false, :abbrev => 'LMT'},
+      {:gmtoff => -39600, :isdst => false, :abbrev => '-11'},
+      {:gmtoff => -36000, :isdst => true,  :abbrev => '-10'},
+      {:gmtoff =>  50400, :isdst => true,  :abbrev => '+14'},
+      {:gmtoff =>  46800, :isdst => false, :abbrev => '+13'}]
+
+    transitions = [
+      {:at => Time.utc(2011,  4,  2, 14, 0, 0), :offset_index => 1},
+      {:at => Time.utc(2011,  9, 24, 14, 0, 0), :offset_index => 2},
+      {:at => Time.utc(2011, 12, 30, 10, 0, 0), :offset_index => 3},
+      {:at => Time.utc(2012,  3, 31, 14, 0, 0), :offset_index => 4}]
+
+    tzif_test(offsets, transitions) do |path, format|
+      info = ZoneinfoTimezoneInfo.new('Test/Pacific/Apia', path)
+      assert_equal('Test/Pacific/Apia', info.identifier)
+
+      assert_period(  :LMT,  45184,    0, false,                              
nil, Time.utc(2011,  4,  2, 14, 0, 0), info)
+      assert_period(:'-11', -39600,    0, false, Time.utc(2011,  4,  2, 14, 0, 
0), Time.utc(2011,  9, 24, 14, 0, 0), info)
+      assert_period(:'-10', -39600, 3600,  true, Time.utc(2011,  9, 24, 14, 0, 
0), Time.utc(2011, 12, 30, 10, 0, 0), info)
+      assert_period(:'+14',  46800, 3600,  true, Time.utc(2011, 12, 30, 10, 0, 
0), Time.utc(2012,  3, 31, 14, 0, 0), info)
+      assert_period(:'+13',  46800,    0, false, Time.utc(2012,  3, 31, 14, 0, 
0),                              nil, info)
+    end
+  end
+
+  def test_load_offset_split_for_different_utc_offset
+    # The zoneinfo files don't include the offset from standard time, so this
+    # has to be derived by looking at changes in the total UTC offset.
+
+    offsets = [
+      {:gmtoff =>  3542, :isdst => false, :abbrev => 'LMT'},
+      {:gmtoff =>  3600, :isdst => false, :abbrev => 'XST1'},
+      {:gmtoff =>  7200, :isdst => false, :abbrev => 'XST2'},
+      {:gmtoff => 10800, :isdst => true,  :abbrev => 'XDT'}]
+
+    transitions = [
+      {:at => Time.utc(2000,  1, 1), :offset_index => 1},
+      {:at => Time.utc(2000,  2, 1), :offset_index => 3},
+      {:at => Time.utc(2000,  3, 1), :offset_index => 1},
+      {:at => Time.utc(2000,  4, 1), :offset_index => 2},
+      {:at => Time.utc(2000,  5, 1), :offset_index => 3},
+      {:at => Time.utc(2000,  6, 1), :offset_index => 2},
+      {:at => Time.utc(2000,  7, 1), :offset_index => 1},
+      {:at => Time.utc(2000,  8, 1), :offset_index => 3},
+      {:at => Time.utc(2000,  9, 1), :offset_index => 1},
+      {:at => Time.utc(2000, 10, 1), :offset_index => 2},
+      {:at => Time.utc(2000, 11, 1), :offset_index => 3},
+      {:at => Time.utc(2000, 12, 1), :offset_index => 2}]
+
+    # XDT will be split and defined according to its surrounding standard time
+    # offsets.
+
+    tzif_test(offsets, transitions) do |path, format|
+      info = ZoneinfoTimezoneInfo.new('Zone/SplitUtcOffset', path)
+      assert_equal('Zone/SplitUtcOffset', info.identifier)
+
+      assert_period( :LMT, 3542,    0, false,                   nil, 
Time.utc(2000,  1, 1), info)
+      assert_period(:XST1, 3600,    0, false, Time.utc(2000,  1, 1), 
Time.utc(2000,  2, 1), info)
+      assert_period( :XDT, 3600, 7200,  true, Time.utc(2000,  2, 1), 
Time.utc(2000,  3, 1), info)
+      assert_period(:XST1, 3600,    0, false, Time.utc(2000,  3, 1), 
Time.utc(2000,  4, 1), info)
+      assert_period(:XST2, 7200,    0, false, Time.utc(2000,  4, 1), 
Time.utc(2000,  5, 1), info)
+      assert_period( :XDT, 7200, 3600,  true, Time.utc(2000,  5, 1), 
Time.utc(2000,  6, 1), info)
+      assert_period(:XST2, 7200,    0, false, Time.utc(2000,  6, 1), 
Time.utc(2000,  7, 1), info)
+      assert_period(:XST1, 3600,    0, false, Time.utc(2000,  7, 1), 
Time.utc(2000,  8, 1), info)
+      assert_period( :XDT, 3600, 7200,  true, Time.utc(2000,  8, 1), 
Time.utc(2000,  9, 1), info)
+      assert_period(:XST1, 3600,    0, false, Time.utc(2000,  9, 1), 
Time.utc(2000, 10, 1), info)
+      assert_period(:XST2, 7200,    0, false, Time.utc(2000, 10, 1), 
Time.utc(2000, 11, 1), info)
+      assert_period( :XDT, 7200, 3600,  true, Time.utc(2000, 11, 1), 
Time.utc(2000, 12, 1), info)
+      assert_period(:XST2, 7200,    0, false, Time.utc(2000, 12, 1),           
        nil, info)
+
+      1.upto(6) do |i|
+        assert_same(info.period_for_utc(Time.utc(2000, i, 1)).offset, 
info.period_for_utc(Time.utc(2000, i + 6, 1)).offset)
+      end
+    end
+  end
+
+  def test_load_offset_utc_offset_taken_from_minimum_difference_minimum_after
+    # The zoneinfo files don't include the offset from standard time, so this
+    # has to be derived by looking at changes in the total UTC offset.
+
+    offsets = [
+      {:gmtoff =>  3542, :isdst => false, :abbrev => 'LMT'},
+      {:gmtoff =>  3600, :isdst => false, :abbrev => 'XST1'},
+      {:gmtoff =>  7200, :isdst => false, :abbrev => 'XST2'},
+      {:gmtoff => 10800, :isdst => true,  :abbrev => 'XDT'}]
+
+    transitions = [
+      {:at => Time.utc(2000,  1, 1), :offset_index => 1},
+      {:at => Time.utc(2000,  2, 1), :offset_index => 3},
+      {:at => Time.utc(2000,  3, 1), :offset_index => 2}]
+
+    # XDT should use the closest utc_offset (7200) (and not an equivalent
+    # utc_offset of 3600 and std_offset of 7200).
+
+    tzif_test(offsets, transitions) do |path, format|
+      info = ZoneinfoTimezoneInfo.new('Zone/MinimumUtcOffset', path)
+      assert_equal('Zone/MinimumUtcOffset', info.identifier)
+
+      assert_period( :LMT, 3542,    0, false,                   nil, 
Time.utc(2000,  1, 1), info)
+      assert_period(:XST1, 3600,    0, false, Time.utc(2000,  1, 1), 
Time.utc(2000,  2, 1), info)
+      assert_period( :XDT, 7200, 3600,  true, Time.utc(2000,  2, 1), 
Time.utc(2000,  3, 1), info)
+      assert_period(:XST2, 7200,    0, false, Time.utc(2000,  3, 1),           
        nil, info)
+    end
+  end
+
+  def test_load_offset_utc_offset_taken_from_minimum_difference_minimum_before
+    # The zoneinfo files don't include the offset from standard time, so this
+    # has to be derived by looking at changes in the total UTC offset.
+
+    offsets = [
+      {:gmtoff =>  3542, :isdst => false, :abbrev => 'LMT'},
+      {:gmtoff =>  3600, :isdst => false, :abbrev => 'XST1'},
+      {:gmtoff =>  7200, :isdst => false, :abbrev => 'XST2'},
+      {:gmtoff => 10800, :isdst => true,  :abbrev => 'XDT'}]
+
+    transitions = [
+      {:at => Time.utc(2000,  1, 1), :offset_index => 2},
+      {:at => Time.utc(2000,  2, 1), :offset_index => 3},
+      {:at => Time.utc(2000,  3, 1), :offset_index => 1}]
+
+    # XDT should use the closest utc_offset (7200) (and not an equivalent
+    # utc_offset of 3600 and std_offset of 7200).
+
+    tzif_test(offsets, transitions) do |path, format|
+      info = ZoneinfoTimezoneInfo.new('Zone/MinimumUtcOffset', path)
+      assert_equal('Zone/MinimumUtcOffset', info.identifier)
+
+      assert_period( :LMT, 3542,    0, false,                   nil, 
Time.utc(2000,  1, 1), info)
+      assert_period(:XST2, 7200,    0, false, Time.utc(2000,  1, 1), 
Time.utc(2000,  2, 1), info)
+      assert_period( :XDT, 7200, 3600,  true, Time.utc(2000,  2, 1), 
Time.utc(2000,  3, 1), info)
+      assert_period(:XST1, 3600,    0, false, Time.utc(2000,  3, 1),           
        nil, info)
+    end
+  end
+
+  def test_load_offset_does_not_use_equal_utc_total_offset_equal_after
+    # The zoneinfo files don't include the offset from standard time, so this
+    # has to be derived by looking at changes in the total UTC offset.
+
+    offsets = [
+      {:gmtoff => 3542, :isdst => false, :abbrev => 'LMT'},
+      {:gmtoff => 3600, :isdst => false, :abbrev => 'XST1'},
+      {:gmtoff => 7200, :isdst => false, :abbrev => 'XST2'},
+      {:gmtoff => 7200, :isdst => true,  :abbrev => 'XDT'}]
+
+    transitions = [
+      {:at => Time.utc(2000,  1, 1), :offset_index => 1},
+      {:at => Time.utc(2000,  2, 1), :offset_index => 3},
+      {:at => Time.utc(2000,  3, 1), :offset_index => 2}]
+
+    # XDT will be based on the utc_offset of XST1 even though XST2 has an
+    # equivalent (or greater) utc_total_offset.
+
+    tzif_test(offsets, transitions) do |path, format|
+      info = ZoneinfoTimezoneInfo.new('Zone/UtcOffsetEqual', path)
+      assert_equal('Zone/UtcOffsetEqual', info.identifier)
+
+      assert_period( :LMT, 3542,    0, false,                   nil, 
Time.utc(2000,  1, 1), info)
+      assert_period(:XST1, 3600,    0, false, Time.utc(2000,  1, 1), 
Time.utc(2000,  2, 1), info)
+      assert_period( :XDT, 3600, 3600,  true, Time.utc(2000,  2, 1), 
Time.utc(2000,  3, 1), info)
+      assert_period(:XST2, 7200,    0, false, Time.utc(2000,  3, 1),           
        nil, info)
+    end
+  end
+
+  def test_load_offset_does_not_use_equal_utc_total_offset_equal_before
+    # The zoneinfo files don't include the offset from standard time, so this
+    # has to be derived by looking at changes in the total UTC offset.
+
+    offsets = [
+      {:gmtoff => 3542, :isdst => false, :abbrev => 'LMT'},
+      {:gmtoff => 3600, :isdst => false, :abbrev => 'XST1'},
+      {:gmtoff => 7200, :isdst => false, :abbrev => 'XST2'},
+      {:gmtoff => 7200, :isdst => true,  :abbrev => 'XDT'}]
+
+    transitions = [
+      {:at => Time.utc(2000,  1, 1), :offset_index => 2},
+      {:at => Time.utc(2000,  2, 1), :offset_index => 3},
+      {:at => Time.utc(2000,  3, 1), :offset_index => 1}]
+
+    # XDT will be based on the utc_offset of XST1 even though XST2 has an
+    # equivalent (or greater) utc_total_offset.
+
+    tzif_test(offsets, transitions) do |path, format|
+      info = ZoneinfoTimezoneInfo.new('Zone/UtcOffsetEqual', path)
+      assert_equal('Zone/UtcOffsetEqual', info.identifier)
+
+      assert_period( :LMT, 3542,    0, false,                   nil, 
Time.utc(2000,  1, 1), info)
+      assert_period(:XST2, 7200,    0, false, Time.utc(2000,  1, 1), 
Time.utc(2000,  2, 1), info)
+      assert_period( :XDT, 3600, 3600,  true, Time.utc(2000,  2, 1), 
Time.utc(2000,  3, 1), info)
+      assert_period(:XST1, 3600,    0, false, Time.utc(2000,  3, 1),           
        nil, info)
+    end
+  end
+
+  def test_load_offset_both_adjacent_non_dst_equal_utc_total_offset
+    # The zoneinfo files don't include the offset from standard time, so this
+    # has to be derived by looking at changes in the total UTC offset.
+
+    offsets = [
+      {:gmtoff => 7142, :isdst => false, :abbrev => 'LMT'},
+      {:gmtoff => 7200, :isdst => false, :abbrev => 'XST'},
+      {:gmtoff => 7200, :isdst => true,  :abbrev => 'XDT'}]
+
+    transitions = [
+      {:at => Time.utc(2000,  1, 1), :offset_index => 1},
+      {:at => Time.utc(2000,  2, 1), :offset_index => 2},
+      {:at => Time.utc(2000,  3, 1), :offset_index => 1}]
+
+    # XDT will just assume an std_offset of +1 hour and calculate the 
utc_offset
+    # from utc_total_offset - std_offset.
+
+    tzif_test(offsets, transitions) do |path, format|
+      info = ZoneinfoTimezoneInfo.new('Zone/AdjacentEqual', path)
+      assert_equal('Zone/AdjacentEqual', info.identifier)
+
+      assert_period(:LMT, 7142,    0, false,                   nil, 
Time.utc(2000,  1, 1), info)
+      assert_period(:XST, 7200,    0, false, Time.utc(2000,  1, 1), 
Time.utc(2000,  2, 1), info)
+      assert_period(:XDT, 3600, 3600,  true, Time.utc(2000,  2, 1), 
Time.utc(2000,  3, 1), info)
+      assert_period(:XST, 7200,    0, false, Time.utc(2000,  3, 1),            
       nil, info)
+    end
+  end
+
+  def test_load_offset_utc_offset_preserved_from_next
+    # The zoneinfo files don't include the offset from standard time, so this
+    # has to be derived by looking at changes in the total UTC offset.
+
+    offsets = [
+      {:gmtoff =>  3542, :isdst => false, :abbrev => 'LMT'},
+      {:gmtoff =>  3600, :isdst => false, :abbrev => 'XST1'},
+      {:gmtoff =>  7200, :isdst => false, :abbrev => 'XST2'},
+      {:gmtoff => 10800, :isdst => true,  :abbrev => 'XDT1'},
+      {:gmtoff => 10800, :isdst => true,  :abbrev => 'XDT2'}]
+
+    transitions = [
+      {:at => Time.utc(2000,  1, 1), :offset_index => 1},
+      {:at => Time.utc(2000,  2, 1), :offset_index => 3},
+      {:at => Time.utc(2000,  3, 1), :offset_index => 4},
+      {:at => Time.utc(2000,  4, 1), :offset_index => 2}]
+
+    # Both XDT1 and XDT2 should both use the closest utc_offset (7200) (and not
+    # an equivalent utc_offset of 3600 and std_offset of 7200).
+
+    tzif_test(offsets, transitions) do |path, format|
+      info = ZoneinfoTimezoneInfo.new('Zone/UtcOffsetPreserved', path)
+      assert_equal('Zone/UtcOffsetPreserved', info.identifier)
+
+      assert_period( :LMT, 3542,    0, false,                   nil, 
Time.utc(2000,  1, 1), info)
+      assert_period(:XST1, 3600,    0, false, Time.utc(2000,  1, 1), 
Time.utc(2000,  2, 1), info)
+      assert_period(:XDT1, 7200, 3600,  true, Time.utc(2000,  2, 1), 
Time.utc(2000,  3, 1), info)
+      assert_period(:XDT2, 7200, 3600,  true, Time.utc(2000,  3, 1), 
Time.utc(2000,  4, 1), info)
+      assert_period(:XST2, 7200,    0, false, Time.utc(2000,  4, 1),           
        nil, info)
+    end
+  end
+
+  def test_load_offset_utc_offset_preserved_from_previous
+    # The zoneinfo files don't include the offset from standard time, so this
+    # has to be derived by looking at changes in the total UTC offset.
+
+    offsets = [
+      {:gmtoff =>  3542, :isdst => false, :abbrev => 'LMT'},
+      {:gmtoff =>  3600, :isdst => false, :abbrev => 'XST1'},
+      {:gmtoff =>  7200, :isdst => false, :abbrev => 'XST2'},
+      {:gmtoff => 10800, :isdst => true,  :abbrev => 'XDT1'},
+      {:gmtoff => 10800, :isdst => true,  :abbrev => 'XDT2'}]
+
+    transitions = [
+      {:at => Time.utc(2000,  1, 1), :offset_index => 2},
+      {:at => Time.utc(2000,  2, 1), :offset_index => 3},
+      {:at => Time.utc(2000,  3, 1), :offset_index => 4},
+      {:at => Time.utc(2000,  4, 1), :offset_index => 1}]
+
+    # Both XDT1 and XDT2 should both use the closest utc_offset (7200) (and not
+    # an equivalent utc_offset of 3600 and std_offset of 7200).
+
+    tzif_test(offsets, transitions) do |path, format|
+      info = ZoneinfoTimezoneInfo.new('Zone/UtcOffsetPreserved', path)
+      assert_equal('Zone/UtcOffsetPreserved', info.identifier)
+
+      assert_period( :LMT, 3542,    0, false,                   nil, 
Time.utc(2000,  1, 1), info)
+      assert_period(:XST2, 7200,    0, false, Time.utc(2000,  1, 1), 
Time.utc(2000,  2, 1), info)
+      assert_period(:XDT1, 7200, 3600,  true, Time.utc(2000,  2, 1), 
Time.utc(2000,  3, 1), info)
+      assert_period(:XDT2, 7200, 3600,  true, Time.utc(2000,  3, 1), 
Time.utc(2000,  4, 1), info)
+      assert_period(:XST1, 3600,    0, false, Time.utc(2000,  4, 1),           
        nil, info)
+    end
+  end
   
   def test_load_in_safe_mode
     offsets = [{:gmtoff => -12094, :isdst => false, :abbrev => 'LT'}]
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/test/test_utils.rb new/test/test_utils.rb
--- old/test/test_utils.rb      2014-08-08 19:46:45.000000000 +0200
+++ new/test/test_utils.rb      2017-03-25 19:37:26.000000000 +0100
@@ -28,7 +28,9 @@
     
       begin
         FileUtils.ln_s(target, path)
-      rescue NotImplementedError
+      rescue NotImplementedError, Errno::EACCES
+        # Symlinks not supported on this platform, or permission denied
+        # (administrative rights are required on Windows). Copy instead.
         target_path = File.join(TZINFO_TEST_ZONEINFO_DIR, target)
         FileUtils.cp(target_path, path)
       end
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/tzinfo.gemspec new/tzinfo.gemspec
--- old/tzinfo.gemspec  2014-08-08 19:46:45.000000000 +0200
+++ new/tzinfo.gemspec  2017-03-25 19:37:26.000000000 +0100
@@ -1,6 +1,6 @@
 Gem::Specification.new do |s|
   s.name = 'tzinfo'
-  s.version = '1.2.2'
+  s.version = '1.2.3'
   s.summary = 'Daylight savings aware timezone library'
   s.description = 'TZInfo provides daylight savings aware transformations 
between times in different time zones.'
   s.author = 'Philip Ross'


Reply via email to