tzinfo 1.2.5 → 1.2.9

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of tzinfo might be problematic. Click here for more details.

Files changed (66) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +3 -3
  3. data.tar.gz.sig +0 -0
  4. data/CHANGES.md +86 -48
  5. data/LICENSE +1 -1
  6. data/README.md +9 -8
  7. data/lib/tzinfo.rb +3 -0
  8. data/lib/tzinfo/annual_rules.rb +51 -0
  9. data/lib/tzinfo/data_source.rb +1 -1
  10. data/lib/tzinfo/posix_time_zone_parser.rb +136 -0
  11. data/lib/tzinfo/ruby_core_support.rb +24 -1
  12. data/lib/tzinfo/ruby_data_source.rb +24 -20
  13. data/lib/tzinfo/time_or_datetime.rb +11 -0
  14. data/lib/tzinfo/timezone.rb +10 -6
  15. data/lib/tzinfo/transition_rule.rb +325 -0
  16. data/lib/tzinfo/zoneinfo_data_source.rb +10 -1
  17. data/lib/tzinfo/zoneinfo_timezone_info.rb +264 -40
  18. data/test/tc_annual_rules.rb +95 -0
  19. data/test/tc_country.rb +6 -2
  20. data/test/tc_posix_time_zone_parser.rb +261 -0
  21. data/test/tc_ruby_data_source.rb +26 -2
  22. data/test/tc_time_or_datetime.rb +26 -6
  23. data/test/tc_timezone.rb +13 -2
  24. data/test/tc_transition_data_timezone_info.rb +11 -1
  25. data/test/tc_transition_rule.rb +663 -0
  26. data/test/tc_zoneinfo_data_source.rb +11 -2
  27. data/test/tc_zoneinfo_timezone_info.rb +1034 -113
  28. data/test/test_utils.rb +32 -3
  29. data/test/ts_all_zoneinfo.rb +3 -1
  30. data/test/tzinfo-data/tzinfo/data/definitions/America/Argentina/Buenos_Aires.rb +5 -5
  31. data/test/tzinfo-data/tzinfo/data/definitions/America/New_York.rb +13 -1
  32. data/test/tzinfo-data/tzinfo/data/definitions/Australia/Melbourne.rb +13 -1
  33. data/test/tzinfo-data/tzinfo/data/definitions/EST.rb +1 -1
  34. data/test/tzinfo-data/tzinfo/data/definitions/Etc/GMT__m__1.rb +2 -2
  35. data/test/tzinfo-data/tzinfo/data/definitions/Etc/GMT__p__1.rb +2 -2
  36. data/test/tzinfo-data/tzinfo/data/definitions/Etc/UTC.rb +1 -1
  37. data/test/tzinfo-data/tzinfo/data/definitions/Europe/Amsterdam.rb +15 -3
  38. data/test/tzinfo-data/tzinfo/data/definitions/Europe/Andorra.rb +13 -1
  39. data/test/tzinfo-data/tzinfo/data/definitions/Europe/London.rb +13 -1
  40. data/test/tzinfo-data/tzinfo/data/definitions/Europe/Paris.rb +15 -3
  41. data/test/tzinfo-data/tzinfo/data/definitions/Europe/Prague.rb +19 -4
  42. data/test/tzinfo-data/tzinfo/data/definitions/UTC.rb +1 -1
  43. data/test/tzinfo-data/tzinfo/data/indexes/countries.rb +197 -184
  44. data/test/tzinfo-data/tzinfo/data/indexes/timezones.rb +60 -47
  45. data/test/tzinfo-data/tzinfo/data/version.rb +9 -3
  46. data/test/zoneinfo/America/Argentina/Buenos_Aires +0 -0
  47. data/test/zoneinfo/America/New_York +0 -0
  48. data/test/zoneinfo/Australia/Melbourne +0 -0
  49. data/test/zoneinfo/EST +0 -0
  50. data/test/zoneinfo/Etc/UTC +0 -0
  51. data/test/zoneinfo/Europe/Amsterdam +0 -0
  52. data/test/zoneinfo/Europe/Andorra +0 -0
  53. data/test/zoneinfo/Europe/London +0 -0
  54. data/test/zoneinfo/Europe/Paris +0 -0
  55. data/test/zoneinfo/Europe/Prague +0 -0
  56. data/test/zoneinfo/Factory +0 -0
  57. data/test/zoneinfo/iso3166.tab +13 -14
  58. data/test/zoneinfo/leapseconds +38 -21
  59. data/test/zoneinfo/posix/Europe/London +0 -0
  60. data/test/zoneinfo/posixrules +0 -0
  61. data/test/zoneinfo/right/Europe/London +0 -0
  62. data/test/zoneinfo/zone.tab +172 -159
  63. data/test/zoneinfo/zone1970.tab +185 -170
  64. data/tzinfo.gemspec +2 -2
  65. metadata +28 -24
  66. metadata.gz.sig +0 -0
@@ -7,6 +7,15 @@ require 'tmpdir'
7
7
 
8
8
  include TZInfo
9
9
 
10
+ # Use send as a workaround for an issue on JRuby 9.2.9.0 where using the
11
+ # refinement causes calls to RubyCoreSupport.file_open to fail to pass the block
12
+ # parameter.
13
+ #
14
+ # https://travis-ci.org/tzinfo/tzinfo/jobs/628812051#L1931
15
+ # https://github.com/jruby/jruby/issues/6009
16
+ send(:using, TZInfo::RubyCoreSupport::UntaintExt) if TZInfo::RubyCoreSupport.const_defined?(:UntaintExt)
17
+ send(:using, TaintExt) if Module.const_defined?(:TaintExt)
18
+
10
19
  class TCZoneinfoDataSource < Minitest::Test
11
20
  ZONEINFO_DIR = File.join(File.expand_path(File.dirname(__FILE__)), 'zoneinfo').untaint
12
21
 
@@ -653,7 +662,7 @@ class TCZoneinfoDataSource < Minitest::Test
653
662
  end
654
663
 
655
664
  def test_load_timezone_info_tainted
656
- safe_test do
665
+ safe_test(:unavailable => :skip) do
657
666
  identifier = 'Europe/Amsterdam'.dup.taint
658
667
  assert(identifier.tainted?)
659
668
  info = @data_source.load_timezone_info(identifier)
@@ -840,7 +849,7 @@ class TCZoneinfoDataSource < Minitest::Test
840
849
  end
841
850
 
842
851
  def test_load_country_info_tainted
843
- safe_test do
852
+ safe_test(:unavailable => :skip) do
844
853
  code = 'NL'.dup.taint
845
854
  assert(code.tainted?)
846
855
  info = @data_source.load_country_info(code)
@@ -5,7 +5,20 @@ require 'tempfile'
5
5
 
6
6
  include TZInfo
7
7
 
8
+ # Use send as a workaround for erroneous 'wrong number of arguments' errors with
9
+ # JRuby 9.0.5.0 when calling methods with Java implementations. See #114.
10
+ send(:using, RubyCoreSupport::UntaintExt) if RubyCoreSupport.const_defined?(:UntaintExt)
11
+
8
12
  class TCZoneinfoTimezoneInfo < Minitest::Test
13
+ class FakePosixTimeZoneParser
14
+ def initialize(&block)
15
+ @on_parse = block
16
+ end
17
+
18
+ def parse(tz_string)
19
+ @on_parse.call(tz_string)
20
+ end
21
+ end
9
22
 
10
23
  begin
11
24
  Time.at(-2147483649)
@@ -23,6 +36,16 @@ class TCZoneinfoTimezoneInfo < Minitest::Test
23
36
  SUPPORTS_NEGATIVE = false
24
37
  end
25
38
 
39
+ def setup
40
+ @expect_tz_string = nil
41
+ @tz_parse_result = nil
42
+ @posix_tz_parser = FakePosixTimeZoneParser.new do |tz_string|
43
+ raise "Unexpected tz_string passed to PosixTimeZoneParser: #{tz_string}" unless tz_string == @expect_tz_string
44
+ raise InvalidPosixTimeZone, 'FakePosixTimeZoneParser Failure.' if @tz_parse_result == :fail
45
+ @tz_parse_result
46
+ end
47
+ end
48
+
26
49
  def assert_period(abbreviation, utc_offset, std_offset, dst, start_at, end_at, info)
27
50
  if start_at
28
51
  period = info.period_for_utc(start_at)
@@ -32,24 +55,24 @@ class TCZoneinfoTimezoneInfo < Minitest::Test
32
55
  # no transitions, pick the epoch
33
56
  period = info.period_for_utc(Time.utc(1970, 1, 1))
34
57
  end
35
-
36
- assert_equal(abbreviation, period.abbreviation)
37
- assert_equal(utc_offset, period.utc_offset)
38
- assert_equal(std_offset, period.std_offset)
39
- assert_equal(dst, period.dst?)
40
-
58
+
59
+ assert_equal(abbreviation, period.abbreviation, 'abbreviation')
60
+ assert_equal(utc_offset, period.utc_offset, 'utc_offset')
61
+ assert_equal(std_offset, period.std_offset, 'std_offset')
62
+ assert_equal(dst, period.dst?, 'dst')
63
+
41
64
  if start_at
42
- refute_nil(period.utc_start_time)
43
- assert_equal(start_at, period.utc_start_time)
65
+ refute_nil(period.utc_start_time, 'utc_start_time')
66
+ assert_equal(start_at, period.utc_start_time, 'utc_start_time')
44
67
  else
45
- assert_nil(period.utc_start_time)
68
+ assert_nil(period.utc_start_time, 'utc_start_time')
46
69
  end
47
70
 
48
71
  if end_at
49
- refute_nil(period.utc_end_time)
50
- assert_equal(end_at, period.utc_end_time)
72
+ refute_nil(period.utc_end_time, 'utc_end_time')
73
+ assert_equal(end_at, period.utc_end_time, 'utc_end_time')
51
74
  else
52
- assert_nil(period.utc_end_time)
75
+ assert_nil(period.utc_end_time, 'utc_end_time')
53
76
  end
54
77
  end
55
78
 
@@ -77,13 +100,15 @@ class TCZoneinfoTimezoneInfo < Minitest::Test
77
100
  pack_int64_network_order(values.collect {|value| value < 0 ? value + 0x10000000000000000 : value})
78
101
  end
79
102
 
80
- def write_tzif(format, offsets, transitions, leaps = [], options = {})
103
+ def write_tzif(format, offsets, transitions, tz_string, leaps, options = {})
81
104
 
82
105
  # Options for testing malformed zoneinfo files.
83
106
  magic = options[:magic]
84
107
  section2_magic = options[:section2_magic]
85
108
  abbrev_separator = options[:abbrev_separator] || "\0"
86
109
  abbrev_offset_base = options[:abbrev_offset_base] || 0
110
+ omit_tz_string_start_new_line = options[:omit_tz_string_start_new_line]
111
+ omit_tz_string_end_new_line = options[:omit_tz_string_end_new_line]
87
112
 
88
113
  unless magic
89
114
  if format == 1
@@ -187,20 +212,37 @@ class TCZoneinfoTimezoneInfo < Minitest::Test
187
212
  end
188
213
 
189
214
  # Empty POSIX timezone string
190
- file.write("\n\n")
215
+ file.write("\n") unless omit_tz_string_start_new_line
216
+ tz_string = tz_string.encode(Encoding::UTF_8) if tz_string.respond_to?(:encode)
217
+ file.write(tz_string)
218
+ file.write("\n") unless omit_tz_string_end_new_line
191
219
  end
192
220
 
193
221
  file.flush
194
222
 
195
- yield file.path, format
223
+ yield file.path
196
224
  end
197
225
  end
198
226
 
199
- def tzif_test(offsets, transitions, leaps = [], options = {}, &block)
200
- min_format = options[:min_format] || 1
227
+ def tzif_test(offsets, transitions, options = {}, &block)
228
+ rules = options[:rules]
229
+ tz_string = options[:tz_string] || (rules ? "TEST_TZ_STRING_#{rand(1000000)}" : '')
230
+ leaps = options[:leaps] || []
231
+ min_format = options[:min_format] || (tz_string.empty? ? 1 : 2)
201
232
 
202
233
  min_format.upto(3) do |format|
203
- write_tzif(format, offsets, transitions, leaps, options, &block)
234
+ write_tzif(format, offsets, transitions, tz_string, leaps, options) do |path|
235
+ if format >= 2
236
+ @tz_parse_result = rules
237
+ @expect_tz_string = tz_string
238
+ end
239
+ begin
240
+ yield path, format
241
+ ensure
242
+ @tz_parse_result = nil
243
+ @expect_tz_string = nil
244
+ end
245
+ end
204
246
  end
205
247
  end
206
248
 
@@ -218,7 +260,7 @@ class TCZoneinfoTimezoneInfo < Minitest::Test
218
260
  {:at => Time.utc(2000, 12, 31), :offset_index => 3}]
219
261
 
220
262
  tzif_test(offsets, transitions) do |path, format|
221
- info = ZoneinfoTimezoneInfo.new('Zone/One', path)
263
+ info = ZoneinfoTimezoneInfo.new('Zone/One', path, @posix_tz_parser)
222
264
  assert_equal('Zone/One', info.identifier)
223
265
 
224
266
  assert_period(:LMT, 3542, 0, false, nil, Time.utc(1971, 1, 2), info)
@@ -243,7 +285,7 @@ class TCZoneinfoTimezoneInfo < Minitest::Test
243
285
  {:at => Time.utc(1992, 4, 1, 4, 30, 0), :offset_index => 3}]
244
286
 
245
287
  tzif_test(offsets, transitions) do |path, format|
246
- info = ZoneinfoTimezoneInfo.new('Zone/One', path)
288
+ info = ZoneinfoTimezoneInfo.new('Zone/One', path, @posix_tz_parser)
247
289
  assert_equal('Zone/One', info.identifier)
248
290
 
249
291
  assert_period(:LMT, -12492, 0, false, nil, Time.utc(1971, 7, 9, 3, 0, 0), info)
@@ -268,7 +310,7 @@ class TCZoneinfoTimezoneInfo < Minitest::Test
268
310
  {:at => Time.utc(2000, 12, 31), :offset_index => 3}]
269
311
 
270
312
  tzif_test(offsets, transitions) do |path, format|
271
- info = ZoneinfoTimezoneInfo.new('Zone/Two', path)
313
+ info = ZoneinfoTimezoneInfo.new('Zone/Two', path, @posix_tz_parser)
272
314
  assert_equal('Zone/Two', info.identifier)
273
315
 
274
316
  assert_period(:LMT, 3542, 0, false, nil, Time.utc(1979, 1, 2), info)
@@ -279,7 +321,7 @@ class TCZoneinfoTimezoneInfo < Minitest::Test
279
321
  offsets = [{:gmtoff => -12094, :isdst => false, :abbrev => 'LT'}]
280
322
 
281
323
  tzif_test(offsets, []) do |path, format|
282
- info = ZoneinfoTimezoneInfo.new('Zone/three', path)
324
+ info = ZoneinfoTimezoneInfo.new('Zone/three', path, @posix_tz_parser)
283
325
  assert_equal('Zone/three', info.identifier)
284
326
 
285
327
  assert_period(:LT, -12094, 0, false, nil, nil, info)
@@ -292,7 +334,7 @@ class TCZoneinfoTimezoneInfo < Minitest::Test
292
334
 
293
335
  tzif_test(offsets, transitions) do |path, format|
294
336
  assert_raises(InvalidZoneinfoFile) do
295
- ZoneinfoTimezoneInfo.new('Zone', path)
337
+ ZoneinfoTimezoneInfo.new('Zone', path, @posix_tz_parser)
296
338
  end
297
339
  end
298
340
  end
@@ -303,7 +345,7 @@ class TCZoneinfoTimezoneInfo < Minitest::Test
303
345
 
304
346
  tzif_test(offsets, transitions) do |path, format|
305
347
  assert_raises(InvalidZoneinfoFile) do
306
- ZoneinfoTimezoneInfo.new('Zone', path)
348
+ ZoneinfoTimezoneInfo.new('Zone', path, @posix_tz_parser)
307
349
  end
308
350
  end
309
351
  end
@@ -312,9 +354,9 @@ class TCZoneinfoTimezoneInfo < Minitest::Test
312
354
  offsets = [{:gmtoff => -0, :isdst => false, :abbrev => 'LMT'}]
313
355
  leaps = [{:at => Time.utc(1972,6,30,23,59,60), :seconds => 1}]
314
356
 
315
- tzif_test(offsets, [], leaps) do |path, format|
357
+ tzif_test(offsets, [], :leaps => leaps) do |path, format|
316
358
  assert_raises(InvalidZoneinfoFile) do
317
- ZoneinfoTimezoneInfo.new('Zone', path)
359
+ ZoneinfoTimezoneInfo.new('Zone', path, @posix_tz_parser)
318
360
  end
319
361
  end
320
362
  end
@@ -323,41 +365,36 @@ class TCZoneinfoTimezoneInfo < Minitest::Test
323
365
  ['TZif4', 'tzif2', '12345'].each do |magic|
324
366
  offsets = [{:gmtoff => -12094, :isdst => false, :abbrev => 'LT'}]
325
367
 
326
- tzif_test(offsets, [], [], :magic => magic) do |path, format|
368
+ tzif_test(offsets, [], :magic => magic) do |path, format|
327
369
  assert_raises(InvalidZoneinfoFile) do
328
- ZoneinfoTimezoneInfo.new('Zone2', path)
370
+ ZoneinfoTimezoneInfo.new('Zone2', path, @posix_tz_parser)
329
371
  end
330
372
  end
331
373
  end
332
374
  end
333
375
 
334
- # These tests can only be run if the platform supports 64-bit Times. When
335
- # 64-bit support is unavailable, the second section will not be read, so no
336
- # error will be raised.
337
- if SUPPORTS_64BIT
338
- def test_load_invalid_section2_magic
339
- ['TZif4', 'tzif2', '12345'].each do |section2_magic|
340
- offsets = [{:gmtoff => -12094, :isdst => false, :abbrev => 'LT'}]
341
-
342
- tzif_test(offsets, [], [], :min_format => 2, :section2_magic => section2_magic) do |path, format|
343
- assert_raises(InvalidZoneinfoFile) do
344
- ZoneinfoTimezoneInfo.new('Zone4', path)
345
- end
376
+ def test_load_invalid_section2_magic
377
+ ['TZif4', 'tzif2', '12345'].each do |section2_magic|
378
+ offsets = [{:gmtoff => -12094, :isdst => false, :abbrev => 'LT'}]
379
+
380
+ tzif_test(offsets, [], :min_format => 2, :section2_magic => section2_magic) do |path, format|
381
+ assert_raises(InvalidZoneinfoFile) do
382
+ ZoneinfoTimezoneInfo.new('Zone4', path, @posix_tz_parser)
346
383
  end
347
384
  end
348
385
  end
386
+ end
387
+
388
+ def test_load_mismatched_section2_magic
389
+ minus_one = Proc.new {|f| f == 2 ? "TZif\0" : "TZif#{f - 1}" }
390
+ plus_one = Proc.new {|f| "TZif#{f + 1}" }
349
391
 
350
- def test_load_mismatched_section2_magic
351
- minus_one = Proc.new {|f| f == 2 ? "TZif\0" : "TZif#{f - 1}" }
352
- plus_one = Proc.new {|f| "TZif#{f + 1}" }
353
-
354
- [minus_one, plus_one].each do |section2_magic|
355
- offsets = [{:gmtoff => -12094, :isdst => false, :abbrev => 'LT'}]
356
-
357
- tzif_test(offsets, [], [], :min_format => 2, :section2_magic => section2_magic) do |path, format|
358
- assert_raises(InvalidZoneinfoFile) do
359
- ZoneinfoTimezoneInfo.new('Zone5', path)
360
- end
392
+ [minus_one, plus_one].each do |section2_magic|
393
+ offsets = [{:gmtoff => -12094, :isdst => false, :abbrev => 'LT'}]
394
+
395
+ tzif_test(offsets, [], :min_format => 2, :section2_magic => section2_magic) do |path, format|
396
+ assert_raises(InvalidZoneinfoFile) do
397
+ ZoneinfoTimezoneInfo.new('Zone5', path, @posix_tz_parser)
361
398
  end
362
399
  end
363
400
  end
@@ -369,7 +406,7 @@ class TCZoneinfoTimezoneInfo < Minitest::Test
369
406
  file.flush
370
407
 
371
408
  assert_raises(InvalidZoneinfoFile) do
372
- ZoneinfoTimezoneInfo.new('Zone3', file.path)
409
+ ZoneinfoTimezoneInfo.new('Zone3', file.path, @posix_tz_parser)
373
410
  end
374
411
  end
375
412
  end
@@ -382,9 +419,9 @@ class TCZoneinfoTimezoneInfo < Minitest::Test
382
419
  transitions = [
383
420
  {:at => Time.utc(2000, 1, 1), :offset_index => 1}]
384
421
 
385
- tzif_test(offsets, transitions, [], :abbrev_separator => '^') do |path, format|
422
+ tzif_test(offsets, transitions, :abbrev_separator => '^') do |path, format|
386
423
  assert_raises(InvalidZoneinfoFile) do
387
- ZoneinfoTimezoneInfo.new('Zone', path)
424
+ ZoneinfoTimezoneInfo.new('Zone', path, @posix_tz_parser)
388
425
  end
389
426
  end
390
427
  end
@@ -397,9 +434,9 @@ class TCZoneinfoTimezoneInfo < Minitest::Test
397
434
  transitions = [
398
435
  {:at => Time.utc(2000, 1, 1), :offset_index => 1}]
399
436
 
400
- tzif_test(offsets, transitions, [], :abbrev_offset_base => 8) do |path, format|
437
+ tzif_test(offsets, transitions, :abbrev_offset_base => 8) do |path, format|
401
438
  assert_raises(InvalidZoneinfoFile) do
402
- ZoneinfoTimezoneInfo.new('Zone', path)
439
+ ZoneinfoTimezoneInfo.new('Zone', path, @posix_tz_parser)
403
440
  end
404
441
  end
405
442
  end
@@ -424,7 +461,7 @@ class TCZoneinfoTimezoneInfo < Minitest::Test
424
461
  {:at => Time.utc(2000, 12, 31), :offset_index => 3}]
425
462
 
426
463
  tzif_test(offsets, transitions) do |path, format|
427
- info = ZoneinfoTimezoneInfo.new('Zone/Negative', path)
464
+ info = ZoneinfoTimezoneInfo.new('Zone/Negative', path, @posix_tz_parser)
428
465
  assert_equal('Zone/Negative', info.identifier)
429
466
 
430
467
  if SUPPORTS_NEGATIVE
@@ -455,7 +492,7 @@ class TCZoneinfoTimezoneInfo < Minitest::Test
455
492
  {:at => Time.utc(2000, 12, 31), :offset_index => 3}]
456
493
 
457
494
  tzif_test(offsets, transitions) do |path, format|
458
- info = ZoneinfoTimezoneInfo.new('Zone/Negative', path)
495
+ info = ZoneinfoTimezoneInfo.new('Zone/Negative', path, @posix_tz_parser)
459
496
  assert_equal('Zone/Negative', info.identifier)
460
497
 
461
498
  if SUPPORTS_NEGATIVE
@@ -472,13 +509,15 @@ class TCZoneinfoTimezoneInfo < Minitest::Test
472
509
  end
473
510
 
474
511
  def test_load_64bit
475
- # Some platforms support 64-bit Times, others only 32-bit. The TZif version
476
- # 2 and later format contains both 32-bit and 64-bit times.
512
+ # The TZif version2 and later format contains both 32-bit and 64-bit
513
+ # sections. The 64-bit section is always used.
514
+ #
515
+ # Negative transitions before the supported range are moved to the start of
516
+ # the supported range.
517
+ #
518
+ # Transitions after 2**31 - 1 are discarded if 64-bit times aren't
519
+ # supported.
477
520
 
478
- # Where 64-bit is supported and a TZif 2 or later file is provided, the
479
- # 64-bit times should be used, otherwise the 32-bit information should be
480
- # used.
481
-
482
521
  offsets = [
483
522
  {:gmtoff => 3542, :isdst => false, :abbrev => 'LMT'},
484
523
  {:gmtoff => 3600, :isdst => false, :abbrev => 'XST'},
@@ -492,27 +531,35 @@ class TCZoneinfoTimezoneInfo < Minitest::Test
492
531
  {:at => 2240524800, :offset_index => 3}] # Time.utc(2040, 12, 31)
493
532
 
494
533
  tzif_test(offsets, transitions) do |path, format|
495
- info = ZoneinfoTimezoneInfo.new('Zone/SixtyFour', path)
534
+ info = ZoneinfoTimezoneInfo.new('Zone/SixtyFour', path, @posix_tz_parser)
496
535
  assert_equal('Zone/SixtyFour', info.identifier)
497
536
 
498
- if SUPPORTS_64BIT && format >= 2
499
- assert_period(:LMT, 3542, 0, false, nil, Time.utc(1850, 1, 2), info)
537
+ if SUPPORTS_64BIT && SUPPORTS_NEGATIVE && format >= 2
538
+ assert_period(:LMT, 3542, 0, false, nil, Time.utc(1850, 1, 2), info)
500
539
  assert_period(:XST, 3600, 0, false, Time.utc(1850, 1, 2), Time.utc(2003, 4, 22), info)
501
540
  assert_period(:XDT, 3600, 3600, true, Time.utc(2003, 4, 22), Time.utc(2003, 10, 21), info)
502
541
  assert_period(:XST, 3600, 0, false, Time.utc(2003, 10, 21), Time.utc(2040, 12, 31), info)
503
- assert_period(:XNST, 0, 0, false, Time.utc(2040, 12, 31), nil, info)
504
- else
505
- assert_period(:LMT, 3542, 0, false, nil, Time.utc(2003, 4, 22), info)
542
+ assert_period(:XNST, 0, 0, false, Time.utc(2040, 12, 31), nil, info)
543
+ elsif format < 2
544
+ assert_period(:LMT, 3542, 0, false, nil, Time.utc(2003, 4, 22), info)
506
545
  assert_period(:XDT, 3600, 3600, true, Time.utc(2003, 4, 22), Time.utc(2003, 10, 21), info)
507
- assert_period(:XST, 3600, 0, false, Time.utc(2003, 10, 21), nil, info)
546
+ assert_period(:XST, 3600, 0, false, Time.utc(2003, 10, 21), nil, info)
547
+ else
548
+ min_supported = SUPPORTS_NEGATIVE ? -2**31 : 0
549
+ assert_period(:LMT, 3542, 0, false, nil, Time.at(min_supported).utc, info)
550
+ assert_period(:XST, 3600, 0, false, Time.at(min_supported).utc, Time.utc(2003, 4, 22), info)
551
+ assert_period(:XDT, 3600, 3600, true, Time.utc(2003, 4, 22), Time.utc(2003, 10, 21), info)
552
+ assert_period(:XST, 3600, 0, false, Time.utc(2003, 10, 21), nil, info)
508
553
  end
509
554
  end
510
555
  end
511
556
 
512
557
  def test_load_64bit_range
513
558
  # The full range of 64 bit timestamps is not currently supported because of
514
- # the way transitions are indexed. Transitions outside the supported range
515
- # will be ignored.
559
+ # the way transitions are indexed. The last transition before the earliest
560
+ # supported time will be moved to that time if there isn't already a
561
+ # transition at that time. Transitions after the latest supported time are
562
+ # ignored.
516
563
 
517
564
  offsets = [
518
565
  {:gmtoff => 3542, :isdst => false, :abbrev => 'LMT'},
@@ -525,7 +572,7 @@ class TCZoneinfoTimezoneInfo < Minitest::Test
525
572
  {:at => 2**63 - 1, :offset_index => 0}]
526
573
 
527
574
  tzif_test(offsets, transitions) do |path, format|
528
- info = ZoneinfoTimezoneInfo.new('Zone/SixtyFourRange', path)
575
+ info = ZoneinfoTimezoneInfo.new('Zone/SixtyFourRange', path, @posix_tz_parser)
529
576
  assert_equal('Zone/SixtyFourRange', info.identifier)
530
577
 
531
578
  if SUPPORTS_64BIT && format >= 2
@@ -536,19 +583,73 @@ class TCZoneinfoTimezoneInfo < Minitest::Test
536
583
  #assert_period(:LMT, 3542, 0, false, Time.at(2**63 - 1).utc, nil, info)
537
584
 
538
585
  # Without full range support, the following periods will be defined:
586
+ assert_period(:LMT, 3542, 0, false, nil, Time.utc(1700, 1, 1), info)
587
+ assert_period(:XST, 3600, 0, false, Time.utc(1700, 1, 1), Time.utc(2014, 5, 27), info)
588
+ assert_period(:XNST, 7200, 0, false, Time.utc(2014, 5, 27), nil, info)
589
+ elsif format < 2
539
590
  assert_period(:LMT, 3542, 0, false, nil, Time.utc(2014, 5, 27), info)
540
591
  assert_period(:XNST, 7200, 0, false, Time.utc(2014, 5, 27), nil, info)
541
592
  else
593
+ min_supported = SUPPORTS_NEGATIVE ? -2**31 : 0
594
+ assert_period(:LMT, 3542, 0, false, nil, Time.at(min_supported).utc, info)
595
+ assert_period(:XST, 3600, 0, false, Time.at(min_supported).utc, Time.utc(2014, 5, 27), info)
596
+ assert_period(:XNST, 7200, 0, false, Time.utc(2014, 5, 27), nil, info)
597
+ end
598
+ end
599
+ end
600
+
601
+ def test_load_64bit_range_transition_at_earliest_supported
602
+ # The full range of 64 bit timestamps is not currently supported because of
603
+ # the way transitions are indexed. The last transition before the earliest
604
+ # supported time will be moved to that time if there isn't already a
605
+ # transition at that time. Transitions after the latest supported time are
606
+ # ignored.
607
+
608
+ offsets = [
609
+ {:gmtoff => 3542, :isdst => false, :abbrev => 'LMT'},
610
+ {:gmtoff => 3600, :isdst => false, :abbrev => 'XST'},
611
+ {:gmtoff => 3600, :isdst => false, :abbrev => 'XST2'},
612
+ {:gmtoff => 7200, :isdst => false, :abbrev => 'XNST'}]
613
+
614
+ transitions = [
615
+ {:at => -2**63, :offset_index => 1},
616
+ {:at => -8520336000, :offset_index => 2}, # Time.utc(1700, 1, 1).to_i
617
+ {:at => Time.utc(2014, 5, 27), :offset_index => 3},
618
+ {:at => 2**63 - 1, :offset_index => 0}]
619
+
620
+ tzif_test(offsets, transitions) do |path, format|
621
+ info = ZoneinfoTimezoneInfo.new('Zone/SixtyFourRange', path, @posix_tz_parser)
622
+ assert_equal('Zone/SixtyFourRange', info.identifier)
623
+
624
+ if SUPPORTS_64BIT && format >= 2
625
+ # When the full range is supported, the following periods will be defined:
626
+ #assert_period(:LMT, 3542, 0, false, nil, Time.at(-2**63).utc, info)
627
+ #assert_period(:XST, 3600, 0, false, Time.at(-2**63).utc, Time.utc(2014, 5, 27), info)
628
+ #assert_period(:XNST, 7200, 0, false, Time.utc(2014, 5, 27), Time.at(2**63 - 1).utc, info)
629
+ #assert_period(:LMT, 3542, 0, false, Time.at(2**63 - 1).utc, nil, info)
630
+
631
+ # Without full range support, the following periods will be defined:
632
+ assert_period(:LMT, 3542, 0, false, nil, Time.utc(1700, 1, 1), info)
633
+ assert_period(:XST2, 3600, 0, false, Time.utc(1700, 1, 1), Time.utc(2014, 5, 27), info)
634
+ assert_period(:XNST, 7200, 0, false, Time.utc(2014, 5, 27), nil, info)
635
+ elsif format < 2
542
636
  assert_period(:LMT, 3542, 0, false, nil, Time.utc(2014, 5, 27), info)
543
637
  assert_period(:XNST, 7200, 0, false, Time.utc(2014, 5, 27), nil, info)
638
+ elsif SUPPORTS_NEGATIVE
639
+ assert_period(:LMT, 3542, 0, false, nil, Time.at(-2**31).utc, info)
640
+ assert_period(:XST, 3600, 0, false, Time.at(-2**31).utc, Time.utc(2014, 5, 27), info)
641
+ assert_period(:XNST, 7200, 0, false, Time.utc(2014, 5, 27), nil, info)
642
+ else
643
+ assert_period(:LMT, 3542, 0, false, nil, Time.utc(1970, 1, 1), info)
644
+ assert_period(:XST2, 3600, 0, false, Time.utc(1970, 1, 1), Time.utc(2014, 5, 27), info)
645
+ assert_period(:XNST, 7200, 0, false, Time.utc(2014, 5, 27), nil, info)
544
646
  end
545
647
  end
546
648
  end
547
649
 
548
650
  def test_load_supported_64bit_range
549
651
  # The full range of 64 bit timestamps is not currently supported because of
550
- # the way transitions are indexed. Transitions outside the supported range
551
- # will be ignored.
652
+ # the way transitions are indexed.
552
653
 
553
654
  min_timestamp = -8520336000 # Time.utc(1700, 1, 1).to_i
554
655
  max_timestamp = 16725225600 # Time.utc(2500, 1, 1).to_i
@@ -564,7 +665,7 @@ class TCZoneinfoTimezoneInfo < Minitest::Test
564
665
  {:at => max_timestamp - 1, :offset_index => 0}]
565
666
 
566
667
  tzif_test(offsets, transitions) do |path, format|
567
- info = ZoneinfoTimezoneInfo.new('Zone/SupportedSixtyFourRange', path)
668
+ info = ZoneinfoTimezoneInfo.new('Zone/SupportedSixtyFourRange', path, @posix_tz_parser)
568
669
  assert_equal('Zone/SupportedSixtyFourRange', info.identifier)
569
670
 
570
671
  if SUPPORTS_64BIT && format >= 2
@@ -572,9 +673,14 @@ class TCZoneinfoTimezoneInfo < Minitest::Test
572
673
  assert_period(:XST, 3600, 0, false, Time.at(min_timestamp).utc, Time.utc(2014, 5, 27), info)
573
674
  assert_period(:XNST, 7200, 0, false, Time.utc(2014, 5, 27), Time.at(max_timestamp - 1).utc, info)
574
675
  assert_period(:LMT, 3542, 0, false, Time.at(max_timestamp - 1).utc, nil, info)
575
- else
676
+ elsif format < 2
576
677
  assert_period(:LMT, 3542, 0, false, nil, Time.utc(2014, 5, 27), info)
577
678
  assert_period(:XNST, 7200, 0, false, Time.utc(2014, 5, 27), nil, info)
679
+ else
680
+ min_supported = SUPPORTS_NEGATIVE ? -2**31 : 0
681
+ assert_period(:LMT, 3542, 0, false, nil, Time.at(min_supported).utc, info)
682
+ assert_period(:XST, 3600, 0, false, Time.at(min_supported).utc, Time.utc(2014, 5, 27), info)
683
+ assert_period(:XNST, 7200, 0, false, Time.utc(2014, 5, 27), nil, info)
578
684
  end
579
685
  end
580
686
  end
@@ -591,19 +697,14 @@ class TCZoneinfoTimezoneInfo < Minitest::Test
591
697
  {:at => 2**31 - 1, :offset_index => 0}]
592
698
 
593
699
  tzif_test(offsets, transitions) do |path, format|
594
- info = ZoneinfoTimezoneInfo.new('Zone/ThirtyTwoRange', path)
700
+ info = ZoneinfoTimezoneInfo.new('Zone/ThirtyTwoRange', path, @posix_tz_parser)
595
701
  assert_equal('Zone/ThirtyTwoRange', info.identifier)
596
702
 
597
- if SUPPORTS_NEGATIVE
598
- assert_period(:LMT, 3542, 0, false, nil, Time.at(-2**31).utc, info)
599
- assert_period(:XST, 3600, 0, false, Time.at(-2**31).utc, Time.utc(2014, 5, 27), info)
600
- assert_period(:XNST, 7200, 0, false, Time.utc(2014, 5, 27), Time.at(2**31 - 1).utc, info)
601
- assert_period(:LMT, 3542, 0, false, Time.at(2**31 - 1).utc, nil, info)
602
- else
603
- assert_period(:XST, 3600, 0, false, Time.utc(1970, 1, 1), Time.utc(2014, 5, 27), info)
604
- assert_period(:XNST, 7200, 0, false, Time.utc(2014, 5, 27), Time.at(2**31 - 1).utc, info)
605
- assert_period(:LMT, 3542, 0, false, Time.at(2**31 - 1).utc, nil, info)
606
- end
703
+ min_supported = SUPPORTS_NEGATIVE ? -2**31 : 0
704
+ assert_period(:LMT, 3542, 0, false, nil, Time.at(min_supported).utc, info)
705
+ assert_period(:XST, 3600, 0, false, Time.at(min_supported).utc, Time.utc(2014, 5, 27), info)
706
+ assert_period(:XNST, 7200, 0, false, Time.utc(2014, 5, 27), Time.at(2**31 - 1), info)
707
+ assert_period(:LMT, 3542, 0, false, Time.at(2**31 - 1).utc, nil, info)
607
708
  end
608
709
  end
609
710
 
@@ -624,7 +725,7 @@ class TCZoneinfoTimezoneInfo < Minitest::Test
624
725
  {:at => Time.utc(2000, 4, 1), :offset_index => 1}]
625
726
 
626
727
  tzif_test(offsets, transitions) do |path, format|
627
- info = ZoneinfoTimezoneInfo.new('Zone/DoubleDaylight', path)
728
+ info = ZoneinfoTimezoneInfo.new('Zone/DoubleDaylight', path, @posix_tz_parser)
628
729
  assert_equal('Zone/DoubleDaylight', info.identifier)
629
730
 
630
731
  assert_period(:LMT, 3542, 0, false, nil, Time.utc(2000, 1, 1), info)
@@ -641,7 +742,7 @@ class TCZoneinfoTimezoneInfo < Minitest::Test
641
742
 
642
743
  offsets = [
643
744
  {:gmtoff => 3542, :isdst => false, :abbrev => 'LMT'},
644
- {:gmtoff => 3600, :isdst => false, :abbrev => 'XST'},
745
+ {:gmtoff => 3600, :isdst => false, :abbrev => 'XST'},
645
746
  {:gmtoff => 10800, :isdst => true, :abbrev => 'XDDT'}]
646
747
 
647
748
  transitions = [
@@ -650,7 +751,7 @@ class TCZoneinfoTimezoneInfo < Minitest::Test
650
751
  {:at => Time.utc(2000, 6, 1), :offset_index => 1}]
651
752
 
652
753
  tzif_test(offsets, transitions) do |path, format|
653
- info = ZoneinfoTimezoneInfo.new('Zone/DoubleDaylight', path)
754
+ info = ZoneinfoTimezoneInfo.new('Zone/DoubleDaylight', path, @posix_tz_parser)
654
755
  assert_equal('Zone/DoubleDaylight', info.identifier)
655
756
 
656
757
  assert_period(:LMT, 3542, 0, false, nil, Time.utc(2000, 4, 1), info)
@@ -679,7 +780,7 @@ class TCZoneinfoTimezoneInfo < Minitest::Test
679
780
  {:at => Time.utc(2000, 6, 1), :offset_index => 1}]
680
781
 
681
782
  tzif_test(offsets, transitions) do |path, format|
682
- info = ZoneinfoTimezoneInfo.new('Zone/DoubleDaylight', path)
783
+ info = ZoneinfoTimezoneInfo.new('Zone/DoubleDaylight', path, @posix_tz_parser)
683
784
  assert_equal('Zone/DoubleDaylight', info.identifier)
684
785
 
685
786
  assert_period(:LMT, -10821, 0, false, nil, Time.utc(2000, 1, 1), info)
@@ -708,7 +809,7 @@ class TCZoneinfoTimezoneInfo < Minitest::Test
708
809
  {:at => Time.utc(2000, 3, 1), :offset_index => 1}]
709
810
 
710
811
  tzif_test(offsets, transitions) do |path, format|
711
- info = ZoneinfoTimezoneInfo.new('Zone/DoubleDaylight', path)
812
+ info = ZoneinfoTimezoneInfo.new('Zone/DoubleDaylight', path, @posix_tz_parser)
712
813
  assert_equal('Zone/DoubleDaylight', info.identifier)
713
814
 
714
815
  assert_period(:LMT, 3542, 0, false, nil, Time.utc(2000, 1, 1), info)
@@ -729,7 +830,7 @@ class TCZoneinfoTimezoneInfo < Minitest::Test
729
830
  transitions = [{:at => Time.utc(2000, 1, 1), :offset_index => 1}]
730
831
 
731
832
  tzif_test(offsets, transitions) do |path, format|
732
- info = ZoneinfoTimezoneInfo.new('Zone/OnlyDST', path)
833
+ info = ZoneinfoTimezoneInfo.new('Zone/OnlyDST', path, @posix_tz_parser)
733
834
  assert_equal('Zone/OnlyDST', info.identifier)
734
835
 
735
836
  assert_period(:LMT, 3542, 0, false, nil, Time.utc(2000, 1, 1), info)
@@ -746,7 +847,7 @@ class TCZoneinfoTimezoneInfo < Minitest::Test
746
847
  transitions = [{:at => Time.utc(2000, 1, 1), :offset_index => 0}]
747
848
 
748
849
  tzif_test(offsets, transitions) do |path, format|
749
- info = ZoneinfoTimezoneInfo.new('Zone/OnlyDST', path)
850
+ info = ZoneinfoTimezoneInfo.new('Zone/OnlyDST', path, @posix_tz_parser)
750
851
  assert_equal('Zone/OnlyDST', info.identifier)
751
852
 
752
853
  assert_period(:XDT, 3600, 3600, true, nil, Time.utc(2000, 1, 1), info)
@@ -771,7 +872,7 @@ class TCZoneinfoTimezoneInfo < Minitest::Test
771
872
  {:at => Time.utc(2000, 2, 1), :offset_index => 2}]
772
873
 
773
874
  tzif_test(offsets, transitions) do |path, format|
774
- info = ZoneinfoTimezoneInfo.new('Zone/DoubleDaylight', path)
875
+ info = ZoneinfoTimezoneInfo.new('Zone/DoubleDaylight', path, @posix_tz_parser)
775
876
  assert_equal('Zone/DoubleDaylight', info.identifier)
776
877
 
777
878
  assert_period(:LMT, 3542, 0, false, nil, Time.utc(2000, 1, 1), info)
@@ -801,7 +902,7 @@ class TCZoneinfoTimezoneInfo < Minitest::Test
801
902
  {:at => Time.utc(2012, 3, 31, 14, 0, 0), :offset_index => 4}]
802
903
 
803
904
  tzif_test(offsets, transitions) do |path, format|
804
- info = ZoneinfoTimezoneInfo.new('Test/Pacific/Apia', path)
905
+ info = ZoneinfoTimezoneInfo.new('Test/Pacific/Apia', path, @posix_tz_parser)
805
906
  assert_equal('Test/Pacific/Apia', info.identifier)
806
907
 
807
908
  assert_period( :LMT, 45184, 0, false, nil, Time.utc(2011, 4, 2, 14, 0, 0), info)
@@ -840,7 +941,7 @@ class TCZoneinfoTimezoneInfo < Minitest::Test
840
941
  # offsets.
841
942
 
842
943
  tzif_test(offsets, transitions) do |path, format|
843
- info = ZoneinfoTimezoneInfo.new('Zone/SplitUtcOffset', path)
944
+ info = ZoneinfoTimezoneInfo.new('Zone/SplitUtcOffset', path, @posix_tz_parser)
844
945
  assert_equal('Zone/SplitUtcOffset', info.identifier)
845
946
 
846
947
  assert_period( :LMT, 3542, 0, false, nil, Time.utc(2000, 1, 1), info)
@@ -882,7 +983,7 @@ class TCZoneinfoTimezoneInfo < Minitest::Test
882
983
  # utc_offset of 3600 and std_offset of 7200).
883
984
 
884
985
  tzif_test(offsets, transitions) do |path, format|
885
- info = ZoneinfoTimezoneInfo.new('Zone/MinimumUtcOffset', path)
986
+ info = ZoneinfoTimezoneInfo.new('Zone/MinimumUtcOffset', path, @posix_tz_parser)
886
987
  assert_equal('Zone/MinimumUtcOffset', info.identifier)
887
988
 
888
989
  assert_period( :LMT, 3542, 0, false, nil, Time.utc(2000, 1, 1), info)
@@ -911,7 +1012,7 @@ class TCZoneinfoTimezoneInfo < Minitest::Test
911
1012
  # utc_offset of 3600 and std_offset of 7200).
912
1013
 
913
1014
  tzif_test(offsets, transitions) do |path, format|
914
- info = ZoneinfoTimezoneInfo.new('Zone/MinimumUtcOffset', path)
1015
+ info = ZoneinfoTimezoneInfo.new('Zone/MinimumUtcOffset', path, @posix_tz_parser)
915
1016
  assert_equal('Zone/MinimumUtcOffset', info.identifier)
916
1017
 
917
1018
  assert_period( :LMT, 3542, 0, false, nil, Time.utc(2000, 1, 1), info)
@@ -940,7 +1041,7 @@ class TCZoneinfoTimezoneInfo < Minitest::Test
940
1041
  # equivalent (or greater) utc_total_offset.
941
1042
 
942
1043
  tzif_test(offsets, transitions) do |path, format|
943
- info = ZoneinfoTimezoneInfo.new('Zone/UtcOffsetEqual', path)
1044
+ info = ZoneinfoTimezoneInfo.new('Zone/UtcOffsetEqual', path, @posix_tz_parser)
944
1045
  assert_equal('Zone/UtcOffsetEqual', info.identifier)
945
1046
 
946
1047
  assert_period( :LMT, 3542, 0, false, nil, Time.utc(2000, 1, 1), info)
@@ -969,7 +1070,7 @@ class TCZoneinfoTimezoneInfo < Minitest::Test
969
1070
  # equivalent (or greater) utc_total_offset.
970
1071
 
971
1072
  tzif_test(offsets, transitions) do |path, format|
972
- info = ZoneinfoTimezoneInfo.new('Zone/UtcOffsetEqual', path)
1073
+ info = ZoneinfoTimezoneInfo.new('Zone/UtcOffsetEqual', path, @posix_tz_parser)
973
1074
  assert_equal('Zone/UtcOffsetEqual', info.identifier)
974
1075
 
975
1076
  assert_period( :LMT, 3542, 0, false, nil, Time.utc(2000, 1, 1), info)
@@ -997,7 +1098,7 @@ class TCZoneinfoTimezoneInfo < Minitest::Test
997
1098
  # from utc_total_offset - std_offset.
998
1099
 
999
1100
  tzif_test(offsets, transitions) do |path, format|
1000
- info = ZoneinfoTimezoneInfo.new('Zone/AdjacentEqual', path)
1101
+ info = ZoneinfoTimezoneInfo.new('Zone/AdjacentEqual', path, @posix_tz_parser)
1001
1102
  assert_equal('Zone/AdjacentEqual', info.identifier)
1002
1103
 
1003
1104
  assert_period(:LMT, 7142, 0, false, nil, Time.utc(2000, 1, 1), info)
@@ -1028,7 +1129,7 @@ class TCZoneinfoTimezoneInfo < Minitest::Test
1028
1129
  # an equivalent utc_offset of 3600 and std_offset of 7200).
1029
1130
 
1030
1131
  tzif_test(offsets, transitions) do |path, format|
1031
- info = ZoneinfoTimezoneInfo.new('Zone/UtcOffsetPreserved', path)
1132
+ info = ZoneinfoTimezoneInfo.new('Zone/UtcOffsetPreserved', path, @posix_tz_parser)
1032
1133
  assert_equal('Zone/UtcOffsetPreserved', info.identifier)
1033
1134
 
1034
1135
  assert_period( :LMT, 3542, 0, false, nil, Time.utc(2000, 1, 1), info)
@@ -1060,7 +1161,7 @@ class TCZoneinfoTimezoneInfo < Minitest::Test
1060
1161
  # an equivalent utc_offset of 3600 and std_offset of 7200).
1061
1162
 
1062
1163
  tzif_test(offsets, transitions) do |path, format|
1063
- info = ZoneinfoTimezoneInfo.new('Zone/UtcOffsetPreserved', path)
1164
+ info = ZoneinfoTimezoneInfo.new('Zone/UtcOffsetPreserved', path, @posix_tz_parser)
1064
1165
  assert_equal('Zone/UtcOffsetPreserved', info.identifier)
1065
1166
 
1066
1167
  assert_period( :LMT, 3542, 0, false, nil, Time.utc(2000, 1, 1), info)
@@ -1088,7 +1189,7 @@ class TCZoneinfoTimezoneInfo < Minitest::Test
1088
1189
  {:at => Time.utc(2000, 5, 1), :offset_index => 1}]
1089
1190
 
1090
1191
  tzif_test(offsets, transitions) do |path, format|
1091
- info = ZoneinfoTimezoneInfo.new('Zone/NegativeStdOffsetDst', path)
1192
+ info = ZoneinfoTimezoneInfo.new('Zone/NegativeStdOffsetDst', path, @posix_tz_parser)
1092
1193
  assert_equal('Zone/NegativeStdOffsetDst', info.identifier)
1093
1194
 
1094
1195
  assert_period(:LMT, -100, 0, false, nil, Time.utc(2000, 1, 1), info)
@@ -1117,7 +1218,7 @@ class TCZoneinfoTimezoneInfo < Minitest::Test
1117
1218
  {:at => Time.utc(2000, 5, 1), :offset_index => 1}]
1118
1219
 
1119
1220
  tzif_test(offsets, transitions) do |path, format|
1120
- info = ZoneinfoTimezoneInfo.new('Zone/NegativeStdOffsetDstInitialDst', path)
1221
+ info = ZoneinfoTimezoneInfo.new('Zone/NegativeStdOffsetDstInitialDst', path, @posix_tz_parser)
1121
1222
  assert_equal('Zone/NegativeStdOffsetDstInitialDst', info.identifier)
1122
1223
 
1123
1224
  assert_period(:LMT, -100, 0, false, nil, Time.utc(2000, 1, 1), info)
@@ -1142,7 +1243,7 @@ class TCZoneinfoTimezoneInfo < Minitest::Test
1142
1243
  {:at => Time.utc(2000, 3, 1), :offset_index => 3}]
1143
1244
 
1144
1245
  tzif_test(offsets, transitions) do |path, format|
1145
- info = ZoneinfoTimezoneInfo.new('Zone/BaseOffsetMovesToDstNotHour', path)
1246
+ info = ZoneinfoTimezoneInfo.new('Zone/BaseOffsetMovesToDstNotHour', path, @posix_tz_parser)
1146
1247
  assert_equal('Zone/BaseOffsetMovesToDstNotHour', info.identifier)
1147
1248
 
1148
1249
  assert_period(:LMT, -100, 0, false, nil, Time.utc(2000, 1, 1), info)
@@ -1165,7 +1266,7 @@ class TCZoneinfoTimezoneInfo < Minitest::Test
1165
1266
  {:at => Time.utc(2000, 3, 1), :offset_index => 3}]
1166
1267
 
1167
1268
  tzif_test(offsets, transitions) do |path, format|
1168
- info = ZoneinfoTimezoneInfo.new('Zone/BaseOffsetMovesFromDstNotHour', path)
1269
+ info = ZoneinfoTimezoneInfo.new('Zone/BaseOffsetMovesFromDstNotHour', path, @posix_tz_parser)
1169
1270
  assert_equal('Zone/BaseOffsetMovesFromDstNotHour', info.identifier)
1170
1271
 
1171
1272
  assert_period(:LMT, -100, 0, false, nil, Time.utc(2000, 1, 1), info)
@@ -1183,7 +1284,7 @@ class TCZoneinfoTimezoneInfo < Minitest::Test
1183
1284
  path.untaint
1184
1285
 
1185
1286
  safe_test do
1186
- info = ZoneinfoTimezoneInfo.new('Zone/three', path)
1287
+ info = ZoneinfoTimezoneInfo.new('Zone/three', path, @posix_tz_parser)
1187
1288
  assert_equal('Zone/three', info.identifier)
1188
1289
 
1189
1290
  assert_period(:LT, -12094, 0, false, nil, nil, info)
@@ -1203,7 +1304,7 @@ class TCZoneinfoTimezoneInfo < Minitest::Test
1203
1304
  {:at => Time.utc(1971, 1, 2), :offset_index => 1}]
1204
1305
 
1205
1306
  tzif_test(offsets, transitions) do |path, format|
1206
- info = ZoneinfoTimezoneInfo.new('Zone/One', path)
1307
+ info = ZoneinfoTimezoneInfo.new('Zone/One', path, @posix_tz_parser)
1207
1308
  assert_equal('Zone/One', info.identifier)
1208
1309
 
1209
1310
  assert_period(:LMT, 3542, 0, false, nil, Time.utc(1971, 1, 2), info)
@@ -1222,11 +1323,831 @@ class TCZoneinfoTimezoneInfo < Minitest::Test
1222
1323
  {:at => Time.utc(2011, 12, 31, 13, 24, 26), :offset_index => 1}]
1223
1324
 
1224
1325
  tzif_test(offsets, transitions) do |path, format|
1225
- info = ZoneinfoTimezoneInfo.new('Zone/One', path)
1326
+ info = ZoneinfoTimezoneInfo.new('Zone/One', path, @posix_tz_parser)
1226
1327
  assert_equal('Zone/One', info.identifier)
1227
1328
 
1228
1329
  assert_period(:LMT, 3542, 0, false, nil, Time.utc(2011, 12, 31, 13, 24, 26), info)
1229
1330
  assert_period(:XST, 3600, 0, false, Time.utc(2011, 12, 31, 13, 24, 26), nil, info)
1230
1331
  end
1231
1332
  end
1333
+
1334
+ def test_load_invalid_tz_string
1335
+ offsets = [{:gmtoff => 0, :isdst => false, :abbrev => 'UTC'}]
1336
+
1337
+ tzif_test(offsets, [], :rules => :fail) do |path, format|
1338
+ error = assert_raises(InvalidZoneinfoFile) { ZoneinfoTimezoneInfo.new('Invalid/String', path, @posix_tz_parser) }
1339
+ assert_equal("Failed to parse POSIX-style TZ string in file '#{path}': FakePosixTimeZoneParser Failure.", error.message)
1340
+ end
1341
+ end
1342
+
1343
+ def test_load_tz_string_missing_start_newline
1344
+ offsets = [{:gmtoff => 0, :isdst => false, :abbrev => 'UTC'}]
1345
+ rules = TimezoneOffset.new(0, 0, 'UTC')
1346
+
1347
+ tzif_test(offsets, [], :rules => rules, :omit_tz_string_start_new_line => true) do |path, format|
1348
+ error = assert_raises(InvalidZoneinfoFile) { ZoneinfoTimezoneInfo.new('Missing/Start', path, @posix_tz_parser) }
1349
+ assert_equal("Expected newline starting POSIX-style TZ string in file '#{path}'.", error.message)
1350
+ end
1351
+ end
1352
+
1353
+ def test_load_tz_string_missing_end_newline
1354
+ offsets = [{:gmtoff => 0, :isdst => false, :abbrev => 'UTC'}]
1355
+ rules = TimezoneOffset.new(0, 0, 'UTC')
1356
+
1357
+ tzif_test(offsets, [], :rules => rules, :omit_tz_string_end_new_line => true) do |path, format|
1358
+ error = assert_raises(InvalidZoneinfoFile) { ZoneinfoTimezoneInfo.new('Missing/End', path, @posix_tz_parser) }
1359
+ assert_equal("Expected newline ending POSIX-style TZ string in file '#{path}'.", error.message)
1360
+ end
1361
+ end
1362
+
1363
+ [
1364
+ [false, 1, 0, 'TEST'],
1365
+ [false, 0, 1, 'TEST'],
1366
+ [false, 0, 0, 'TEST2'],
1367
+ [false, -1, 1, 'TEST'],
1368
+ [true, 0, 0, 'TEST']
1369
+ ].each do |(isdst, base_utc_offset, std_offset, abbreviation)|
1370
+ define_method "test_load_tz_string_does_not_match_#{isdst ? 'dst' : 'std'}_constant_offset_#{base_utc_offset}_#{std_offset}_#{abbreviation}" do
1371
+ offsets = [{:gmtoff => 0, :isdst => isdst, :abbrev => 'TEST'}]
1372
+ rules = TimezoneOffset.new(base_utc_offset, std_offset, abbreviation)
1373
+
1374
+ tzif_test(offsets, [], :rules => rules) do |path, format|
1375
+ error = assert_raises(InvalidZoneinfoFile) { ZoneinfoTimezoneInfo.new('Offset/Mismatch', path, @posix_tz_parser) }
1376
+ assert_equal("Constant offset POSIX-style TZ string does not match constant offset in file '#{path}'.", error.message)
1377
+ end
1378
+ end
1379
+ end
1380
+
1381
+ [
1382
+ [3601, 'XST'],
1383
+ [3600, 'YST']
1384
+ ].each do |(base_utc_offset, abbreviation)|
1385
+ define_method "test_load_tz_string_does_not_match_final_std_transition_offset_#{base_utc_offset}_#{abbreviation}" do
1386
+ offsets = [
1387
+ {:gmtoff => 3542, :isdst => false, :abbrev => 'LMT'},
1388
+ {:gmtoff => 3600, :isdst => false, :abbrev => 'XST'},
1389
+ {:gmtoff => 7200, :isdst => true, :abbrev => 'XDT'}
1390
+ ]
1391
+
1392
+ transitions = [
1393
+ {:at => Time.utc(1971, 1, 2, 2, 0, 0) - 3542, :offset_index => 1},
1394
+ {:at => Time.utc(1981, 4, 10, 2, 0, 0) - 3600, :offset_index => 2},
1395
+ {:at => Time.utc(1981, 10, 27, 2, 0, 0) - 7200, :offset_index => 1}
1396
+ ]
1397
+
1398
+ rules = AnnualRules.new(
1399
+ TimezoneOffset.new(base_utc_offset, 0, abbreviation),
1400
+ TimezoneOffset.new(3600, 3600, 'XDT'),
1401
+ JulianDayOfYearTransitionRule.new(100, 7200),
1402
+ JulianDayOfYearTransitionRule.new(300, 7200)
1403
+ )
1404
+
1405
+ tzif_test(offsets, transitions, :rules => rules) do |path, format|
1406
+ error = assert_raises(InvalidZoneinfoFile) { ZoneinfoTimezoneInfo.new('Offset/Mismatch', path, @posix_tz_parser) }
1407
+ assert_equal("The first offset indicated by the POSIX-style TZ string did not match the final defined offset in file '#{path}'.", error.message)
1408
+ end
1409
+ end
1410
+ end
1411
+
1412
+ [
1413
+ [3601, 0, 'XST'],
1414
+ [3600, 1, 'XST'],
1415
+ [3600, 0, 'YST']
1416
+ ].each do |(base_utc_offset, std_offset, abbreviation)|
1417
+ define_method "test_load_tz_string_does_not_match_final_dst_transition_offset_#{base_utc_offset}_#{std_offset}_#{abbreviation}" do
1418
+ offsets = [
1419
+ {:gmtoff => 3542, :isdst => false, :abbrev => 'LMT'},
1420
+ {:gmtoff => 3600, :isdst => false, :abbrev => 'XST'},
1421
+ {:gmtoff => 7200, :isdst => true, :abbrev => 'XDT'}
1422
+ ]
1423
+
1424
+ transitions = [
1425
+ {:at => Time.utc(1971, 1, 2, 2, 0, 0) - 3542, :offset_index => 1},
1426
+ {:at => Time.utc(1981, 4, 10, 2, 0, 0) - 3600, :offset_index => 2}
1427
+ ]
1428
+
1429
+ rules = AnnualRules.new(
1430
+ TimezoneOffset.new(3600, 0, 'XST'),
1431
+ TimezoneOffset.new(base_utc_offset, std_offset, abbreviation),
1432
+ JulianDayOfYearTransitionRule.new(100, 7200),
1433
+ JulianDayOfYearTransitionRule.new(300, 7200)
1434
+ )
1435
+
1436
+ tzif_test(offsets, transitions, :rules => rules) do |path, format|
1437
+ error = assert_raises(InvalidZoneinfoFile) { ZoneinfoTimezoneInfo.new('Offset/Mismatch', path, @posix_tz_parser) }
1438
+ assert_equal("The first offset indicated by the POSIX-style TZ string did not match the final defined offset in file '#{path}'.", error.message)
1439
+ end
1440
+ end
1441
+ end
1442
+
1443
+ [
1444
+ [3600, 0],
1445
+ [3600, 3600],
1446
+ [10800, -3600]
1447
+ ].each do |(base_utc_offset, std_offset)|
1448
+ rules = TimezoneOffset.new(base_utc_offset, std_offset, 'TEST')
1449
+
1450
+ define_method "test_load_tz_string_uses_constant_offset_with_no_transitions_#{base_utc_offset}_#{std_offset}" do
1451
+ offsets = [{:gmtoff => base_utc_offset + std_offset, :isdst => std_offset != 0, :abbrev => 'TEST'}]
1452
+
1453
+ tzif_test(offsets, [], :rules => rules) do |path, format|
1454
+ info = ZoneinfoTimezoneInfo.new('Constant/Offset', path, @posix_tz_parser)
1455
+ assert_equal('Constant/Offset', info.identifier)
1456
+ assert_period(:TEST, base_utc_offset, std_offset, std_offset != 0, nil, nil, info)
1457
+ end
1458
+ end
1459
+
1460
+ define_method "test_load_tz_string_uses_constant_offset_after_last_transition_#{base_utc_offset}_#{std_offset}" do
1461
+ offsets = [
1462
+ {:gmtoff => 3542, :isdst => false, :abbrev => 'LMT'},
1463
+ {:gmtoff => base_utc_offset + std_offset, :isdst => std_offset != 0, :abbrev => 'TEST'}
1464
+ ]
1465
+
1466
+ transitions = [
1467
+ {:at => Time.utc(1971, 1, 2, 2, 0, 0) - 3542, :offset_index => 1}
1468
+ ]
1469
+
1470
+ t0 = Time.utc(1971, 1, 2, 2, 0, 0) - 3542
1471
+
1472
+ tzif_test(offsets, transitions, :rules => rules) do |path, format|
1473
+ info = ZoneinfoTimezoneInfo.new('Constant/After', path, @posix_tz_parser)
1474
+ assert_equal('Constant/After', info.identifier)
1475
+ assert_period(:LMT, 3542, 0, false, nil, t0, info)
1476
+ assert_period(:TEST, base_utc_offset, std_offset, std_offset != 0, t0, nil, info)
1477
+ end
1478
+ end
1479
+ end
1480
+
1481
+ def test_load_tz_string_uses_rules_to_generate_all_transitions_when_none_defined
1482
+ offsets = [{:gmtoff => 7200, :isdst => false, :abbrev => 'XST'}]
1483
+
1484
+ rules = AnnualRules.new(
1485
+ TimezoneOffset.new(7200, 0, 'XST'),
1486
+ TimezoneOffset.new(7200, 3600, 'XDT'),
1487
+ JulianDayOfYearTransitionRule.new(100, 3600),
1488
+ JulianDayOfYearTransitionRule.new(300, 7200)
1489
+ )
1490
+
1491
+ generate_up_to = ZoneinfoTimezoneInfo::GENERATE_UP_TO
1492
+
1493
+ tzif_test(offsets, [], :rules => rules) do |path, format|
1494
+ info = ZoneinfoTimezoneInfo.new('All/Rules', path, @posix_tz_parser)
1495
+ assert_equal('All/Rules', info.identifier)
1496
+
1497
+ assert_period(:XST, 7200, 0, false, nil, Time.utc(1970, 4, 10, 1, 0, 0) - 7200, info)
1498
+
1499
+ 1970.upto(generate_up_to - 1).each do |year|
1500
+ assert_period(:XDT, 7200, 3600, true, Time.utc(year, 4, 10, 1, 0, 0) - 7200, Time.utc(year, 10, 27, 2, 0, 0) - 10800, info)
1501
+ assert_period(:XST, 7200, 0, false, Time.utc(year, 10, 27, 2, 0, 0) - 10800, Time.utc(year + 1, 4, 10, 1, 0, 0) - 7200, info)
1502
+ end
1503
+
1504
+ assert_period(:XDT, 7200, 3600, true, Time.utc(generate_up_to, 4, 10, 1, 0, 0) - 7200, Time.utc(generate_up_to, 10, 27, 2, 0, 0) - 10800, info)
1505
+ assert_period(:XST, 7200, 0, false, Time.utc(generate_up_to, 10, 27, 2, 0, 0) - 10800, nil, info)
1506
+ end
1507
+ end
1508
+
1509
+ def test_load_tz_string_uses_rules_to_generate_all_transitions_when_none_defined_omitting_first_if_matches_first_offset
1510
+ offsets = [{:gmtoff => 10800, :isdst => true, :abbrev => 'XDT'}]
1511
+
1512
+ rules = AnnualRules.new(
1513
+ TimezoneOffset.new(7200, 0, 'XST'),
1514
+ TimezoneOffset.new(7200, 3600, 'XDT'),
1515
+ JulianDayOfYearTransitionRule.new(100, 3600),
1516
+ JulianDayOfYearTransitionRule.new(300, 7200)
1517
+ )
1518
+
1519
+ generate_up_to = ZoneinfoTimezoneInfo::GENERATE_UP_TO
1520
+
1521
+ tzif_test(offsets, [], :rules => rules) do |path, format|
1522
+ info = ZoneinfoTimezoneInfo.new('All/Rules', path, @posix_tz_parser)
1523
+ assert_equal('All/Rules', info.identifier)
1524
+
1525
+ assert_period(:XDT, 7200, 3600, true, nil, Time.utc(1970, 10, 27, 2, 0, 0) - 10800, info)
1526
+ assert_period(:XST, 7200, 0, false, Time.utc(1970, 10, 27, 2, 0, 0) - 10800, Time.utc(1971, 4, 10, 1, 0, 0) - 7200, info)
1527
+
1528
+ 1971.upto(generate_up_to - 1).each do |year|
1529
+ assert_period(:XDT, 7200, 3600, true, Time.utc(year, 4, 10, 1, 0, 0) - 7200, Time.utc(year, 10, 27, 2, 0, 0) - 10800, info)
1530
+ assert_period(:XST, 7200, 0, false, Time.utc(year, 10, 27, 2, 0, 0) - 10800, Time.utc(year + 1, 4, 10, 1, 0, 0) - 7200, info)
1531
+ end
1532
+
1533
+ assert_period(:XDT, 7200, 3600, true, Time.utc(generate_up_to, 4, 10, 1, 0, 0) - 7200, Time.utc(generate_up_to, 10, 27, 2, 0, 0) - 10800, info)
1534
+ assert_period(:XST, 7200, 0, false, Time.utc(generate_up_to, 10, 27, 2, 0, 0) - 10800, nil, info)
1535
+ end
1536
+ end
1537
+
1538
+ def test_load_tz_string_uses_rules_to_generate_all_transitions_when_none_defined_with_previous_offset_of_first_matching_first_offset
1539
+ offsets = [{:gmtoff => 7142, :isdst => false, :abbrev => 'LMT'}]
1540
+
1541
+ rules = AnnualRules.new(
1542
+ TimezoneOffset.new(7200, 0, 'XST'),
1543
+ TimezoneOffset.new(7200, 3600, 'XDT'),
1544
+ JulianDayOfYearTransitionRule.new(100, 3600),
1545
+ JulianDayOfYearTransitionRule.new(300, 7200)
1546
+ )
1547
+
1548
+ generate_up_to = ZoneinfoTimezoneInfo::GENERATE_UP_TO
1549
+
1550
+ tzif_test(offsets, [], :rules => rules) do |path, format|
1551
+ info = ZoneinfoTimezoneInfo.new('All/Rules', path, @posix_tz_parser)
1552
+ assert_equal('All/Rules', info.identifier)
1553
+
1554
+ assert_period(:LMT, 7142, 0, false, nil, Time.utc(1970, 4, 10, 1, 0, 0) - 7200, info)
1555
+
1556
+ 1970.upto(generate_up_to - 1).each do |year|
1557
+ assert_period(:XDT, 7200, 3600, true, Time.utc(year, 4, 10, 1, 0, 0) - 7200, Time.utc(year, 10, 27, 2, 0, 0) - 10800, info)
1558
+ assert_period(:XST, 7200, 0, false, Time.utc(year, 10, 27, 2, 0, 0) - 10800, Time.utc(year + 1, 4, 10, 1, 0, 0) - 7200, info)
1559
+ end
1560
+
1561
+ assert_period(:XDT, 7200, 3600, true, Time.utc(generate_up_to, 4, 10, 1, 0, 0) - 7200, Time.utc(generate_up_to, 10, 27, 2, 0, 0) - 10800, info)
1562
+ assert_period(:XST, 7200, 0, false, Time.utc(generate_up_to, 10, 27, 2, 0, 0) - 10800, nil, info)
1563
+ end
1564
+ end
1565
+
1566
+ def test_load_tz_string_uses_rules_to_generate_all_transitions_when_none_defined_correcting_initial_offset
1567
+ offsets = [{:gmtoff => 10800, :isdst => true, :abbrev => 'XDDT'}]
1568
+
1569
+ rules = AnnualRules.new(
1570
+ TimezoneOffset.new(3600, 0, 'XST'),
1571
+ TimezoneOffset.new(3600, 7200, 'XDDT'),
1572
+ JulianDayOfYearTransitionRule.new(100, 3600),
1573
+ JulianDayOfYearTransitionRule.new(300, 7200)
1574
+ )
1575
+
1576
+ generate_up_to = ZoneinfoTimezoneInfo::GENERATE_UP_TO
1577
+
1578
+ tzif_test(offsets, [], :rules => rules) do |path, format|
1579
+ info = ZoneinfoTimezoneInfo.new('All/Rules', path, @posix_tz_parser)
1580
+ assert_equal('All/Rules', info.identifier)
1581
+
1582
+ assert_period(:XDDT, 3600, 7200, true, nil, Time.utc(1970, 10, 27, 2, 0, 0) - 10800, info) # would be :XDT, 7200, 3600 otherwise
1583
+ assert_period(:XST, 3600, 0, false, Time.utc(1970, 10, 27, 2, 0, 0) - 10800, Time.utc(1971, 4, 10, 1, 0, 0) - 3600, info)
1584
+
1585
+ 1971.upto(generate_up_to - 1).each do |year|
1586
+ assert_period(:XDDT, 3600, 7200, true, Time.utc(year, 4, 10, 1, 0, 0) - 3600, Time.utc(year, 10, 27, 2, 0, 0) - 10800, info)
1587
+ assert_period(:XST, 3600, 0, false, Time.utc(year, 10, 27, 2, 0, 0) - 10800, Time.utc(year + 1, 4, 10, 1, 0, 0) - 3600, info)
1588
+ end
1589
+
1590
+ assert_period(:XDDT, 3600, 7200, true, Time.utc(generate_up_to, 4, 10, 1, 0, 0) - 3600, Time.utc(generate_up_to, 10, 27, 2, 0, 0) - 10800, info)
1591
+ assert_period(:XST, 3600, 0, false, Time.utc(generate_up_to, 10, 27, 2, 0, 0) - 10800, nil, info)
1592
+ end
1593
+ end
1594
+
1595
+ def test_load_tz_string_extends_transitions_starting_from_std_to_dst_following_year
1596
+ offsets = [
1597
+ {:gmtoff => 7142, :isdst => false, :abbrev => 'LMT'},
1598
+ {:gmtoff => 7200, :isdst => false, :abbrev => 'XST'},
1599
+ {:gmtoff => 10800, :isdst => true, :abbrev => 'XDT'}
1600
+ ]
1601
+
1602
+ transitions = [
1603
+ {:at => Time.utc(1971, 1, 2, 2, 0, 0) - 7142, :offset_index => 1},
1604
+ {:at => Time.utc(1981, 4, 10, 1, 0, 0) - 7200, :offset_index => 2},
1605
+ {:at => Time.utc(1981, 10, 27, 2, 0, 0) - 10800, :offset_index => 1}
1606
+ ]
1607
+
1608
+ rules = AnnualRules.new(
1609
+ TimezoneOffset.new(7200, 0, 'XST'),
1610
+ TimezoneOffset.new(7200, 3600, 'XDT'),
1611
+ JulianDayOfYearTransitionRule.new(100, 3600),
1612
+ JulianDayOfYearTransitionRule.new(300, 7200)
1613
+ )
1614
+
1615
+ generate_up_to = ZoneinfoTimezoneInfo::GENERATE_UP_TO
1616
+
1617
+ tzif_test(offsets, transitions, :rules => rules) do |path, format|
1618
+ info = ZoneinfoTimezoneInfo.new('From/Rules', path, @posix_tz_parser)
1619
+ assert_equal('From/Rules', info.identifier)
1620
+
1621
+ assert_period(:LMT, 7142, 0, false, nil, Time.utc(1971, 1, 2, 2, 0, 0) - 7142, info)
1622
+ assert_period(:XST, 7200, 0, false, Time.utc(1971, 1, 2, 2, 0, 0) - 7142, Time.utc(1981, 4, 10, 1, 0, 0) - 7200, info)
1623
+
1624
+ 1981.upto(generate_up_to - 1).each do |year|
1625
+ assert_period(:XDT, 7200, 3600, true, Time.utc(year, 4, 10, 1, 0, 0) - 7200, Time.utc(year, 10, 27, 2, 0, 0) - 10800, info)
1626
+ assert_period(:XST, 7200, 0, false, Time.utc(year, 10, 27, 2, 0, 0) - 10800, Time.utc(year + 1, 4, 10, 1, 0, 0) - 7200, info)
1627
+ end
1628
+
1629
+ assert_period(:XDT, 7200, 3600, true, Time.utc(generate_up_to, 4, 10, 1, 0, 0) - 7200, Time.utc(generate_up_to, 10, 27, 2, 0, 0) - 10800, info)
1630
+ assert_period(:XST, 7200, 0, false, Time.utc(generate_up_to, 10, 27, 2, 0, 0) - 10800, nil, info)
1631
+ end
1632
+ end
1633
+
1634
+ def test_load_tz_string_extends_transitions_starting_from_dst_to_std_same_year
1635
+ offsets = [
1636
+ {:gmtoff => 7142, :isdst => false, :abbrev => 'LMT'},
1637
+ {:gmtoff => 7200, :isdst => false, :abbrev => 'XST'},
1638
+ {:gmtoff => 10800, :isdst => true, :abbrev => 'XDT'}
1639
+ ]
1640
+
1641
+ transitions = [
1642
+ {:at => Time.utc(1971, 1, 2, 2, 0, 0) - 7142, :offset_index => 1},
1643
+ {:at => Time.utc(1981, 4, 10, 1, 0, 0) - 7200, :offset_index => 2},
1644
+ {:at => Time.utc(1981, 10, 27, 2, 0, 0) - 10800, :offset_index => 1},
1645
+ {:at => Time.utc(1982, 4, 10, 1, 0, 0) - 7200, :offset_index => 2}
1646
+ ]
1647
+
1648
+ rules = AnnualRules.new(
1649
+ TimezoneOffset.new(7200, 0, 'XST'),
1650
+ TimezoneOffset.new(7200, 3600, 'XDT'),
1651
+ JulianDayOfYearTransitionRule.new(100, 3600),
1652
+ JulianDayOfYearTransitionRule.new(300, 7200)
1653
+ )
1654
+
1655
+ generate_up_to = ZoneinfoTimezoneInfo::GENERATE_UP_TO
1656
+
1657
+ tzif_test(offsets, transitions, :rules => rules) do |path, format|
1658
+ info = ZoneinfoTimezoneInfo.new('From/Rules', path, @posix_tz_parser)
1659
+ assert_equal('From/Rules', info.identifier)
1660
+
1661
+ assert_period(:LMT, 7142, 0, false, nil, Time.utc(1971, 1, 2, 2, 0, 0) - 7142, info)
1662
+ assert_period(:XST, 7200, 0, false, Time.utc(1971, 1, 2, 2, 0, 0) - 7142, Time.utc(1981, 4, 10, 1, 0, 0) - 7200, info)
1663
+
1664
+ 1981.upto(generate_up_to - 1).each do |year|
1665
+ assert_period(:XDT, 7200, 3600, true, Time.utc(year, 4, 10, 1, 0, 0) - 7200, Time.utc(year, 10, 27, 2, 0, 0) - 10800, info)
1666
+ assert_period(:XST, 7200, 0, false, Time.utc(year, 10, 27, 2, 0, 0) - 10800, Time.utc(year + 1, 4, 10, 1, 0, 0) - 7200, info)
1667
+ end
1668
+
1669
+ assert_period(:XDT, 7200, 3600, true, Time.utc(generate_up_to, 4, 10, 1, 0, 0) - 7200, Time.utc(generate_up_to, 10, 27, 2, 0, 0) - 10800, info)
1670
+ assert_period(:XST, 7200, 0, false, Time.utc(generate_up_to, 10, 27, 2, 0, 0) - 10800, nil, info)
1671
+ end
1672
+ end
1673
+
1674
+ def test_load_tz_string_extends_transitions_starting_from_dst_to_std_following_year
1675
+ offsets = [
1676
+ {:gmtoff => 7142, :isdst => false, :abbrev => 'LMT'},
1677
+ {:gmtoff => 7200, :isdst => false, :abbrev => 'XST'},
1678
+ {:gmtoff => 10800, :isdst => true, :abbrev => 'XDT'}
1679
+ ]
1680
+
1681
+ transitions = [
1682
+ {:at => Time.utc(1971, 1, 2, 1, 0, 0) - 7142, :offset_index => 1},
1683
+ {:at => Time.utc(1981, 10, 27, 1, 0, 0) - 7200, :offset_index => 2},
1684
+ {:at => Time.utc(1982, 4, 10, 2, 0, 0) - 10800, :offset_index => 1},
1685
+ {:at => Time.utc(1982, 10, 27, 1, 0, 0) - 7200, :offset_index => 2}
1686
+ ]
1687
+
1688
+ rules = AnnualRules.new(
1689
+ TimezoneOffset.new(7200, 0, 'XST'),
1690
+ TimezoneOffset.new(7200, 3600, 'XDT'),
1691
+ JulianDayOfYearTransitionRule.new(300, 3600),
1692
+ JulianDayOfYearTransitionRule.new(100, 7200)
1693
+ )
1694
+
1695
+ generate_up_to = ZoneinfoTimezoneInfo::GENERATE_UP_TO
1696
+
1697
+ tzif_test(offsets, transitions, :rules => rules) do |path, format|
1698
+ info = ZoneinfoTimezoneInfo.new('From/Rules', path, @posix_tz_parser)
1699
+ assert_equal('From/Rules', info.identifier)
1700
+
1701
+ assert_period(:LMT, 7142, 0, false, nil, Time.utc(1971, 1, 2, 1, 0, 0) - 7142, info)
1702
+ assert_period(:XST, 7200, 0, false, Time.utc(1971, 1, 2, 1, 0, 0) - 7142, Time.utc(1981, 10, 27, 1, 0, 0) - 7200, info)
1703
+ assert_period(:XDT, 7200, 3600, true, Time.utc(1981, 10, 27, 1, 0, 0) - 7200, Time.utc(1982, 4, 10, 2, 0, 0) - 10800, info)
1704
+
1705
+ 1982.upto(generate_up_to - 1).each do |year|
1706
+ assert_period(:XST, 7200, 0, false, Time.utc(year, 4, 10, 1, 0, 0) - 7200, Time.utc(year, 10, 27, 2, 0, 0) - 10800, info)
1707
+ assert_period(:XDT, 7200, 3600, true, Time.utc(year, 10, 27, 2, 0, 0) - 10800, Time.utc(year + 1, 4, 10, 1, 0, 0) - 7200, info)
1708
+ end
1709
+
1710
+ assert_period(:XST, 7200, 0, false, Time.utc(generate_up_to, 4, 10, 1, 0, 0) - 7200, Time.utc(generate_up_to, 10, 27, 2, 0, 0) - 10800, info)
1711
+ assert_period(:XDT, 7200, 3600, true, Time.utc(generate_up_to, 10, 27, 2, 0, 0) - 10800, nil, info)
1712
+ end
1713
+ end
1714
+
1715
+ def test_load_tz_string_extends_transitions_starting_from_std_to_dst_same_year
1716
+ offsets = [
1717
+ {:gmtoff => 7142, :isdst => false, :abbrev => 'LMT'},
1718
+ {:gmtoff => 7200, :isdst => false, :abbrev => 'XST'},
1719
+ {:gmtoff => 10800, :isdst => true, :abbrev => 'XDT'}
1720
+ ]
1721
+
1722
+ transitions = [
1723
+ {:at => Time.utc(1971, 1, 2, 1, 0, 0) - 7142, :offset_index => 1},
1724
+ {:at => Time.utc(1981, 10, 27, 1, 0, 0) - 7200, :offset_index => 2},
1725
+ {:at => Time.utc(1982, 4, 10, 2, 0, 0) - 10800, :offset_index => 1},
1726
+ {:at => Time.utc(1982, 10, 27, 1, 0, 0) - 7200, :offset_index => 2},
1727
+ {:at => Time.utc(1983, 4, 10, 2, 0, 0) - 10800, :offset_index => 1}
1728
+ ]
1729
+
1730
+ rules = AnnualRules.new(
1731
+ TimezoneOffset.new(7200, 0, 'XST'),
1732
+ TimezoneOffset.new(7200, 3600, 'XDT'),
1733
+ JulianDayOfYearTransitionRule.new(300, 3600),
1734
+ JulianDayOfYearTransitionRule.new(100, 7200)
1735
+ )
1736
+
1737
+ generate_up_to = ZoneinfoTimezoneInfo::GENERATE_UP_TO
1738
+
1739
+ tzif_test(offsets, transitions, :rules => rules) do |path, format|
1740
+ info = ZoneinfoTimezoneInfo.new('From/Rules', path, @posix_tz_parser)
1741
+ assert_equal('From/Rules', info.identifier)
1742
+
1743
+ assert_period(:LMT, 7142, 0, false, nil, Time.utc(1971, 1, 2, 1, 0, 0) - 7142, info)
1744
+ assert_period(:XST, 7200, 0, false, Time.utc(1971, 1, 2, 1, 0, 0) - 7142, Time.utc(1981, 10, 27, 1, 0, 0) - 7200, info)
1745
+ assert_period(:XDT, 7200, 3600, true, Time.utc(1981, 10, 27, 1, 0, 0) - 7200, Time.utc(1982, 4, 10, 2, 0, 0) - 10800, info)
1746
+
1747
+ 1982.upto(generate_up_to - 1).each do |year|
1748
+ assert_period(:XST, 7200, 0, false, Time.utc(year, 4, 10, 1, 0, 0) - 7200, Time.utc(year, 10, 27, 2, 0, 0) - 10800, info)
1749
+ assert_period(:XDT, 7200, 3600, true, Time.utc(year, 10, 27, 2, 0, 0) - 10800, Time.utc(year + 1, 4, 10, 1, 0, 0) - 7200, info)
1750
+ end
1751
+
1752
+ assert_period(:XST, 7200, 0, false, Time.utc(generate_up_to, 4, 10, 1, 0, 0) - 7200, Time.utc(generate_up_to, 10, 27, 2, 0, 0) - 10800, info)
1753
+ assert_period(:XDT, 7200, 3600, true, Time.utc(generate_up_to, 10, 27, 2, 0, 0) - 10800, nil, info)
1754
+ end
1755
+ end
1756
+
1757
+ def test_load_tz_string_extends_transitions_negative_dst
1758
+ offsets = [
1759
+ {:gmtoff => 7142, :isdst => false, :abbrev => 'LMT'},
1760
+ {:gmtoff => 7200, :isdst => true, :abbrev => 'XDT'},
1761
+ {:gmtoff => 10800, :isdst => false, :abbrev => 'XST'}
1762
+ ]
1763
+
1764
+ transitions = [
1765
+ {:at => Time.utc(1971, 1, 2, 2, 0, 0) - 7142, :offset_index => 1},
1766
+ {:at => Time.utc(1981, 4, 10, 1, 0, 0) - 7200, :offset_index => 2},
1767
+ {:at => Time.utc(1981, 10, 27, 2, 0, 0) - 10800, :offset_index => 1}
1768
+ ]
1769
+
1770
+ rules = AnnualRules.new(
1771
+ TimezoneOffset.new(10800, 0, 'XST'),
1772
+ TimezoneOffset.new(10800, -3600, 'XDT'),
1773
+ JulianDayOfYearTransitionRule.new(300, 7200),
1774
+ JulianDayOfYearTransitionRule.new(100, 3600)
1775
+ )
1776
+
1777
+ generate_up_to = ZoneinfoTimezoneInfo::GENERATE_UP_TO
1778
+
1779
+ tzif_test(offsets, transitions, :rules => rules) do |path, format|
1780
+ info = ZoneinfoTimezoneInfo.new('From/Rules', path, @posix_tz_parser)
1781
+ assert_equal('From/Rules', info.identifier)
1782
+
1783
+ assert_period(:LMT, 7142, 0, false, nil, Time.utc(1971, 1, 2, 2, 0, 0) - 7142, info)
1784
+ assert_period(:XDT, 10800, -3600, true, Time.utc(1971, 1, 2, 2, 0, 0) - 7142, Time.utc(1981, 4, 10, 1, 0, 0) - 7200, info)
1785
+
1786
+ 1981.upto(generate_up_to - 1).each do |year|
1787
+ assert_period(:XST, 10800, 0, false, Time.utc(year, 4, 10, 1, 0, 0) - 7200, Time.utc(year, 10, 27, 2, 0, 0) - 10800, info)
1788
+ assert_period(:XDT, 10800, -3600, true, Time.utc(year, 10, 27, 2, 0, 0) - 10800, Time.utc(year + 1, 4, 10, 1, 0, 0) - 7200, info)
1789
+ end
1790
+
1791
+ assert_period(:XST, 10800, 0, false, Time.utc(generate_up_to, 4, 10, 1, 0, 0) - 7200, Time.utc(generate_up_to, 10, 27, 2, 0, 0) - 10800, info)
1792
+ assert_period(:XDT, 10800, -3600, true, Time.utc(generate_up_to, 10, 27, 2, 0, 0) - 10800, nil, info)
1793
+ end
1794
+ end
1795
+
1796
+ def test_load_tz_string_extends_single_transition_in_final_year
1797
+ offsets = [
1798
+ {:gmtoff => 7142, :isdst => false, :abbrev => 'LMT'},
1799
+ {:gmtoff => 7200, :isdst => false, :abbrev => 'XST'},
1800
+ {:gmtoff => 10800, :isdst => true, :abbrev => 'XDT'}
1801
+ ]
1802
+
1803
+ generate_up_to = ZoneinfoTimezoneInfo::GENERATE_UP_TO
1804
+
1805
+ transitions = [
1806
+ {:at => Time.utc( 1971, 1, 2, 2, 0, 0) - 7142, :offset_index => 1},
1807
+ {:at => Time.utc(generate_up_to - 1, 4, 10, 1, 0, 0) - 7200, :offset_index => 2},
1808
+ {:at => Time.utc(generate_up_to - 1, 10, 27, 2, 0, 0) - 10800, :offset_index => 1},
1809
+ {:at => Time.utc(generate_up_to, 4, 10, 1, 0, 0) - 7200, :offset_index => 2}
1810
+ ]
1811
+
1812
+ rules = AnnualRules.new(
1813
+ TimezoneOffset.new(7200, 0, 'XST'),
1814
+ TimezoneOffset.new(7200, 3600, 'XDT'),
1815
+ JulianDayOfYearTransitionRule.new(100, 3600),
1816
+ JulianDayOfYearTransitionRule.new(300, 7200)
1817
+ )
1818
+
1819
+ tzif_test(offsets, transitions, :rules => rules) do |path, format|
1820
+ info = ZoneinfoTimezoneInfo.new('Final/Year', path, @posix_tz_parser)
1821
+ assert_equal('Final/Year', info.identifier)
1822
+
1823
+ assert_period(:LMT, 7142, 0, false, nil, Time.utc( 1971, 1, 2, 2, 0, 0) - 7142, info)
1824
+ assert_period(:XST, 7200, 0, false, Time.utc( 1971, 1, 2, 2, 0, 0) - 7142, Time.utc(generate_up_to - 1, 4, 10, 1, 0, 0) - 7200, info)
1825
+ assert_period(:XDT, 7200, 3600, true, Time.utc(generate_up_to - 1, 4, 10, 1, 0, 0) - 7200, Time.utc(generate_up_to - 1, 10, 27, 2, 0, 0) - 10800, info)
1826
+ assert_period(:XST, 7200, 0, false, Time.utc(generate_up_to - 1, 10, 27, 2, 0, 0) - 10800, Time.utc(generate_up_to, 4, 10, 1, 0, 0) - 7200, info)
1827
+ assert_period(:XDT, 7200, 3600, true, Time.utc(generate_up_to, 4, 10, 1, 0, 0) - 7200, Time.utc(generate_up_to, 10, 27, 2, 0, 0) - 10800, info)
1828
+ assert_period(:XST, 7200, 0, false, Time.utc(generate_up_to, 10, 27, 2, 0, 0) - 10800, nil, info)
1829
+ end
1830
+ end
1831
+
1832
+ def test_load_tz_string_adds_nothing_if_transitions_up_to_final_year
1833
+ offsets = [
1834
+ {:gmtoff => 7142, :isdst => false, :abbrev => 'LMT'},
1835
+ {:gmtoff => 7200, :isdst => false, :abbrev => 'XST'},
1836
+ {:gmtoff => 10800, :isdst => true, :abbrev => 'XDT'}
1837
+ ]
1838
+
1839
+ generate_up_to = ZoneinfoTimezoneInfo::GENERATE_UP_TO
1840
+
1841
+ transitions = [
1842
+ {:at => Time.utc( 1971, 1, 2, 2, 0, 0) - 7142, :offset_index => 1},
1843
+ {:at => Time.utc(generate_up_to, 4, 10, 1, 0, 0) - 7200, :offset_index => 2},
1844
+ {:at => Time.utc(generate_up_to, 10, 27, 2, 0, 0) - 10800, :offset_index => 1},
1845
+ ]
1846
+
1847
+ rules = AnnualRules.new(
1848
+ TimezoneOffset.new(7200, 0, 'XST'),
1849
+ TimezoneOffset.new(7200, 3600, 'XDT'),
1850
+ JulianDayOfYearTransitionRule.new(100, 3600),
1851
+ JulianDayOfYearTransitionRule.new(300, 7200)
1852
+ )
1853
+
1854
+ tzif_test(offsets, transitions, :rules => rules) do |path, format|
1855
+ info = ZoneinfoTimezoneInfo.new('Final/Year', path, @posix_tz_parser)
1856
+ assert_equal('Final/Year', info.identifier)
1857
+
1858
+ assert_period(:LMT, 7142, 0, false, nil, Time.utc( 1971, 1, 2, 2, 0, 0) - 7142, info)
1859
+ assert_period(:XDT, 7200, 3600, true, Time.utc(generate_up_to, 4, 10, 1, 0, 0) - 7200, Time.utc(generate_up_to, 10, 27, 2, 0, 0) - 10800, info)
1860
+ assert_period(:XST, 7200, 0, false, Time.utc(generate_up_to, 10, 27, 2, 0, 0) - 10800, nil, info)
1861
+ end
1862
+ end
1863
+
1864
+ def test_load_tz_string_corrects_offset_of_final_transition
1865
+ offsets = [
1866
+ {:gmtoff => 3542, :isdst => false, :abbrev => 'LMT'},
1867
+ {:gmtoff => 7200, :isdst => true, :abbrev => 'XDT'}]
1868
+
1869
+ transitions = [{:at => Time.utc(2000, 4, 10, 1, 0, 0) - 3542, :offset_index => 1}]
1870
+
1871
+ rules = AnnualRules.new(
1872
+ TimezoneOffset.new(3600, 0, 'XST'),
1873
+ TimezoneOffset.new(3600, 3600, 'XDT'),
1874
+ JulianDayOfYearTransitionRule.new(100, 3600),
1875
+ JulianDayOfYearTransitionRule.new(300, 7200)
1876
+ )
1877
+
1878
+ generate_up_to = ZoneinfoTimezoneInfo::GENERATE_UP_TO
1879
+
1880
+ tzif_test(offsets, transitions, :rules => rules) do |path, format|
1881
+ info = ZoneinfoTimezoneInfo.new('Correct/Final', path, @posix_tz_parser)
1882
+ assert_equal('Correct/Final', info.identifier)
1883
+
1884
+ assert_period(:LMT, 3542, 0, false, nil, Time.utc(2000, 4, 10, 1, 0, 0) - 3542, info)
1885
+ assert_period(:XDT, 3600, 3600, true, Time.utc(2000, 4, 10, 1, 0, 0) - 3542, Time.utc(2000, 10, 27, 2, 0, 0) - 7200, info) # would be :XDT, 3542, 3658 without tz_string
1886
+ assert_period(:XST, 3600, 0, false, Time.utc(2000, 10, 27, 2, 0, 0) - 7200, Time.utc(2001, 4, 10, 1, 0, 0) - 3600, info)
1887
+
1888
+ 2001.upto(generate_up_to - 1).each do |year|
1889
+ assert_period(:XDT, 3600, 3600, true, Time.utc(year, 4, 10, 1, 0, 0) - 3600, Time.utc(year, 10, 27, 2, 0, 0) - 7200, info)
1890
+ assert_period(:XST, 3600, 0, false, Time.utc(year, 10, 27, 2, 0, 0) - 7200, Time.utc(year + 1, 4, 10, 1, 0, 0) - 3600, info)
1891
+ end
1892
+
1893
+ assert_period(:XDT, 3600, 3600, true, Time.utc(generate_up_to, 4, 10, 1, 0, 0) - 3600, Time.utc(generate_up_to, 10, 27, 2, 0, 0) - 7200, info)
1894
+ assert_period(:XST, 3600, 0, false, Time.utc(generate_up_to, 10, 27, 2, 0, 0) - 7200, nil, info)
1895
+ end
1896
+ end
1897
+
1898
+ def test_load_tz_string_specifies_transition_to_offset_of_final_transition_same_year_skip_dst_start
1899
+ # TZInfo v1.2.8 considered this to be an error. However, this is a valid
1900
+ # situation with Africa/Casablanca in 2018e.
1901
+ #
1902
+ # The last defined transitions are:
1903
+ # At 2037-03-29 02:00Z change to WEST UTC+1
1904
+ # At 2037-10-04 02:00Z change to WET UTC+0
1905
+ #
1906
+ # The rules define the end of DST to be at 03:00 local time on the last
1907
+ # Sunday of October (2037-10-31). This later transition needs to be
1908
+ # ignored.
1909
+
1910
+ offsets = [
1911
+ {:gmtoff => 7142, :isdst => false, :abbrev => 'LMT'},
1912
+ {:gmtoff => 7200, :isdst => false, :abbrev => 'XST'},
1913
+ {:gmtoff => 10800, :isdst => true, :abbrev => 'XDT'}
1914
+ ]
1915
+
1916
+ transitions = [
1917
+ {:at => Time.utc(1971, 1, 2, 2, 0, 0) - 7142, :offset_index => 1},
1918
+ {:at => Time.utc(1981, 4, 10, 1, 0, 0) - 7200, :offset_index => 2},
1919
+ {:at => Time.utc(1981, 10, 27, 2, 0, 0) - 10800, :offset_index => 1},
1920
+ {:at => Time.utc(1982, 4, 10, 1, 0, 0) - 7200, :offset_index => 2}
1921
+ ]
1922
+
1923
+ rules = AnnualRules.new(
1924
+ TimezoneOffset.new(7200, 0, 'XST'),
1925
+ TimezoneOffset.new(7200, 3600, 'XDT'),
1926
+ JulianDayOfYearTransitionRule.new(101, 3600),
1927
+ JulianDayOfYearTransitionRule.new(300, 7200)
1928
+ )
1929
+
1930
+ generate_up_to = ZoneinfoTimezoneInfo::GENERATE_UP_TO
1931
+
1932
+ tzif_test(offsets, transitions, :rules => rules) do |path, format|
1933
+ info = ZoneinfoTimezoneInfo.new('Ignore/Std', path, @posix_tz_parser)
1934
+ assert_equal('Ignore/Std', info.identifier)
1935
+
1936
+ assert_period(:LMT, 7142, 0, false, nil, Time.utc(1971, 1, 2, 2, 0, 0) - 7142, info)
1937
+ assert_period(:XST, 7200, 0, false, Time.utc(1971, 1, 2, 2, 0, 0) - 7142, Time.utc(1981, 4, 10, 1, 0, 0) - 7200, info)
1938
+ assert_period(:XDT, 7200, 3600, true, Time.utc(1981, 4, 10, 1, 0, 0) - 7200, Time.utc(1981, 10, 27, 2, 0, 0) - 10800, info)
1939
+ assert_period(:XST, 7200, 0, false, Time.utc(1981, 10, 27, 2, 0, 0) - 10800, Time.utc(1982, 4, 10, 1, 0, 0) - 7200, info)
1940
+ assert_period(:XDT, 7200, 3600, true, Time.utc(1982, 4, 10, 1, 0, 0) - 7200, Time.utc(1982, 10, 27, 2, 0, 0) - 10800, info)
1941
+
1942
+ 1983.upto(generate_up_to).each do |year|
1943
+ assert_period(:XST, 7200, 0, false, Time.utc(year - 1, 10, 27, 2, 0, 0) - 10800, Time.utc(year, 4, 11, 1, 0, 0) - 7200, info)
1944
+ assert_period(:XDT, 7200, 3600, true, Time.utc(year, 4, 11, 1, 0, 0) - 7200, Time.utc(year, 10, 27, 2, 0, 0) - 10800, info)
1945
+ end
1946
+
1947
+ assert_period(:XST, 7200, 0, false, Time.utc(generate_up_to, 10, 27, 2, 0, 0) - 10800, nil, info)
1948
+ end
1949
+ end
1950
+
1951
+ def test_load_tz_string_specifies_transition_to_offset_of_final_transition_same_year_skip_dst_end
1952
+ # TZInfo v1.2.8 considered this to be an error. However, this is a valid
1953
+ # situation with Africa/Casablanca in 2018e.
1954
+ #
1955
+ # The last defined transitions are:
1956
+ # At 2037-03-29 02:00Z change to WEST UTC+1
1957
+ # At 2037-10-04 02:00Z change to WET UTC+0
1958
+ #
1959
+ # The rules define the end of DST to be at 03:00 local time on the last
1960
+ # Sunday of October (2037-10-31). This later transition needs to be
1961
+ # ignored.
1962
+
1963
+ offsets = [
1964
+ {:gmtoff => 7142, :isdst => false, :abbrev => 'LMT'},
1965
+ {:gmtoff => 7200, :isdst => false, :abbrev => 'XST'},
1966
+ {:gmtoff => 10800, :isdst => true, :abbrev => 'XDT'}
1967
+ ]
1968
+
1969
+ transitions = [
1970
+ {:at => Time.utc(1971, 1, 2, 2, 0, 0) - 7142, :offset_index => 1},
1971
+ {:at => Time.utc(1981, 4, 10, 1, 0, 0) - 7200, :offset_index => 2},
1972
+ {:at => Time.utc(1981, 10, 27, 2, 0, 0) - 10800, :offset_index => 1}
1973
+ ]
1974
+
1975
+ rules = AnnualRules.new(
1976
+ TimezoneOffset.new(7200, 0, 'XST'),
1977
+ TimezoneOffset.new(7200, 3600, 'XDT'),
1978
+ JulianDayOfYearTransitionRule.new(100, 3600),
1979
+ JulianDayOfYearTransitionRule.new(301, 7200)
1980
+ )
1981
+
1982
+ generate_up_to = ZoneinfoTimezoneInfo::GENERATE_UP_TO
1983
+
1984
+ tzif_test(offsets, transitions, :rules => rules) do |path, format|
1985
+ info = ZoneinfoTimezoneInfo.new('Ignore/Std', path, @posix_tz_parser)
1986
+ assert_equal('Ignore/Std', info.identifier)
1987
+
1988
+ assert_period(:LMT, 7142, 0, false, nil, Time.utc(1971, 1, 2, 2, 0, 0) - 7142, info)
1989
+ assert_period(:XST, 7200, 0, false, Time.utc(1971, 1, 2, 2, 0, 0) - 7142, Time.utc(1981, 4, 10, 1, 0, 0) - 7200, info)
1990
+ assert_period(:XDT, 7200, 3600, true, Time.utc(1981, 4, 10, 1, 0, 0) - 7200, Time.utc(1981, 10, 27, 2, 0, 0) - 10800, info)
1991
+ assert_period(:XST, 7200, 0, false, Time.utc(1981, 10, 27, 2, 0, 0) - 10800, Time.utc(1982, 4, 10, 1, 0, 0) - 7200, info)
1992
+
1993
+ 1982.upto(generate_up_to - 1).each do |year|
1994
+ assert_period(:XDT, 7200, 3600, true, Time.utc(year, 4, 10, 1, 0, 0) - 7200, Time.utc(year, 10, 28, 2, 0, 0) - 10800, info)
1995
+ assert_period(:XST, 7200, 0, false, Time.utc(year, 10, 28, 2, 0, 0) - 10800, Time.utc(year + 1, 4, 10, 1, 0, 0) - 7200, info)
1996
+ end
1997
+
1998
+ assert_period(:XDT, 7200, 3600, true, Time.utc(generate_up_to, 4, 10, 1, 0, 0) - 7200, Time.utc(generate_up_to, 10, 28, 2, 0, 0) - 10800, info)
1999
+ assert_period(:XST, 7200, 0, false, Time.utc(generate_up_to, 10, 28, 2, 0, 0) - 10800, nil, info)
2000
+ end
2001
+ end
2002
+
2003
+ def test_load_tz_string_specifies_transition_to_offset_of_final_transition_following_year
2004
+ offsets = [
2005
+ {:gmtoff => 7142, :isdst => false, :abbrev => 'LMT'},
2006
+ {:gmtoff => 7200, :isdst => false, :abbrev => 'XST'},
2007
+ {:gmtoff => 10800, :isdst => true, :abbrev => 'XDT'}
2008
+ ]
2009
+
2010
+ transitions = [
2011
+ {:at => Time.utc(1971, 1, 2, 2, 0, 0) - 7142, :offset_index => 1},
2012
+ {:at => Time.utc(1981, 10, 27, 2, 0, 0) - 7200, :offset_index => 2}
2013
+ ]
2014
+
2015
+ rules = AnnualRules.new(
2016
+ TimezoneOffset.new(7200, 0, 'XST'),
2017
+ TimezoneOffset.new(7200, 3600, 'XDT'),
2018
+ JulianDayOfYearTransitionRule.new(100, 3600),
2019
+ JulianDayOfYearTransitionRule.new(299, 7200)
2020
+ )
2021
+
2022
+ tzif_test(offsets, transitions, :rules => rules) do |path, format|
2023
+ error = assert_raises(InvalidZoneinfoFile) { ZoneinfoTimezoneInfo.new('Invalid/Offset', path, @posix_tz_parser) }
2024
+ assert_equal("The first offset indicated by the POSIX-style TZ string did not match the final defined offset in file '#{path}'.", error.message)
2025
+ end
2026
+ end
2027
+
2028
+ unless SUPPORTS_64BIT
2029
+ def test_generate_up_to_limited_to_2037_with_no_64_bit_support
2030
+ assert_equal(2037, ZoneinfoTimezoneInfo::GENERATE_UP_TO)
2031
+ end
2032
+
2033
+ def test_load_tz_string_does_not_generate_if_transition_after_32_bit_range_with_no_64_bit_support
2034
+ offsets = [
2035
+ {:gmtoff => 7142, :isdst => false, :abbrev => 'LMT'},
2036
+ {:gmtoff => 7200, :isdst => false, :abbrev => 'XST'},
2037
+ {:gmtoff => 10800, :isdst => true, :abbrev => 'XDT'}
2038
+ ]
2039
+
2040
+ transitions = [
2041
+ {:at => Time.utc(1971, 1, 2, 2, 0, 0) - 7142, :offset_index => 1},
2042
+ {:at => 2154474000 - 7200, :offset_index => 2}, # Time.utc(2038, 4, 10, 1, 0, 0).to_i
2043
+ {:at => 2171757600 - 10800, :offset_index => 1} # Time.utc(2038, 10, 27, 2, 0, 0).to_i
2044
+ ]
2045
+
2046
+ rules = AnnualRules.new(
2047
+ TimezoneOffset.new(7200, 0, 'XST'),
2048
+ TimezoneOffset.new(7200, 3600, 'XDT'),
2049
+ JulianDayOfYearTransitionRule.new(100, 3600),
2050
+ JulianDayOfYearTransitionRule.new(300, 7200)
2051
+ )
2052
+
2053
+ tzif_test(offsets, transitions, :rules => rules) do |path, format|
2054
+ info = ZoneinfoTimezoneInfo.new('From/Rules', path, @posix_tz_parser)
2055
+ assert_equal('From/Rules', info.identifier)
2056
+
2057
+ assert_period(:LMT, 7142, 0, false, nil, Time.utc(1971, 1, 2, 2, 0, 0) - 7142, info)
2058
+ assert_period(:XST, 7200, 0, false, Time.utc(1971, 1, 2, 2, 0, 0) - 7142, nil, info)
2059
+ end
2060
+ end
2061
+ end
2062
+
2063
+ if SUPPORTS_NEGATIVE
2064
+ def test_load_tz_string_generates_from_last_transition_if_before_1970_and_supports_negative
2065
+ offsets = [
2066
+ {:gmtoff => 7142, :isdst => false, :abbrev => 'LMT'},
2067
+ {:gmtoff => 7200, :isdst => false, :abbrev => 'XST'},
2068
+ {:gmtoff => 10800, :isdst => true, :abbrev => 'XDT'}
2069
+ ]
2070
+
2071
+ transitions = [
2072
+ {:at => Time.utc(1961, 1, 2, 2, 0, 0) - 7142, :offset_index => 1},
2073
+ {:at => Time.utc(1962, 4, 10, 1, 0, 0) - 7200, :offset_index => 2},
2074
+ {:at => Time.utc(1962, 10, 27, 2, 0, 0) - 10800, :offset_index => 1}
2075
+ ]
2076
+
2077
+ rules = AnnualRules.new(
2078
+ TimezoneOffset.new(7200, 0, 'XST'),
2079
+ TimezoneOffset.new(7200, 3600, 'XDT'),
2080
+ JulianDayOfYearTransitionRule.new(100, 3600),
2081
+ JulianDayOfYearTransitionRule.new(300, 7200)
2082
+ )
2083
+
2084
+ generate_up_to = ZoneinfoTimezoneInfo::GENERATE_UP_TO
2085
+
2086
+ tzif_test(offsets, transitions, :rules => rules) do |path, format|
2087
+ info = ZoneinfoTimezoneInfo.new('Pre/1970', path, @posix_tz_parser)
2088
+ assert_equal('Pre/1970', info.identifier)
2089
+
2090
+ assert_period(:LMT, 7142, 0, false, nil, Time.utc(1961, 1, 2, 2, 0, 0) - 7142, info)
2091
+ assert_period(:XST, 7200, 0, false, Time.utc(1961, 1, 2, 2, 0, 0) - 7142, Time.utc(1962, 4, 10, 1, 0, 0) - 7200, info)
2092
+
2093
+ 1962.upto(generate_up_to - 1).each do |year|
2094
+ assert_period(:XDT, 7200, 3600, true, Time.utc(year, 4, 10, 1, 0, 0) - 7200, Time.utc(year, 10, 27, 2, 0, 0) - 10800, info)
2095
+ assert_period(:XST, 7200, 0, false, Time.utc(year, 10, 27, 2, 0, 0) - 10800, Time.utc(year + 1, 4, 10, 1, 0, 0) - 7200, info)
2096
+ end
2097
+
2098
+ assert_period(:XDT, 7200, 3600, true, Time.utc(generate_up_to, 4, 10, 1, 0, 0) - 7200, Time.utc(generate_up_to, 10, 27, 2, 0, 0) - 10800, info)
2099
+ assert_period(:XST, 7200, 0, false, Time.utc(generate_up_to, 10, 27, 2, 0, 0) - 10800, nil, info)
2100
+ end
2101
+ end
2102
+ else
2103
+ def test_load_tz_string_generates_from_1970_if_last_transition_before_1970_and_does_not_support_negative
2104
+ offsets = [
2105
+ {:gmtoff => 7142, :isdst => false, :abbrev => 'LMT'},
2106
+ {:gmtoff => 7200, :isdst => false, :abbrev => 'XST'},
2107
+ {:gmtoff => 10800, :isdst => true, :abbrev => 'XDT'}
2108
+ ]
2109
+
2110
+ transitions = [
2111
+ {:at => -283903200 - 7142, :offset_index => 1}, # Time.utc(1961, 1, 2, 2, 0, 0)
2112
+ {:at => -243903600 - 7200, :offset_index => 2}, # Time.utc(1962, 4, 10, 1, 0, 0)
2113
+ {:at => -226620000 - 10800, :offset_index => 1} # Time.utc(1962, 10, 27, 2, 0, 0)
2114
+ ]
2115
+
2116
+ rules = AnnualRules.new(
2117
+ TimezoneOffset.new(7200, 0, 'XST'),
2118
+ TimezoneOffset.new(7200, 3600, 'XDT'),
2119
+ JulianDayOfYearTransitionRule.new(100, 3600),
2120
+ JulianDayOfYearTransitionRule.new(300, 7200)
2121
+ )
2122
+
2123
+ generate_up_to = ZoneinfoTimezoneInfo::GENERATE_UP_TO
2124
+
2125
+ tzif_test(offsets, transitions, :rules => rules) do |path, format|
2126
+ info = ZoneinfoTimezoneInfo.new('Pre/1970', path, @posix_tz_parser)
2127
+ assert_equal('Pre/1970', info.identifier)
2128
+
2129
+ assert_period(:LMT, 7142, 0, false, nil, Time.utc(1970, 1, 1), info)
2130
+ assert_period(:XST, 7200, 0, false, Time.utc(1970, 1, 1), Time.utc(1970, 4, 10, 1, 0, 0) - 7200, info)
2131
+
2132
+ 1970.upto(generate_up_to - 1).each do |year|
2133
+ assert_period(:XDT, 7200, 3600, true, Time.utc(year, 4, 10, 1, 0, 0) - 7200, Time.utc(year, 10, 27, 2, 0, 0) - 10800, info)
2134
+ assert_period(:XST, 7200, 0, false, Time.utc(year, 10, 27, 2, 0, 0) - 10800, Time.utc(year + 1, 4, 10, 1, 0, 0) - 7200, info)
2135
+ end
2136
+
2137
+ assert_period(:XDT, 7200, 3600, true, Time.utc(generate_up_to, 4, 10, 1, 0, 0) - 7200, Time.utc(generate_up_to, 10, 27, 2, 0, 0) - 10800, info)
2138
+ assert_period(:XST, 7200, 0, false, Time.utc(generate_up_to, 10, 27, 2, 0, 0) - 10800, nil, info)
2139
+ end
2140
+ end
2141
+ end
2142
+
2143
+ def test_load_tz_string_as_utf8
2144
+ offsets = [{:gmtoff => 3600, :isdst => false, :abbrev => 'áccént'}]
2145
+ rules = TimezoneOffset.new(3600, 0, 'áccént')
2146
+
2147
+ tzif_test(offsets, [], :tz_string => '<áccént>1', :rules => rules) do |path, format|
2148
+ # FakePosixTimeZoneParser will test that the tz_string matches.
2149
+ info = ZoneinfoTimezoneInfo.new('Test/Utf8', path, @posix_tz_parser)
2150
+ assert_period(:'áccént', 3600, 0, false, nil, nil, info)
2151
+ end
2152
+ end
1232
2153
  end