* 2022-01-18 [ci skip]
[ruby-80x24.org.git] / test / ruby / test_time_tz.rb
blob6ae12dea5de622bb0d27995b7c8862c3da2514f0
1 # frozen_string_literal: false
2 require 'test/unit'
3 require '-test-/time'
5 class 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|openbsd/
13     has_lisbon_tz = false
14     force_tz_test = true
15   end
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
39   module Util
40     def have_tz_offset?(tz)
41       with_tz(tz) {!Time.now.utc_offset.zero?}
42     end
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
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
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
81   end
83   include Util
84   extend Util
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   }
99   def time_to_s(t)
100     t.to_s
101   end
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
111   def test_localtime_zone
112     t = with_tz("America/Los_Angeles") {
113       Time.local(2000, 1, 1)
114     }
115     omit "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
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
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
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
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
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
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
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
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
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
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
220   def test_right_utc
221     with_tz(tz="right/UTC") {
222       assert_time_constructor(tz, "2008-12-31 23:59:59 UTC", :utc, [2008,12,31,23,59,59])
223       assert_time_constructor(tz, "2008-12-31 23:59:60 UTC", :utc, [2008,12,31,23,59,60])
224       assert_time_constructor(tz, "2009-01-01 00:00:00 UTC", :utc, [2008,12,31,24,0,0])
225       assert_time_constructor(tz, "2009-01-01 00:00:00 UTC", :utc, [2009,1,1,0,0,0])
226     }
227   end if has_right_tz
229   def test_right_utc_switching
230     with_tz("UTC") { # ensure no leap second timezone
231       assert_equal(4102444800, Time.utc(2100,1,1,0,0,0).to_i)
232       with_tz(tz="right/UTC") {
233         assert_time_constructor(tz, "2008-12-31 23:59:59 UTC", :utc, [2008,12,31,23,59,59])
234         assert_time_constructor(tz, "2008-12-31 23:59:60 UTC", :utc, [2008,12,31,23,59,60])
235         assert_time_constructor(tz, "2009-01-01 00:00:00 UTC", :utc, [2008,12,31,24,0,0])
236         assert_time_constructor(tz, "2009-01-01 00:00:00 UTC", :utc, [2009,1,1,0,0,0])
237         assert_not_equal(4102444800, Time.utc(2100,1,1,0,0,0).to_i)
238       }
239     }
240     with_tz("right/UTC") {
241       assert_not_equal(4102444800, Time.utc(2100,1,1,0,0,0).to_i)
242       with_tz(tz="UTC") {
243         assert_time_constructor(tz, "2008-12-31 23:59:59 UTC", :utc, [2008,12,31,23,59,59])
244         assert_time_constructor(tz, "2009-01-01 00:00:00 UTC", :utc, [2008,12,31,23,59,60])
245         assert_time_constructor(tz, "2009-01-01 00:00:00 UTC", :utc, [2008,12,31,24,0,0])
246         assert_time_constructor(tz, "2009-01-01 00:00:00 UTC", :utc, [2009,1,1,0,0,0])
247         assert_equal(4102444800, Time.utc(2100,1,1,0,0,0).to_i)
248       }
249     }
250   end if has_right_tz
252   def test_right_america_los_angeles
253     with_tz(tz="right/America/Los_Angeles") {
254       assert_time_constructor(tz, "2008-12-31 15:59:59 -0800", :local, [2008,12,31,15,59,59])
255       assert_time_constructor(tz, "2008-12-31 15:59:60 -0800", :local, [2008,12,31,15,59,60])
256       assert_time_constructor(tz, "2008-12-31 16:00:00 -0800", :local, [2008,12,31,16,0,0])
257     }
258   end if has_right_tz
260   def test_utc_names
261     assert_predicate(Time.new(2019, 1, 1, 0, 0, 0, "UTC"), :utc?)
262     assert_predicate(Time.new(2019, 1, 1, 0, 0, 0, "utc"), :utc?)
263     assert_predicate(Time.new(2019, 1, 1, 0, 0, 0, "Z"), :utc?)
264     assert_predicate(Time.new(2019, 1, 1, 0, 0, 0, "-00:00"), :utc?)
265     assert_not_predicate(Time.new(2019, 1, 1, 0, 0, 0, "+00:00"), :utc?)
266   end
268   def test_military_names
269     assert_equal( +1*3600, Time.new(2019, 1, 1, 0, 0, 0, "A").gmtoff)
270     assert_equal( +2*3600, Time.new(2019, 1, 1, 0, 0, 0, "B").gmtoff)
271     assert_equal( +3*3600, Time.new(2019, 1, 1, 0, 0, 0, "C").gmtoff)
272     assert_equal( +4*3600, Time.new(2019, 1, 1, 0, 0, 0, "D").gmtoff)
273     assert_equal( +5*3600, Time.new(2019, 1, 1, 0, 0, 0, "E").gmtoff)
274     assert_equal( +6*3600, Time.new(2019, 1, 1, 0, 0, 0, "F").gmtoff)
275     assert_equal( +7*3600, Time.new(2019, 1, 1, 0, 0, 0, "G").gmtoff)
276     assert_equal( +8*3600, Time.new(2019, 1, 1, 0, 0, 0, "H").gmtoff)
277     assert_equal( +9*3600, Time.new(2019, 1, 1, 0, 0, 0, "I").gmtoff)
278     assert_equal(+10*3600, Time.new(2019, 1, 1, 0, 0, 0, "K").gmtoff)
279     assert_equal(+11*3600, Time.new(2019, 1, 1, 0, 0, 0, "L").gmtoff)
280     assert_equal(+12*3600, Time.new(2019, 1, 1, 0, 0, 0, "M").gmtoff)
281     assert_equal( -1*3600, Time.new(2019, 1, 1, 0, 0, 0, "N").gmtoff)
282     assert_equal( -2*3600, Time.new(2019, 1, 1, 0, 0, 0, "O").gmtoff)
283     assert_equal( -3*3600, Time.new(2019, 1, 1, 0, 0, 0, "P").gmtoff)
284     assert_equal( -4*3600, Time.new(2019, 1, 1, 0, 0, 0, "Q").gmtoff)
285     assert_equal( -5*3600, Time.new(2019, 1, 1, 0, 0, 0, "R").gmtoff)
286     assert_equal( -6*3600, Time.new(2019, 1, 1, 0, 0, 0, "S").gmtoff)
287     assert_equal( -7*3600, Time.new(2019, 1, 1, 0, 0, 0, "T").gmtoff)
288     assert_equal( -8*3600, Time.new(2019, 1, 1, 0, 0, 0, "U").gmtoff)
289     assert_equal( -9*3600, Time.new(2019, 1, 1, 0, 0, 0, "V").gmtoff)
290     assert_equal(-10*3600, Time.new(2019, 1, 1, 0, 0, 0, "W").gmtoff)
291     assert_equal(-11*3600, Time.new(2019, 1, 1, 0, 0, 0, "X").gmtoff)
292     assert_equal(-12*3600, Time.new(2019, 1, 1, 0, 0, 0, "Y").gmtoff)
293     assert_equal(       0, Time.new(2019, 1, 1, 0, 0, 0, "Z").gmtoff)
295     assert_equal( +1*3600, Time.at(0, in: "A").gmtoff)
296     assert_equal( +2*3600, Time.at(0, in: "B").gmtoff)
297     assert_equal( +3*3600, Time.at(0, in: "C").gmtoff)
298     assert_equal( +4*3600, Time.at(0, in: "D").gmtoff)
299     assert_equal( +5*3600, Time.at(0, in: "E").gmtoff)
300     assert_equal( +6*3600, Time.at(0, in: "F").gmtoff)
301     assert_equal( +7*3600, Time.at(0, in: "G").gmtoff)
302     assert_equal( +8*3600, Time.at(0, in: "H").gmtoff)
303     assert_equal( +9*3600, Time.at(0, in: "I").gmtoff)
304     assert_equal(+10*3600, Time.at(0, in: "K").gmtoff)
305     assert_equal(+11*3600, Time.at(0, in: "L").gmtoff)
306     assert_equal(+12*3600, Time.at(0, in: "M").gmtoff)
307     assert_equal( -1*3600, Time.at(0, in: "N").gmtoff)
308     assert_equal( -2*3600, Time.at(0, in: "O").gmtoff)
309     assert_equal( -3*3600, Time.at(0, in: "P").gmtoff)
310     assert_equal( -4*3600, Time.at(0, in: "Q").gmtoff)
311     assert_equal( -5*3600, Time.at(0, in: "R").gmtoff)
312     assert_equal( -6*3600, Time.at(0, in: "S").gmtoff)
313     assert_equal( -7*3600, Time.at(0, in: "T").gmtoff)
314     assert_equal( -8*3600, Time.at(0, in: "U").gmtoff)
315     assert_equal( -9*3600, Time.at(0, in: "V").gmtoff)
316     assert_equal(-10*3600, Time.at(0, in: "W").gmtoff)
317     assert_equal(-11*3600, Time.at(0, in: "X").gmtoff)
318     assert_equal(-12*3600, Time.at(0, in: "Y").gmtoff)
319     assert_equal(       0, Time.at(0, in: "Z").gmtoff)
320   end
322   MON2NUM = {
323     "Jan" => 1, "Feb" => 2, "Mar" => 3, "Apr" => 4, "May" => 5, "Jun" => 6,
324     "Jul" => 7, "Aug" => 8, "Sep" => 9, "Oct" => 10, "Nov" => 11, "Dec" => 12
325   }
327   @testnum = 0
328   def self.gen_test_name(hint)
329     @testnum += 1
330     s = "test_gen_#{@testnum}"
331     s.sub(/gen_/) { "gen" + "_#{hint}_".gsub(/[^0-9A-Za-z]+/, '_') }
332   end
334   def self.parse_zdump_line(line)
335     return nil if /\A\#/ =~ line || /\A\s*\z/ =~ line
336     if /\A(\S+)\s+
337         \S+\s+(\S+)\s+(\d+)\s+(\d\d):(\d\d):(\d\d)\s+(\d+)\s+UTC?
338         \s+=\s+
339         \S+\s+(\S+)\s+(\d+)\s+(\d\d):(\d\d):(\d\d)\s+(\d+)\s+\S+
340         \s+isdst=\d+\s+gmtoff=(-?\d+)\n
341         \z/x !~ line
342       raise "unexpected zdump line: #{line.inspect}"
343     end
344     tz, u_mon, u_day, u_hour, u_min, u_sec, u_year,
345       l_mon, l_day, l_hour, l_min, l_sec, l_year, gmtoff = $~.captures
346     u_year = u_year.to_i
347     u_mon = MON2NUM[u_mon]
348     u_day = u_day.to_i
349     u_hour = u_hour.to_i
350     u_min = u_min.to_i
351     u_sec = u_sec.to_i
352     l_year = l_year.to_i
353     l_mon = MON2NUM[l_mon]
354     l_day = l_day.to_i
355     l_hour = l_hour.to_i
356     l_min = l_min.to_i
357     l_sec = l_sec.to_i
358     gmtoff = gmtoff.to_i
359     [tz,
360      [u_year, u_mon, u_day, u_hour, u_min, u_sec],
361      [l_year, l_mon, l_day, l_hour, l_min, l_sec],
362      gmtoff]
363   end
365   def self.gen_zdump_test(data)
366     sample = []
367     data.each_line {|line|
368       s = parse_zdump_line(line)
369       sample << s if s
370     }
371     sample.each {|tz, u, l, gmtoff|
372       expected_utc = "%04d-%02d-%02d %02d:%02d:%02d UTC" % u
373       expected = "%04d-%02d-%02d %02d:%02d:%02d %s" % (l+[format_gmtoff(gmtoff)])
374       mesg_utc = "TZ=#{tz} Time.utc(#{u.map {|arg| arg.inspect }.join(', ')})"
375       mesg = "#{mesg_utc}.localtime"
376       define_method(gen_test_name(tz)) {
377         with_tz(tz) {
378           t = nil
379           assert_nothing_raised(mesg) { t = Time.utc(*u) }
380           assert_equal(expected_utc, time_to_s(t), mesg_utc)
381           assert_nothing_raised(mesg) { t.localtime }
382           assert_equal(expected, time_to_s(t), mesg)
383           assert_equal(gmtoff, t.gmtoff)
384           assert_equal(format_gmtoff(gmtoff), t.strftime("%z"))
385           assert_equal(format_gmtoff(gmtoff, true), t.strftime("%:z"))
386           assert_equal(format_gmtoff2(gmtoff), t.strftime("%::z"))
387           assert_equal(Encoding::US_ASCII, t.zone.encoding)
388         }
389       }
390     }
392     group_by(sample) {|tz, _, _, _| tz }.each {|tz, a|
393       a.each_with_index {|(_, _, l, gmtoff), i|
394         expected = "%04d-%02d-%02d %02d:%02d:%02d %s" % (l+[format_gmtoff(gmtoff)])
395         monotonic_to_past = i == 0 || (a[i-1][2] <=> l) < 0
396         monotonic_to_future = i == a.length-1 || (l <=> a[i+1][2]) < 0
397         if monotonic_to_past && monotonic_to_future
398           define_method(gen_test_name(tz)) {
399             with_tz(tz) {
400               assert_time_constructor(tz, expected, :local, l)
401               assert_time_constructor(tz, expected, :local, l.reverse+[nil, nil, false, nil])
402               assert_time_constructor(tz, expected, :local, l.reverse+[nil, nil, true, nil])
403               assert_time_constructor(tz, expected, :new, l)
404               assert_time_constructor(tz, expected, :new, l+[:std])
405               assert_time_constructor(tz, expected, :new, l+[:dst])
406             }
407           }
408         elsif monotonic_to_past && !monotonic_to_future
409           define_method(gen_test_name(tz)) {
410             with_tz(tz) {
411               assert_time_constructor(tz, expected, :local, l.reverse+[nil, nil, true, nil])
412               assert_time_constructor(tz, expected, :new, l+[:dst])
413             }
414           }
415         elsif !monotonic_to_past && monotonic_to_future
416           define_method(gen_test_name(tz)) {
417             with_tz(tz) {
418               assert_time_constructor(tz, expected, :local, l.reverse+[nil, nil, false, nil])
419               assert_time_constructor(tz, expected, :new, l+[:std])
420             }
421           }
422         else
423           define_method(gen_test_name(tz)) {
424             flunk("time in reverse order: TZ=#{tz} #{expected}")
425           }
426         end
427       }
428     }
429   end
431   gen_zdump_test <<'End'
432 America/Lima  Sun Apr  1 03:59:59 1990 UTC = Sat Mar 31 23:59:59 1990 PEST isdst=1 gmtoff=-14400
433 America/Lima  Sun Apr  1 04:00:00 1990 UTC = Sat Mar 31 23:00:00 1990 PET isdst=0 gmtoff=-18000
434 America/Lima  Sat Jan  1 04:59:59 1994 UTC = Fri Dec 31 23:59:59 1993 PET isdst=0 gmtoff=-18000
435 America/Lima  Sat Jan  1 05:00:00 1994 UTC = Sat Jan  1 01:00:00 1994 PEST isdst=1 gmtoff=-14400
436 America/Lima  Fri Apr  1 03:59:59 1994 UTC = Thu Mar 31 23:59:59 1994 PEST isdst=1 gmtoff=-14400
437 America/Lima  Fri Apr  1 04:00:00 1994 UTC = Thu Mar 31 23:00:00 1994 PET isdst=0 gmtoff=-18000
438 America/Los_Angeles  Sun Apr  2 09:59:59 2006 UTC = Sun Apr  2 01:59:59 2006 PST isdst=0 gmtoff=-28800
439 America/Los_Angeles  Sun Apr  2 10:00:00 2006 UTC = Sun Apr  2 03:00:00 2006 PDT isdst=1 gmtoff=-25200
440 America/Los_Angeles  Sun Oct 29 08:59:59 2006 UTC = Sun Oct 29 01:59:59 2006 PDT isdst=1 gmtoff=-25200
441 America/Los_Angeles  Sun Oct 29 09:00:00 2006 UTC = Sun Oct 29 01:00:00 2006 PST isdst=0 gmtoff=-28800
442 America/Los_Angeles  Sun Mar 11 09:59:59 2007 UTC = Sun Mar 11 01:59:59 2007 PST isdst=0 gmtoff=-28800
443 America/Los_Angeles  Sun Mar 11 10:00:00 2007 UTC = Sun Mar 11 03:00:00 2007 PDT isdst=1 gmtoff=-25200
444 America/Los_Angeles  Sun Nov  4 08:59:59 2007 UTC = Sun Nov  4 01:59:59 2007 PDT isdst=1 gmtoff=-25200
445 America/Los_Angeles  Sun Nov  4 09:00:00 2007 UTC = Sun Nov  4 01:00:00 2007 PST isdst=0 gmtoff=-28800
446 America/Managua  Thu Sep 24 04:59:59 1992 UTC = Wed Sep 23 23:59:59 1992 EST isdst=0 gmtoff=-18000
447 America/Managua  Thu Sep 24 05:00:00 1992 UTC = Wed Sep 23 23:00:00 1992 CST isdst=0 gmtoff=-21600
448 America/Managua  Fri Jan  1 05:59:59 1993 UTC = Thu Dec 31 23:59:59 1992 CST isdst=0 gmtoff=-21600
449 America/Managua  Fri Jan  1 06:00:00 1993 UTC = Fri Jan  1 01:00:00 1993 EST isdst=0 gmtoff=-18000
450 America/Managua  Wed Jan  1 04:59:59 1997 UTC = Tue Dec 31 23:59:59 1996 EST isdst=0 gmtoff=-18000
451 America/Managua  Wed Jan  1 05:00:00 1997 UTC = Tue Dec 31 23:00:00 1996 CST isdst=0 gmtoff=-21600
452 Asia/Singapore  Sun Aug  8 16:30:00 1965 UTC = Mon Aug  9 00:00:00 1965 SGT isdst=0 gmtoff=27000
453 Asia/Singapore  Thu Dec 31 16:29:59 1981 UTC = Thu Dec 31 23:59:59 1981 SGT isdst=0 gmtoff=27000
454 Asia/Singapore  Thu Dec 31 16:30:00 1981 UTC = Fri Jan  1 00:30:00 1982 SGT isdst=0 gmtoff=28800
456   gen_zdump_test CORRECT_TOKYO_DST_1951 ? <<'End' + (CORRECT_TOKYO_DST_1951 < "2018f" ? <<'2018e' : <<'2018f') : <<'End'
457 Asia/Tokyo  Sat May  5 14:59:59 1951 UTC = Sat May  5 23:59:59 1951 JST isdst=0 gmtoff=32400
458 Asia/Tokyo  Sat May  5 15:00:00 1951 UTC = Sun May  6 01:00:00 1951 JDT isdst=1 gmtoff=36000
460 Asia/Tokyo  Sat Sep  8 13:59:59 1951 UTC = Sat Sep  8 23:59:59 1951 JDT isdst=1 gmtoff=36000
461 Asia/Tokyo  Sat Sep  8 14:00:00 1951 UTC = Sat Sep  8 23:00:00 1951 JST isdst=0 gmtoff=32400
462 2018e
463 Asia/Tokyo  Sat Sep  8 14:59:59 1951 UTC = Sun Sep  9 00:59:59 1951 JDT isdst=1 gmtoff=36000
464 Asia/Tokyo  Sat Sep  8 15:00:00 1951 UTC = Sun Sep  9 00:00:00 1951 JST isdst=0 gmtoff=32400
465 2018f
466 Asia/Tokyo  Sat May  5 16:59:59 1951 UTC = Sun May  6 01:59:59 1951 JST isdst=0 gmtoff=32400
467 Asia/Tokyo  Sat May  5 17:00:00 1951 UTC = Sun May  6 03:00:00 1951 JDT isdst=1 gmtoff=36000
468 Asia/Tokyo  Fri Sep  7 15:59:59 1951 UTC = Sat Sep  8 01:59:59 1951 JDT isdst=1 gmtoff=36000
469 Asia/Tokyo  Fri Sep  7 16:00:00 1951 UTC = Sat Sep  8 01:00:00 1951 JST isdst=0 gmtoff=32400
471   gen_zdump_test <<'End'
472 America/St_Johns  Sun Mar 11 03:30:59 2007 UTC = Sun Mar 11 00:00:59 2007 NST isdst=0 gmtoff=-12600
473 America/St_Johns  Sun Mar 11 03:31:00 2007 UTC = Sun Mar 11 01:01:00 2007 NDT isdst=1 gmtoff=-9000
474 America/St_Johns  Sun Nov  4 02:30:59 2007 UTC = Sun Nov  4 00:00:59 2007 NDT isdst=1 gmtoff=-9000
475 America/St_Johns  Sun Nov  4 02:31:00 2007 UTC = Sat Nov  3 23:01:00 2007 NST isdst=0 gmtoff=-12600
476 Europe/Brussels  Sun Apr 30 22:59:59 1916 UTC = Sun Apr 30 23:59:59 1916 CET isdst=0 gmtoff=3600
477 Europe/Brussels  Sun Apr 30 23:00:00 1916 UTC = Mon May  1 01:00:00 1916 CEST isdst=1 gmtoff=7200
478 Europe/Brussels  Sat Sep 30 22:59:59 1916 UTC = Sun Oct  1 00:59:59 1916 CEST isdst=1 gmtoff=7200
479 Europe/Brussels  Sat Sep 30 23:00:00 1916 UTC = Sun Oct  1 00:00:00 1916 CET isdst=0 gmtoff=3600
480 Europe/London  Sun Mar 16 01:59:59 1947 UTC = Sun Mar 16 01:59:59 1947 GMT isdst=0 gmtoff=0
481 Europe/London  Sun Mar 16 02:00:00 1947 UTC = Sun Mar 16 03:00:00 1947 BST isdst=1 gmtoff=3600
482 Europe/London  Sun Apr 13 00:59:59 1947 UTC = Sun Apr 13 01:59:59 1947 BST isdst=1 gmtoff=3600
483 Europe/London  Sun Apr 13 01:00:00 1947 UTC = Sun Apr 13 03:00:00 1947 BDST isdst=1 gmtoff=7200
484 Europe/London  Sun Aug 10 00:59:59 1947 UTC = Sun Aug 10 02:59:59 1947 BDST isdst=1 gmtoff=7200
485 Europe/London  Sun Aug 10 01:00:00 1947 UTC = Sun Aug 10 02:00:00 1947 BST isdst=1 gmtoff=3600
486 Europe/London  Sun Nov  2 01:59:59 1947 UTC = Sun Nov  2 02:59:59 1947 BST isdst=1 gmtoff=3600
487 Europe/London  Sun Nov  2 02:00:00 1947 UTC = Sun Nov  2 02:00:00 1947 GMT isdst=0 gmtoff=0
489   if CORRECT_KIRITIMATI_SKIP_1994
490     gen_zdump_test <<'End'
491 Pacific/Kiritimati  Sat Dec 31 09:59:59 1994 UTC = Fri Dec 30 23:59:59 1994 LINT isdst=0 gmtoff=-36000
492 Pacific/Kiritimati  Sat Dec 31 10:00:00 1994 UTC = Sun Jan  1 00:00:00 1995 LINT isdst=0 gmtoff=50400
494   else
495     gen_zdump_test <<'End'
496 Pacific/Kiritimati  Sun Jan  1 09:59:59 1995 UTC = Sat Dec 31 23:59:59 1994 LINT isdst=0 gmtoff=-36000
497 Pacific/Kiritimati  Sun Jan  1 10:00:00 1995 UTC = Mon Jan  2 00:00:00 1995 LINT isdst=0 gmtoff=50400
499   end
500   gen_zdump_test <<'End' if has_right_tz
501 right/America/Los_Angeles  Fri Jun 30 23:59:60 1972 UTC = Fri Jun 30 16:59:60 1972 PDT isdst=1 gmtoff=-25200
502 right/America/Los_Angeles  Wed Dec 31 23:59:60 2008 UTC = Wed Dec 31 15:59:60 2008 PST isdst=0 gmtoff=-28800
503 #right/Asia/Tokyo  Fri Jun 30 23:59:60 1972 UTC = Sat Jul  1 08:59:60 1972 JST isdst=0 gmtoff=32400
504 #right/Asia/Tokyo  Sat Dec 31 23:59:60 2005 UTC = Sun Jan  1 08:59:60 2006 JST isdst=0 gmtoff=32400
505 right/Europe/Paris  Fri Jun 30 23:59:60 1972 UTC = Sat Jul  1 00:59:60 1972 CET isdst=0 gmtoff=3600
506 right/Europe/Paris  Wed Dec 31 23:59:60 2008 UTC = Thu Jan  1 00:59:60 2009 CET isdst=0 gmtoff=3600
509   def self.gen_variational_zdump_test(hint, data)
510     sample = []
511     data.each_line {|line|
512       s = parse_zdump_line(line)
513       sample << s if s
514     }
516     define_method(gen_test_name(hint)) {
517       results = []
518       sample.each {|tz, u, l, gmtoff|
519         expected_utc = "%04d-%02d-%02d %02d:%02d:%02d UTC" % u
520         expected = "%04d-%02d-%02d %02d:%02d:%02d %s" % (l+[format_gmtoff(gmtoff)])
521         mesg_utc = "TZ=#{tz} Time.utc(#{u.map {|arg| arg.inspect }.join(', ')})"
522         mesg = "#{mesg_utc}.localtime"
523         with_tz(tz) {
524           t = nil
525           assert_nothing_raised(mesg) { t = Time.utc(*u) }
526           assert_equal(expected_utc, time_to_s(t), mesg_utc)
527           assert_nothing_raised(mesg) { t.localtime }
529           results << [
530             expected == time_to_s(t),
531             gmtoff == t.gmtoff,
532             format_gmtoff(gmtoff) == t.strftime("%z"),
533             format_gmtoff(gmtoff, true) == t.strftime("%:z"),
534             format_gmtoff2(gmtoff) == t.strftime("%::z")
535           ]
536         }
537       }
538       assert_include(results, [true, true, true, true, true])
539     }
540   end
542   # tzdata-2014g fixed the offset for lisbon from -0:36:32 to -0:36:45.
543   # [ruby-core:65058] [Bug #10245]
544   gen_variational_zdump_test "lisbon", <<'End' if has_lisbon_tz
545 Europe/Lisbon  Mon Jan  1 00:36:31 1912 UTC = Sun Dec 31 23:59:59 1911 LMT isdst=0 gmtoff=-2192
546 Europe/Lisbon  Mon Jan  1 00:36:44 1912 UT = Sun Dec 31 23:59:59 1911 LMT isdst=0 gmtoff=-2205
547 Europe/Lisbon  Sun Dec 31 23:59:59 1911 UT = Sun Dec 31 23:23:14 1911 LMT isdst=0 gmtoff=-2205
550   class TZ
551     attr_reader :name
553     def initialize(name, abbr, offset, abbr2 = nil, offset2 = nil)
554       @name = name
555       @abbr = abbr
556       @offset = offset
557       @abbr2 = abbr2
558       @offset2 = offset2
559     end
561     def dst?(t)
562       return false unless @offset2
563       case t when Integer
564         return nil
565       end
566       case t.mon
567       when 4..9
568         true
569       else
570         false
571       end
572     end
574     def offset(t)
575       (dst?(t) ? @offset2 : @offset)
576     end
578     def local_to_utc(t)
579       t - offset(t)
580     end
582     def utc_to_local(t)
583       t + offset(t)
584     end
586     def abbr(t)
587       dst?(t) ? @abbr2 : @abbr
588     end
590     def ==(other)
591       @name == other.name and abbr(0) == other.abbr(0) and offset(0) == other.offset(0)
592     end
594     def inspect
595       "#<TZ: #@name #@abbr #@offset>"
596     end
597   end
600 module TestTimeTZ::WithTZ
601   def subtest_new(time_class, tz, tzarg, tzname, abbr, utc_offset)
602     abbr, abbr2 = *abbr
603     utc_offset, utc_offset2 = *utc_offset
604     t = time_class.new(2018, 9, 1, 12, 0, 0, tzarg)
605     utc_offset, abbr = utc_offset2, abbr2 if tz.dst?(t)
606     assert_equal([2018, 9, 1, 12, 0, 0, tz], [t.year, t.mon, t.mday, t.hour, t.min, t.sec, t.zone])
607     h, m = (-utc_offset / 60).divmod(60)
608     assert_equal(time_class.utc(2018, 9, 1, 12+h, m, 0).to_i, t.to_i)
609     assert_equal(6, t.wday)
610     assert_equal(244, t.yday)
611     assert_equal(t, time_class.new(2018, 9, 1, 12, in: tzarg))
612     assert_raise(ArgumentError) {time_class.new(2018, 9, 1, 12, 0, 0, tzarg, in: tzarg)}
613   end
615   def subtest_now(time_class, tz, tzarg, tzname, abbr, utc_offset)
616     t = time_class.now(in: tzarg)
617     assert_equal(tz, t.zone)
618   end
620   def subtest_getlocal(time_class, tz, tzarg, tzname, abbr, utc_offset)
621     abbr, abbr2 = *abbr
622     utc_offset, utc_offset2 = *utc_offset
623     utc = time_class.utc(2018, 9, 1, 12, 0, 0)
624     utc_offset, abbr = utc_offset2, abbr2 if tz.dst?(utc)
625     t = utc.getlocal(tzarg)
626     h, m = (utc_offset / 60).divmod(60)
627     assert_equal([2018, 9, 1, 12+h, m, 0, tz], [t.year, t.mon, t.mday, t.hour, t.min, t.sec, t.zone])
628     assert_equal(time_class.utc(2018, 9, 1, 12, 0, 0), t)
629   end
631   def subtest_strftime(time_class, tz, tzarg, tzname, abbr, utc_offset)
632     abbr, abbr2 = *abbr
633     utc_offset, utc_offset2 = *utc_offset
634     t = time_class.new(2018, 9, 1, 12, 0, 0, tzarg)
635     utc_offset, abbr = utc_offset2, abbr2 if tz.dst?(t)
636     h, m = (utc_offset.abs / 60).divmod(60)
637     h = -h if utc_offset < 0
638     assert_equal("%+.2d%.2d %s" % [h, m, abbr], t.strftime("%z %Z"))
639     assert_equal("34 35 35", t.strftime("%U %V %W"))
640   end
642   def subtest_plus(time_class, tz, tzarg, tzname, abbr, utc_offset)
643     abbr, abbr2 = *abbr
644     utc_offset, utc_offset2 = *utc_offset
645     t = time_class.new(2018, 9, 1, 12, 0, 0, tzarg) + 4000
646     utc_offset, abbr = utc_offset2, abbr2 if tz.dst?(t)
647     assert_equal([2018, 9, 1, 13, 6, 40, tz], [t.year, t.mon, t.mday, t.hour, t.min, t.sec, t.zone])
648     m, s = (4000-utc_offset).divmod(60)
649     h, m = m.divmod(60)
650     assert_equal(time_class.utc(2018, 9, 1, 12+h, m, s), t)
651   end
653   def subtest_at(time_class, tz, tzarg, tzname, abbr, utc_offset)
654     abbr, abbr2 = *abbr
655     utc_offset, utc_offset2 = *utc_offset
656     utc = time_class.utc(2018, 9, 1, 12, 0, 0)
657     utc_offset, abbr = utc_offset2, abbr2 if tz.dst?(utc)
658     h, m = (utc_offset / 60).divmod(60)
659     t = time_class.at(utc, in: tzarg)
660     assert_equal([2018, 9, 1, 12+h, m, 0, tz], [t.year, t.mon, t.mday, t.hour, t.min, t.sec, t.zone])
661     assert_equal(utc.to_i, t.to_i)
662     utc = utc.to_i
663     t = time_class.at(utc, in: tzarg)
664     assert_equal([2018, 9, 1, 12+h, m, 0, tz], [t.year, t.mon, t.mday, t.hour, t.min, t.sec, t.zone])
665     assert_equal(utc, t.to_i)
666   end
668   def subtest_to_a(time_class, tz, tzarg, tzname, abbr, utc_offset)
669     t = time_class.new(2018, 9, 1, 12, 0, 0, tzarg)
670     ary = t.to_a
671     assert_equal(ary, [t.sec, t.min, t.hour, t.mday, t.mon, t.year, t.wday, t.yday, t.isdst, t.zone])
672   end
674   def subtest_marshal(time_class, tz, tzarg, tzname, abbr, utc_offset)
675     t = time_class.new(2018, 9, 1, 12, 0, 0, tzarg)
676     t2 = Marshal.load(Marshal.dump(t))
677     assert_equal(t, t2)
678     assert_equal(t.utc_offset, t2.utc_offset)
679     assert_equal(t.utc_offset, (t2+1).utc_offset)
680     assert_instance_of(t.zone.class, t2.zone)
681     assert_equal(t.dst?, t2.dst?)
682   end
684   def test_invalid_zone
685     make_timezone("INVALID", "INV", 0)
686   rescue => e
687     assert_kind_of(StandardError, e)
688   else
689     assert false, "ArgumentError expected but nothing was raised."
690   end
692   def nametest_marshal_compatibility(time_class, tzname, abbr, utc_offset)
693     data = [
694       "\x04\x08Iu:".b, Marshal.dump(time_class)[3..-1],
695       "\x0d""\xEF\xA7\x1D\x80\x00\x00\x00\x00".b,
696       Marshal.dump({offset: utc_offset, zone: abbr})[3..-1],
697     ].join('')
698     t = Marshal.load(data)
699     assert_equal(utc_offset, t.utc_offset)
700     assert_equal(utc_offset, (t+1).utc_offset)
701     # t.zone may be a mere String or timezone object.
702   end
704   ZONES = {
705     "Asia/Tokyo" => ["JST", +9*3600],
706     "America/Los_Angeles" => ["PST", -8*3600, "PDT", -7*3600],
707     "Africa/Ndjamena" => ["WAT", +1*3600],
708   }
710   def make_timezone(tzname, abbr, utc_offset, abbr2 = nil, utc_offset2 = nil)
711     self.class::TIME_CLASS.find_timezone(tzname)
712   end
714   def subtest_dst?(time_class, tz, tzarg, tzname, abbr, utc_offset)
715     t = time_class.new(2018, 6, 22, 12, 0, 0, tzarg)
716     return unless tz.dst?(t)
717     assert_predicate t, :dst?
718     t = time_class.new(2018, 12, 22, 12, 0, 0, tzarg)
719     assert_not_predicate t, :dst?
720   end
722   instance_methods(false).grep(/\Asub(?=test_)/) do |subtest|
723     test = $'
724     ZONES.each_pair do |tzname, (abbr, utc_offset, abbr2, utc_offset2)|
725       define_method("#{test}@#{tzname}") do
726         tz = make_timezone(tzname, abbr, utc_offset, abbr2, utc_offset2)
727         time_class = self.class::TIME_CLASS
728         __send__(subtest, time_class, tz, tz, tzname, [abbr, abbr2], [utc_offset, utc_offset2])
729         __send__(subtest, time_class, tz, tzname, tzname, [abbr, abbr2], [utc_offset, utc_offset2])
730       end
731     end
732   end
734   instance_methods(false).grep(/\Aname(?=test_)/) do |subtest|
735     test = $'
736     ZONES.each_pair do |tzname, (abbr, utc_offset)|
737       define_method("#{test}@#{tzname}") do
738         time_class = self.class::TIME_CLASS
739         __send__(subtest, time_class, tzname, abbr, utc_offset)
740       end
741     end
742   end
745 class TestTimeTZ::DummyTZ < Test::Unit::TestCase
746   include TestTimeTZ::WithTZ
748   class TIME_CLASS < ::Time
749     ZONES = TestTimeTZ::WithTZ::ZONES
750     def self.find_timezone(tzname)
751       tz = ZONES[tzname] or raise ArgumentError, "Unknown timezone: #{name}"
752       TestTimeTZ::TZ.new(tzname, *tz)
753     end
754   end
756   def self.make_timezone(tzname, abbr, utc_offset, abbr2 = nil, utc_offset2 = nil)
757     TestTimeTZ::TZ.new(tzname, abbr, utc_offset, abbr2, utc_offset2)
758   end
760   def test_fractional_second
761     x = Object.new
762     def x.local_to_utc(t); t + 8*3600; end
763     def x.utc_to_local(t); t - 8*3600; end
765     t1 = Time.new(2020,11,11,12,13,14.124r, '-08:00')
766     t2 = Time.new(2020,11,11,12,13,14.124r, x)
767     assert_equal(t1, t2)
768   end
771 begin
772   require "tzinfo"
773 rescue LoadError
774 else
775   class TestTimeTZ::GemTZInfo < Test::Unit::TestCase
776     include TestTimeTZ::WithTZ
778     class TIME_CLASS < ::Time
779       def self.find_timezone(tzname)
780         TZInfo::Timezone.get(tzname)
781       end
782     end
784     def tz
785       @tz ||= TZInfo::Timezone.get(tzname)
786     end
787   end
790 begin
791   require "timezone"
792 rescue LoadError
793 else
794   class TestTimeTZ::GemTimezone < Test::Unit::TestCase
795     include TestTimeTZ::WithTZ
797     class TIME_CLASS < ::Time
798       def self.find_timezone(name)
799         Timezone.fetch(name)
800       end
801     end
803     def tz
804       @tz ||= Timezone[tzname]
805     end
806   end