1# frozen_string_literal: false
2require 'test/unit'
3require '-test-/time'
4
5class TestTimeTZ < Test::Unit::TestCase
6  has_right_tz = true
7  has_lisbon_tz = true
8  force_tz_test = ENV["RUBY_FORCE_TIME_TZ_TEST"] == "yes"
9  case RUBY_PLATFORM
10  when /linux/
11    force_tz_test = true
12  when /darwin|freebsd/
13    has_lisbon_tz = false
14    force_tz_test = true
15  end
16
17  if force_tz_test
18    module Util
19      def with_tz(tz)
20        old = ENV["TZ"]
21        begin
22          ENV["TZ"] = tz
23          yield
24        ensure
25          ENV["TZ"] = old
26        end
27      end
28    end
29  else
30    module Util
31      def with_tz(tz)
32        if ENV["TZ"] == tz
33          yield
34        end
35      end
36    end
37  end
38
39  module Util
40    def have_tz_offset?(tz)
41      with_tz(tz) {!Time.now.utc_offset.zero?}
42    end
43
44    def format_gmtoff(gmtoff, colon=false)
45      if gmtoff < 0
46        expected = "-"
47        gmtoff = -gmtoff
48      else
49        expected = "+"
50      end
51      gmtoff /= 60
52      expected << "%02d" % [gmtoff / 60]
53      expected << ":" if colon
54      expected << "%02d" % [gmtoff % 60]
55      expected
56    end
57
58    def format_gmtoff2(gmtoff)
59      if gmtoff < 0
60        expected = "-"
61        gmtoff = -gmtoff
62      else
63        expected = "+"
64      end
65      expected << "%02d:%02d:%02d" % [gmtoff / 3600, gmtoff % 3600 / 60, gmtoff % 60]
66      expected
67    end
68
69    def group_by(e, &block)
70      if e.respond_to? :group_by
71        e.group_by(&block)
72      else
73        h = {}
74        e.each {|o|
75          (h[yield(o)] ||= []) << o
76        }
77        h
78      end
79    end
80
81  end
82
83  include Util
84  extend Util
85
86  has_right_tz &&= have_tz_offset?("right/America/Los_Angeles")
87  has_lisbon_tz &&= have_tz_offset?("Europe/Lisbon")
88  CORRECT_TOKYO_DST_1951 = with_tz("Asia/Tokyo") {
89    if Time.local(1951, 5, 6, 12, 0, 0).dst? # noon, DST
90      if Time.local(1951, 5, 6, 1, 0, 0).dst? # DST with fixed tzdata
91        Time.local(1951, 9, 8, 23, 0, 0).dst? ? "2018f" : "2018e"
92      end
93    end
94  }
95  CORRECT_KIRITIMATI_SKIP_1994 = with_tz("Pacific/Kiritimati") {
96    Time.local(1994, 12, 31, 0, 0, 0).year == 1995
97  }
98
99  def time_to_s(t)
100    t.to_s
101  end
102
103
104  def assert_time_constructor(tz, expected, method, args, message=nil)
105    m = message ? "#{message}\n" : ""
106    m << "TZ=#{tz} Time.#{method}(#{args.map {|arg| arg.inspect }.join(', ')})"
107    real = time_to_s(Time.send(method, *args))
108    assert_equal(expected, real, m)
109  end
110
111  def test_localtime_zone
112    t = with_tz("America/Los_Angeles") {
113      Time.local(2000, 1, 1)
114    }
115    skip "force_tz_test is false on this environment" unless t
116    z1 = t.zone
117    z2 = with_tz(tz="Asia/Singapore") {
118      t.localtime.zone
119    }
120    assert_equal(z2, z1)
121  end
122
123  def test_america_los_angeles
124    with_tz(tz="America/Los_Angeles") {
125      assert_time_constructor(tz, "2007-03-11 03:00:00 -0700", :local, [2007,3,11,2,0,0])
126      assert_time_constructor(tz, "2007-03-11 03:59:59 -0700", :local, [2007,3,11,2,59,59])
127      assert_equal("PST", Time.new(0x1_0000_0000_0000_0000, 1).zone)
128      assert_equal("PDT", Time.new(0x1_0000_0000_0000_0000, 8).zone)
129      assert_equal(false, Time.new(0x1_0000_0000_0000_0000, 1).isdst)
130      assert_equal(true, Time.new(0x1_0000_0000_0000_0000, 8).isdst)
131    }
132  end
133
134  def test_america_managua
135    with_tz(tz="America/Managua") {
136      assert_time_constructor(tz, "1993-01-01 01:00:00 -0500", :local, [1993,1,1,0,0,0])
137      assert_time_constructor(tz, "1993-01-01 01:59:59 -0500", :local, [1993,1,1,0,59,59])
138    }
139  end
140
141  def test_asia_singapore
142    with_tz(tz="Asia/Singapore") {
143      assert_time_constructor(tz, "1981-12-31 23:59:59 +0730", :local, [1981,12,31,23,59,59])
144      assert_time_constructor(tz, "1982-01-01 00:30:00 +0800", :local, [1982,1,1,0,0,0])
145      assert_time_constructor(tz, "1982-01-01 00:59:59 +0800", :local, [1982,1,1,0,29,59])
146      assert_time_constructor(tz, "1982-01-01 00:30:00 +0800", :local, [1982,1,1,0,30,0])
147    }
148  end
149
150  def test_asia_tokyo
151    with_tz(tz="Asia/Tokyo") {
152      h = CORRECT_TOKYO_DST_1951 ? 0 : 2
153      assert_time_constructor(tz, "1951-05-06 0#{h+1}:00:00 +1000", :local, [1951,5,6,h,0,0])
154      assert_time_constructor(tz, "1951-05-06 0#{h+1}:59:59 +1000", :local, [1951,5,6,h,59,59])
155      assert_time_constructor(tz, "2010-06-10 06:13:28 +0900", :local, [2010,6,10,6,13,28])
156    }
157  end
158
159  def test_asia_kuala_lumpur
160    with_tz(tz="Asia/Kuala_Lumpur") {
161      assert_time_constructor(tz, "1933-01-01 00:20:00 +0720", :local, [1933])
162    }
163  end
164
165  def test_canada_newfoundland
166    with_tz(tz="America/St_Johns") {
167      assert_time_constructor(tz, "2007-11-03 23:00:59 -0230", :new, [2007,11,3,23,0,59,:dst])
168      assert_time_constructor(tz, "2007-11-03 23:01:00 -0230", :new, [2007,11,3,23,1,0,:dst])
169      assert_time_constructor(tz, "2007-11-03 23:59:59 -0230", :new, [2007,11,3,23,59,59,:dst])
170      assert_time_constructor(tz, "2007-11-04 00:00:00 -0230", :new, [2007,11,4,0,0,0,:dst])
171      assert_time_constructor(tz, "2007-11-04 00:00:59 -0230", :new, [2007,11,4,0,0,59,:dst])
172      assert_time_constructor(tz, "2007-11-03 23:01:00 -0330", :new, [2007,11,3,23,1,0,:std])
173      assert_time_constructor(tz, "2007-11-03 23:59:59 -0330", :new, [2007,11,3,23,59,59,:std])
174      assert_time_constructor(tz, "2007-11-04 00:00:59 -0330", :new, [2007,11,4,0,0,59,:std])
175      assert_time_constructor(tz, "2007-11-04 00:01:00 -0330", :new, [2007,11,4,0,1,0,:std])
176    }
177  end
178
179  def test_europe_brussels
180    with_tz(tz="Europe/Brussels") {
181      assert_time_constructor(tz, "1916-04-30 23:59:59 +0100", :local, [1916,4,30,23,59,59])
182      assert_time_constructor(tz, "1916-05-01 01:00:00 +0200", :local, [1916,5,1], "[ruby-core:30672] [Bug #3411]")
183      assert_time_constructor(tz, "1916-05-01 01:59:59 +0200", :local, [1916,5,1,0,59,59])
184      assert_time_constructor(tz, "1916-05-01 01:00:00 +0200", :local, [1916,5,1,1,0,0])
185      assert_time_constructor(tz, "1916-05-01 01:59:59 +0200", :local, [1916,5,1,1,59,59])
186    }
187  end
188
189  def test_europe_berlin
190    with_tz(tz="Europe/Berlin") {
191      assert_time_constructor(tz, "2011-10-30 02:00:00 +0100", :local, [2011,10,30,2,0,0], "[ruby-core:67345] [Bug #10698]")
192      assert_time_constructor(tz, "2011-10-30 02:00:00 +0100", :local, [0,0,2,30,10,2011,nil,nil,false,nil])
193      assert_time_constructor(tz, "2011-10-30 02:00:00 +0200", :local, [0,0,2,30,10,2011,nil,nil,true,nil])
194    }
195  end
196
197  def test_europe_lisbon
198    with_tz("Europe/Lisbon") {
199      assert_equal("LMT", Time.new(-0x1_0000_0000_0000_0000).zone)
200    }
201  end if has_lisbon_tz
202
203  def test_pacific_kiritimati
204    with_tz(tz="Pacific/Kiritimati") {
205      assert_time_constructor(tz, "1994-12-30 00:00:00 -1000", :local, [1994,12,30,0,0,0])
206      assert_time_constructor(tz, "1994-12-30 23:59:59 -1000", :local, [1994,12,30,23,59,59])
207      if CORRECT_KIRITIMATI_SKIP_1994
208        assert_time_constructor(tz, "1995-01-01 00:00:00 +1400", :local, [1994,12,31,0,0,0])
209        assert_time_constructor(tz, "1995-01-01 23:59:59 +1400", :local, [1994,12,31,23,59,59])
210        assert_time_constructor(tz, "1995-01-01 00:00:00 +1400", :local, [1995,1,1,0,0,0])
211      else
212        assert_time_constructor(tz, "1994-12-31 23:59:59 -1000", :local, [1994,12,31,23,59,59])
213        assert_time_constructor(tz, "1995-01-02 00:00:00 +1400", :local, [1995,1,1,0,0,0])
214        assert_time_constructor(tz, "1995-01-02 23:59:59 +1400", :local, [1995,1,1,23,59,59])
215      end
216      assert_time_constructor(tz, "1995-01-02 00:00:00 +1400", :local, [1995,1,2,0,0,0])
217    }
218  end
219
220  def test_right_utc
221    with_tz(tz="right/UTC") {
222      ::Bug::Time.reset_leap_second_info
223      assert_time_constructor(tz, "2008-12-31 23:59:59 UTC", :utc, [2008,12,31,23,59,59])
224      assert_time_constructor(tz, "2008-12-31 23:59:60 UTC", :utc, [2008,12,31,23,59,60])
225      assert_time_constructor(tz, "2009-01-01 00:00:00 UTC", :utc, [2008,12,31,24,0,0])
226      assert_time_constructor(tz, "2009-01-01 00:00:00 UTC", :utc, [2009,1,1,0,0,0])
227    }
228  end if has_right_tz
229
230  def test_right_utc_switching
231    with_tz("UTC") { # ensure no leap second timezone
232      ::Bug::Time.reset_leap_second_info
233      assert_equal(4102444800, Time.utc(2100,1,1,0,0,0).to_i)
234      with_tz(tz="right/UTC") {
235        assert_time_constructor(tz, "2008-12-31 23:59:59 UTC", :utc, [2008,12,31,23,59,59])
236        assert_time_constructor(tz, "2009-01-01 00:00:00 UTC", :utc, [2008,12,31,23,59,60])
237        assert_time_constructor(tz, "2009-01-01 00:00:00 UTC", :utc, [2008,12,31,24,0,0])
238        assert_time_constructor(tz, "2009-01-01 00:00:00 UTC", :utc, [2009,1,1,0,0,0])
239        assert_equal(4102444800, Time.utc(2100,1,1,0,0,0).to_i)
240      }
241    }
242    with_tz("right/UTC") {
243      ::Bug::Time.reset_leap_second_info
244      assert_not_equal(4102444800, Time.utc(2100,1,1,0,0,0).to_i)
245      with_tz(tz="UTC") {
246        assert_time_constructor(tz, "2008-12-31 23:59:59 UTC", :utc, [2008,12,31,23,59,59])
247        assert_time_constructor(tz, "2009-01-01 00:00:00 UTC", :utc, [2008,12,31,23,59,60])
248        assert_time_constructor(tz, "2009-01-01 00:00:00 UTC", :utc, [2008,12,31,24,0,0])
249        assert_time_constructor(tz, "2009-01-01 00:00:00 UTC", :utc, [2009,1,1,0,0,0])
250        assert_not_equal(4102444800, Time.utc(2100,1,1,0,0,0).to_i)
251      }
252    }
253  end if has_right_tz
254
255  def test_right_america_los_angeles
256    with_tz(tz="right/America/Los_Angeles") {
257      assert_time_constructor(tz, "2008-12-31 15:59:59 -0800", :local, [2008,12,31,15,59,59])
258      assert_time_constructor(tz, "2008-12-31 15:59:60 -0800", :local, [2008,12,31,15,59,60])
259      assert_time_constructor(tz, "2008-12-31 16:00:00 -0800", :local, [2008,12,31,16,0,0])
260    }
261  end if has_right_tz
262
263  MON2NUM = {
264    "Jan" => 1, "Feb" => 2, "Mar" => 3, "Apr" => 4, "May" => 5, "Jun" => 6,
265    "Jul" => 7, "Aug" => 8, "Sep" => 9, "Oct" => 10, "Nov" => 11, "Dec" => 12
266  }
267
268  @testnum = 0
269  def self.gen_test_name(hint)
270    @testnum += 1
271    s = "test_gen_#{@testnum}"
272    s.sub(/gen_/) { "gen" + "_#{hint}_".gsub(/[^0-9A-Za-z]+/, '_') }
273  end
274
275  def self.parse_zdump_line(line)
276    return nil if /\A\#/ =~ line || /\A\s*\z/ =~ line
277    if /\A(\S+)\s+
278        \S+\s+(\S+)\s+(\d+)\s+(\d\d):(\d\d):(\d\d)\s+(\d+)\s+UTC?
279        \s+=\s+
280        \S+\s+(\S+)\s+(\d+)\s+(\d\d):(\d\d):(\d\d)\s+(\d+)\s+\S+
281        \s+isdst=\d+\s+gmtoff=(-?\d+)\n
282        \z/x !~ line
283      raise "unexpected zdump line: #{line.inspect}"
284    end
285    tz, u_mon, u_day, u_hour, u_min, u_sec, u_year,
286      l_mon, l_day, l_hour, l_min, l_sec, l_year, gmtoff = $~.captures
287    u_year = u_year.to_i
288    u_mon = MON2NUM[u_mon]
289    u_day = u_day.to_i
290    u_hour = u_hour.to_i
291    u_min = u_min.to_i
292    u_sec = u_sec.to_i
293    l_year = l_year.to_i
294    l_mon = MON2NUM[l_mon]
295    l_day = l_day.to_i
296    l_hour = l_hour.to_i
297    l_min = l_min.to_i
298    l_sec = l_sec.to_i
299    gmtoff = gmtoff.to_i
300    [tz,
301     [u_year, u_mon, u_day, u_hour, u_min, u_sec],
302     [l_year, l_mon, l_day, l_hour, l_min, l_sec],
303     gmtoff]
304  end
305
306  def self.gen_zdump_test(data)
307    sample = []
308    data.each_line {|line|
309      s = parse_zdump_line(line)
310      sample << s if s
311    }
312    sample.each {|tz, u, l, gmtoff|
313      expected_utc = "%04d-%02d-%02d %02d:%02d:%02d UTC" % u
314      expected = "%04d-%02d-%02d %02d:%02d:%02d %s" % (l+[format_gmtoff(gmtoff)])
315      mesg_utc = "TZ=#{tz} Time.utc(#{u.map {|arg| arg.inspect }.join(', ')})"
316      mesg = "#{mesg_utc}.localtime"
317      define_method(gen_test_name(tz)) {
318        with_tz(tz) {
319          ::Bug::Time.reset_leap_second_info
320          t = nil
321          assert_nothing_raised(mesg) { t = Time.utc(*u) }
322          assert_equal(expected_utc, time_to_s(t), mesg_utc)
323          assert_nothing_raised(mesg) { t.localtime }
324          assert_equal(expected, time_to_s(t), mesg)
325          assert_equal(gmtoff, t.gmtoff)
326          assert_equal(format_gmtoff(gmtoff), t.strftime("%z"))
327          assert_equal(format_gmtoff(gmtoff, true), t.strftime("%:z"))
328          assert_equal(format_gmtoff2(gmtoff), t.strftime("%::z"))
329          assert_equal(Encoding::US_ASCII, t.zone.encoding)
330        }
331      }
332    }
333
334    group_by(sample) {|tz, _, _, _| tz }.each {|tz, a|
335      a.each_with_index {|(_, _, l, gmtoff), i|
336        expected = "%04d-%02d-%02d %02d:%02d:%02d %s" % (l+[format_gmtoff(gmtoff)])
337        monotonic_to_past = i == 0 || (a[i-1][2] <=> l) < 0
338        monotonic_to_future = i == a.length-1 || (l <=> a[i+1][2]) < 0
339        if monotonic_to_past && monotonic_to_future
340          define_method(gen_test_name(tz)) {
341            with_tz(tz) {
342              assert_time_constructor(tz, expected, :local, l)
343              assert_time_constructor(tz, expected, :local, l.reverse+[nil, nil, false, nil])
344              assert_time_constructor(tz, expected, :local, l.reverse+[nil, nil, true, nil])
345              assert_time_constructor(tz, expected, :new, l)
346              assert_time_constructor(tz, expected, :new, l+[:std])
347              assert_time_constructor(tz, expected, :new, l+[:dst])
348            }
349          }
350        elsif monotonic_to_past && !monotonic_to_future
351          define_method(gen_test_name(tz)) {
352            with_tz(tz) {
353              assert_time_constructor(tz, expected, :local, l.reverse+[nil, nil, true, nil])
354              assert_time_constructor(tz, expected, :new, l+[:dst])
355            }
356          }
357        elsif !monotonic_to_past && monotonic_to_future
358          define_method(gen_test_name(tz)) {
359            with_tz(tz) {
360              assert_time_constructor(tz, expected, :local, l.reverse+[nil, nil, false, nil])
361              assert_time_constructor(tz, expected, :new, l+[:std])
362            }
363          }
364        else
365          define_method(gen_test_name(tz)) {
366            flunk("time in reverse order: TZ=#{tz} #{expected}")
367          }
368        end
369      }
370    }
371  end
372
373  gen_zdump_test <<'End'
374America/Lima  Sun Apr  1 03:59:59 1990 UTC = Sat Mar 31 23:59:59 1990 PEST isdst=1 gmtoff=-14400
375America/Lima  Sun Apr  1 04:00:00 1990 UTC = Sat Mar 31 23:00:00 1990 PET isdst=0 gmtoff=-18000
376America/Lima  Sat Jan  1 04:59:59 1994 UTC = Fri Dec 31 23:59:59 1993 PET isdst=0 gmtoff=-18000
377America/Lima  Sat Jan  1 05:00:00 1994 UTC = Sat Jan  1 01:00:00 1994 PEST isdst=1 gmtoff=-14400
378America/Lima  Fri Apr  1 03:59:59 1994 UTC = Thu Mar 31 23:59:59 1994 PEST isdst=1 gmtoff=-14400
379America/Lima  Fri Apr  1 04:00:00 1994 UTC = Thu Mar 31 23:00:00 1994 PET isdst=0 gmtoff=-18000
380America/Los_Angeles  Sun Apr  2 09:59:59 2006 UTC = Sun Apr  2 01:59:59 2006 PST isdst=0 gmtoff=-28800
381America/Los_Angeles  Sun Apr  2 10:00:00 2006 UTC = Sun Apr  2 03:00:00 2006 PDT isdst=1 gmtoff=-25200
382America/Los_Angeles  Sun Oct 29 08:59:59 2006 UTC = Sun Oct 29 01:59:59 2006 PDT isdst=1 gmtoff=-25200
383America/Los_Angeles  Sun Oct 29 09:00:00 2006 UTC = Sun Oct 29 01:00:00 2006 PST isdst=0 gmtoff=-28800
384America/Los_Angeles  Sun Mar 11 09:59:59 2007 UTC = Sun Mar 11 01:59:59 2007 PST isdst=0 gmtoff=-28800
385America/Los_Angeles  Sun Mar 11 10:00:00 2007 UTC = Sun Mar 11 03:00:00 2007 PDT isdst=1 gmtoff=-25200
386America/Los_Angeles  Sun Nov  4 08:59:59 2007 UTC = Sun Nov  4 01:59:59 2007 PDT isdst=1 gmtoff=-25200
387America/Los_Angeles  Sun Nov  4 09:00:00 2007 UTC = Sun Nov  4 01:00:00 2007 PST isdst=0 gmtoff=-28800
388America/Managua  Thu Sep 24 04:59:59 1992 UTC = Wed Sep 23 23:59:59 1992 EST isdst=0 gmtoff=-18000
389America/Managua  Thu Sep 24 05:00:00 1992 UTC = Wed Sep 23 23:00:00 1992 CST isdst=0 gmtoff=-21600
390America/Managua  Fri Jan  1 05:59:59 1993 UTC = Thu Dec 31 23:59:59 1992 CST isdst=0 gmtoff=-21600
391America/Managua  Fri Jan  1 06:00:00 1993 UTC = Fri Jan  1 01:00:00 1993 EST isdst=0 gmtoff=-18000
392America/Managua  Wed Jan  1 04:59:59 1997 UTC = Tue Dec 31 23:59:59 1996 EST isdst=0 gmtoff=-18000
393America/Managua  Wed Jan  1 05:00:00 1997 UTC = Tue Dec 31 23:00:00 1996 CST isdst=0 gmtoff=-21600
394Asia/Singapore  Sun Aug  8 16:30:00 1965 UTC = Mon Aug  9 00:00:00 1965 SGT isdst=0 gmtoff=27000
395Asia/Singapore  Thu Dec 31 16:29:59 1981 UTC = Thu Dec 31 23:59:59 1981 SGT isdst=0 gmtoff=27000
396Asia/Singapore  Thu Dec 31 16:30:00 1981 UTC = Fri Jan  1 00:30:00 1982 SGT isdst=0 gmtoff=28800
397End
398  gen_zdump_test CORRECT_TOKYO_DST_1951 ? <<'End' + (CORRECT_TOKYO_DST_1951 < "2018f" ? <<'2018e' : <<'2018f') : <<'End'
399Asia/Tokyo  Sat May  5 14:59:59 1951 UTC = Sat May  5 23:59:59 1951 JST isdst=0 gmtoff=32400
400Asia/Tokyo  Sat May  5 15:00:00 1951 UTC = Sun May  6 01:00:00 1951 JDT isdst=1 gmtoff=36000
401End
402Asia/Tokyo  Sat Sep  8 13:59:59 1951 UTC = Sat Sep  8 23:59:59 1951 JDT isdst=1 gmtoff=36000
403Asia/Tokyo  Sat Sep  8 14:00:00 1951 UTC = Sat Sep  8 23:00:00 1951 JST isdst=0 gmtoff=32400
4042018e
405Asia/Tokyo  Sat Sep  8 14:59:59 1951 UTC = Sun Sep  9 00:59:59 1951 JDT isdst=1 gmtoff=36000
406Asia/Tokyo  Sat Sep  8 15:00:00 1951 UTC = Sun Sep  9 00:00:00 1951 JST isdst=0 gmtoff=32400
4072018f
408Asia/Tokyo  Sat May  5 16:59:59 1951 UTC = Sun May  6 01:59:59 1951 JST isdst=0 gmtoff=32400
409Asia/Tokyo  Sat May  5 17:00:00 1951 UTC = Sun May  6 03:00:00 1951 JDT isdst=1 gmtoff=36000
410Asia/Tokyo  Fri Sep  7 15:59:59 1951 UTC = Sat Sep  8 01:59:59 1951 JDT isdst=1 gmtoff=36000
411Asia/Tokyo  Fri Sep  7 16:00:00 1951 UTC = Sat Sep  8 01:00:00 1951 JST isdst=0 gmtoff=32400
412End
413  gen_zdump_test <<'End'
414America/St_Johns  Sun Mar 11 03:30:59 2007 UTC = Sun Mar 11 00:00:59 2007 NST isdst=0 gmtoff=-12600
415America/St_Johns  Sun Mar 11 03:31:00 2007 UTC = Sun Mar 11 01:01:00 2007 NDT isdst=1 gmtoff=-9000
416America/St_Johns  Sun Nov  4 02:30:59 2007 UTC = Sun Nov  4 00:00:59 2007 NDT isdst=1 gmtoff=-9000
417America/St_Johns  Sun Nov  4 02:31:00 2007 UTC = Sat Nov  3 23:01:00 2007 NST isdst=0 gmtoff=-12600
418Europe/Brussels  Sun Apr 30 22:59:59 1916 UTC = Sun Apr 30 23:59:59 1916 CET isdst=0 gmtoff=3600
419Europe/Brussels  Sun Apr 30 23:00:00 1916 UTC = Mon May  1 01:00:00 1916 CEST isdst=1 gmtoff=7200
420Europe/Brussels  Sat Sep 30 22:59:59 1916 UTC = Sun Oct  1 00:59:59 1916 CEST isdst=1 gmtoff=7200
421Europe/Brussels  Sat Sep 30 23:00:00 1916 UTC = Sun Oct  1 00:00:00 1916 CET isdst=0 gmtoff=3600
422Europe/London  Sun Mar 16 01:59:59 1947 UTC = Sun Mar 16 01:59:59 1947 GMT isdst=0 gmtoff=0
423Europe/London  Sun Mar 16 02:00:00 1947 UTC = Sun Mar 16 03:00:00 1947 BST isdst=1 gmtoff=3600
424Europe/London  Sun Apr 13 00:59:59 1947 UTC = Sun Apr 13 01:59:59 1947 BST isdst=1 gmtoff=3600
425Europe/London  Sun Apr 13 01:00:00 1947 UTC = Sun Apr 13 03:00:00 1947 BDST isdst=1 gmtoff=7200
426Europe/London  Sun Aug 10 00:59:59 1947 UTC = Sun Aug 10 02:59:59 1947 BDST isdst=1 gmtoff=7200
427Europe/London  Sun Aug 10 01:00:00 1947 UTC = Sun Aug 10 02:00:00 1947 BST isdst=1 gmtoff=3600
428Europe/London  Sun Nov  2 01:59:59 1947 UTC = Sun Nov  2 02:59:59 1947 BST isdst=1 gmtoff=3600
429Europe/London  Sun Nov  2 02:00:00 1947 UTC = Sun Nov  2 02:00:00 1947 GMT isdst=0 gmtoff=0
430End
431  if CORRECT_KIRITIMATI_SKIP_1994
432    gen_zdump_test <<'End'
433Pacific/Kiritimati  Sat Dec 31 09:59:59 1994 UTC = Fri Dec 30 23:59:59 1994 LINT isdst=0 gmtoff=-36000
434Pacific/Kiritimati  Sat Dec 31 10:00:00 1994 UTC = Sun Jan  1 00:00:00 1995 LINT isdst=0 gmtoff=50400
435End
436  else
437    gen_zdump_test <<'End'
438Pacific/Kiritimati  Sun Jan  1 09:59:59 1995 UTC = Sat Dec 31 23:59:59 1994 LINT isdst=0 gmtoff=-36000
439Pacific/Kiritimati  Sun Jan  1 10:00:00 1995 UTC = Mon Jan  2 00:00:00 1995 LINT isdst=0 gmtoff=50400
440End
441  end
442  gen_zdump_test <<'End' if has_right_tz
443right/America/Los_Angeles  Fri Jun 30 23:59:60 1972 UTC = Fri Jun 30 16:59:60 1972 PDT isdst=1 gmtoff=-25200
444right/America/Los_Angeles  Wed Dec 31 23:59:60 2008 UTC = Wed Dec 31 15:59:60 2008 PST isdst=0 gmtoff=-28800
445#right/Asia/Tokyo  Fri Jun 30 23:59:60 1972 UTC = Sat Jul  1 08:59:60 1972 JST isdst=0 gmtoff=32400
446#right/Asia/Tokyo  Sat Dec 31 23:59:60 2005 UTC = Sun Jan  1 08:59:60 2006 JST isdst=0 gmtoff=32400
447right/Europe/Paris  Fri Jun 30 23:59:60 1972 UTC = Sat Jul  1 00:59:60 1972 CET isdst=0 gmtoff=3600
448right/Europe/Paris  Wed Dec 31 23:59:60 2008 UTC = Thu Jan  1 00:59:60 2009 CET isdst=0 gmtoff=3600
449End
450
451  def self.gen_variational_zdump_test(hint, data)
452    sample = []
453    data.each_line {|line|
454      s = parse_zdump_line(line)
455      sample << s if s
456    }
457
458    define_method(gen_test_name(hint)) {
459      results = []
460      sample.each {|tz, u, l, gmtoff|
461        expected_utc = "%04d-%02d-%02d %02d:%02d:%02d UTC" % u
462        expected = "%04d-%02d-%02d %02d:%02d:%02d %s" % (l+[format_gmtoff(gmtoff)])
463        mesg_utc = "TZ=#{tz} Time.utc(#{u.map {|arg| arg.inspect }.join(', ')})"
464        mesg = "#{mesg_utc}.localtime"
465        with_tz(tz) {
466          t = nil
467          assert_nothing_raised(mesg) { t = Time.utc(*u) }
468          assert_equal(expected_utc, time_to_s(t), mesg_utc)
469          assert_nothing_raised(mesg) { t.localtime }
470
471          results << [
472            expected == time_to_s(t),
473            gmtoff == t.gmtoff,
474            format_gmtoff(gmtoff) == t.strftime("%z"),
475            format_gmtoff(gmtoff, true) == t.strftime("%:z"),
476            format_gmtoff2(gmtoff) == t.strftime("%::z")
477          ]
478        }
479      }
480      assert_include(results, [true, true, true, true, true])
481    }
482  end
483
484  # tzdata-2014g fixed the offset for lisbon from -0:36:32 to -0:36:45.
485  # [ruby-core:65058] [Bug #10245]
486  gen_variational_zdump_test "lisbon", <<'End' if has_lisbon_tz
487Europe/Lisbon  Mon Jan  1 00:36:31 1912 UTC = Sun Dec 31 23:59:59 1911 LMT isdst=0 gmtoff=-2192
488Europe/Lisbon  Mon Jan  1 00:36:44 1912 UT = Sun Dec 31 23:59:59 1911 LMT isdst=0 gmtoff=-2205
489Europe/Lisbon  Sun Dec 31 23:59:59 1911 UT = Sun Dec 31 23:23:14 1911 LMT isdst=0 gmtoff=-2205
490End
491
492  class TZ
493    attr_reader :name, :abbr, :offset
494
495    def initialize(name, abbr, offset)
496      @name = name
497      @abbr = abbr
498      @offset = offset
499    end
500
501    def local_to_utc(t)
502      t - @offset
503    end
504
505    def utc_to_local(t)
506      t + @offset
507    end
508
509    def abbr(t)
510      @abbr
511    end
512
513    def ==(other)
514      @name == other.name and @abbr == other.abbr(0) and @offset == other.offset
515    end
516
517    def inspect
518      "#<TZ: #@name #@abbr #@offset>"
519    end
520  end
521end
522
523module TestTimeTZ::WithTZ
524  def subtest_new(time_class, tz, tzarg, tzname, abbr, utc_offset)
525    t = time_class.new(2018, 9, 1, 12, 0, 0, tzarg)
526    assert_equal([2018, 9, 1, 12, 0, 0, tz], [t.year, t.mon, t.mday, t.hour, t.min, t.sec, t.zone])
527    h, m = (-utc_offset / 60).divmod(60)
528    assert_equal(time_class.utc(2018, 9, 1, 12+h, m, 0).to_i, t.to_i)
529  end
530
531  def subtest_getlocal(time_class, tz, tzarg, tzname, abbr, utc_offset)
532    t = time_class.utc(2018, 9, 1, 12, 0, 0).getlocal(tzarg)
533    h, m = (utc_offset / 60).divmod(60)
534    assert_equal([2018, 9, 1, 12+h, m, 0, tz], [t.year, t.mon, t.mday, t.hour, t.min, t.sec, t.zone])
535    assert_equal(time_class.utc(2018, 9, 1, 12, 0, 0), t)
536  end
537
538  def subtest_strftime(time_class, tz, tzarg, tzname, abbr, utc_offset)
539    t = time_class.new(2018, 9, 1, 12, 0, 0, tzarg)
540    h, m = (utc_offset.abs / 60).divmod(60)
541    h = -h if utc_offset < 0
542    assert_equal("%+.2d%.2d %s" % [h, m, abbr], t.strftime("%z %Z"))
543  end
544
545  def subtest_plus(time_class, tz, tzarg, tzname, abbr, utc_offset)
546    t = time_class.new(2018, 9, 1, 12, 0, 0, tzarg) + 4000
547    assert_equal([2018, 9, 1, 13, 6, 40, tz], [t.year, t.mon, t.mday, t.hour, t.min, t.sec, t.zone])
548    m, s = (4000-utc_offset).divmod(60)
549    h, m = m.divmod(60)
550    assert_equal(time_class.utc(2018, 9, 1, 12+h, m, s), t)
551    assert_equal(6, t.wday)
552    assert_equal(244, t.yday)
553  end
554
555  def subtest_at(time_class, tz, tzarg, tzname, abbr, utc_offset)
556    h, m = (utc_offset / 60).divmod(60)
557    utc = time_class.utc(2018, 9, 1, 12, 0, 0)
558    t = time_class.at(utc, in: tzarg)
559    assert_equal([2018, 9, 1, 12+h, m, 0, tz], [t.year, t.mon, t.mday, t.hour, t.min, t.sec, t.zone])
560    assert_equal(utc.to_i, t.to_i)
561    utc = utc.to_i
562    t = time_class.at(utc, in: tzarg)
563    assert_equal([2018, 9, 1, 12+h, m, 0, tz], [t.year, t.mon, t.mday, t.hour, t.min, t.sec, t.zone])
564    assert_equal(utc, t.to_i)
565  end
566
567  def subtest_marshal(time_class, tz, tzarg, tzname, abbr, utc_offset)
568    t = time_class.new(2018, 9, 1, 12, 0, 0, tzarg)
569    t2 = Marshal.load(Marshal.dump(t))
570    assert_equal(t, t2)
571    assert_equal(t.utc_offset, t2.utc_offset)
572    assert_equal(t.utc_offset, (t2+1).utc_offset)
573    assert_instance_of(t.zone.class, t2.zone)
574  end
575
576  def test_invalid_zone
577    make_timezone("INVALID", "INV", 0)
578  rescue => e
579    assert_kind_of(StandardError, e)
580  else
581    assert false, "ArgumentError expected but nothing was raised."
582  end
583
584  def nametest_marshal_compatibility(time_class, tzname, abbr, utc_offset)
585    data = [
586      "\x04\x08Iu:".b, Marshal.dump(time_class)[3..-1],
587      "\x0d""\xEF\xA7\x1D\x80\x00\x00\x00\x00".b,
588      Marshal.dump({offset: utc_offset, zone: abbr})[3..-1],
589    ].join('')
590    t = Marshal.load(data)
591    assert_equal(utc_offset, t.utc_offset)
592    assert_equal(utc_offset, (t+1).utc_offset)
593    # t.zone may be a mere String or timezone object.
594  end
595
596  ZONES = {
597    "Asia/Tokyo" => ["JST", +9*3600],
598    "America/Los_Angeles" => ["PDT", -7*3600],
599    "Africa/Ndjamena" => ["WAT", +1*3600],
600  }
601
602  def make_timezone(tzname, abbr, utc_offset)
603    self.class::TIME_CLASS.find_timezone(tzname)
604  end
605
606  instance_methods(false).grep(/\Asub(?=test_)/) do |subtest|
607    test = $'
608    ZONES.each_pair do |tzname, (abbr, utc_offset)|
609      define_method("#{test}@#{tzname}") do
610        tz = make_timezone(tzname, abbr, utc_offset)
611        time_class = self.class::TIME_CLASS
612        __send__(subtest, time_class, tz, tz, tzname, abbr, utc_offset)
613        __send__(subtest, time_class, tz, tzname, tzname, abbr, utc_offset)
614      end
615    end
616  end
617
618  instance_methods(false).grep(/\Aname(?=test_)/) do |subtest|
619    test = $'
620    ZONES.each_pair do |tzname, (abbr, utc_offset)|
621      define_method("#{test}@#{tzname}") do
622        time_class = self.class::TIME_CLASS
623        __send__(subtest, time_class, tzname, abbr, utc_offset)
624      end
625    end
626  end
627end
628
629class TestTimeTZ::DummyTZ < Test::Unit::TestCase
630  include TestTimeTZ::WithTZ
631
632  class TIME_CLASS < ::Time
633    ZONES = TestTimeTZ::WithTZ::ZONES
634    def self.find_timezone(tzname)
635      tz = ZONES[tzname] or raise ArgumentError, "Unknown timezone: #{name}"
636      TestTimeTZ::TZ.new(tzname, *tz)
637    end
638  end
639
640  def self.make_timezone(tzname, abbr, utc_offset)
641    TestTimeTZ::TZ.new(tzname, abbr, utc_offset)
642  end
643end
644
645begin
646  require "tzinfo"
647rescue LoadError
648else
649  class TestTimeTZ::GemTZInfo < Test::Unit::TestCase
650    include TestTimeTZ::WithTZ
651
652    class TIME_CLASS < ::Time
653      def self.find_timezone(tzname)
654        TZInfo::Timezone.get(tzname)
655      end
656    end
657
658    def tz
659      @tz ||= TZInfo::Timezone.get(tzname)
660    end
661  end
662
663  def test_fractional_second
664    x = Object.new
665    def x.local_to_utc(t); t + 8*3600; end
666    def x.utc_to_local(t); t - 8*3600; end
667
668    t1 = Time.new(2020,11,11,12,13,14.124r, '-08:00')
669    t2 = Time.new(2020,11,11,12,13,14.124r, x)
670    assert_equal(t1, t2)
671  end
672end
673
674begin
675  require "timezone"
676rescue LoadError
677else
678  class TestTimeTZ::GemTimezone < Test::Unit::TestCase
679    include TestTimeTZ::WithTZ
680
681    class TIME_CLASS < ::Time
682      def self.find_timezone(name)
683        Timezone.fetch(name)
684      end
685    end
686
687    def tz
688      @tz ||= Timezone[tzname]
689    end
690  end
691end
692