tzinfo 1.2.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (111) hide show
  1. checksums.yaml +7 -0
  2. checksums.yaml.gz.sig +2 -0
  3. data.tar.gz.sig +1 -0
  4. data/.yardopts +6 -0
  5. data/CHANGES.md +797 -0
  6. data/LICENSE +19 -0
  7. data/README.md +152 -0
  8. data/Rakefile +107 -0
  9. data/lib/tzinfo.rb +40 -0
  10. data/lib/tzinfo/country.rb +196 -0
  11. data/lib/tzinfo/country_index_definition.rb +31 -0
  12. data/lib/tzinfo/country_info.rb +42 -0
  13. data/lib/tzinfo/country_timezone.rb +135 -0
  14. data/lib/tzinfo/data_source.rb +190 -0
  15. data/lib/tzinfo/data_timezone.rb +58 -0
  16. data/lib/tzinfo/data_timezone_info.rb +55 -0
  17. data/lib/tzinfo/info_timezone.rb +30 -0
  18. data/lib/tzinfo/linked_timezone.rb +63 -0
  19. data/lib/tzinfo/linked_timezone_info.rb +26 -0
  20. data/lib/tzinfo/offset_rationals.rb +77 -0
  21. data/lib/tzinfo/ruby_core_support.rb +176 -0
  22. data/lib/tzinfo/ruby_country_info.rb +74 -0
  23. data/lib/tzinfo/ruby_data_source.rb +138 -0
  24. data/lib/tzinfo/time_or_datetime.rb +340 -0
  25. data/lib/tzinfo/timezone.rb +673 -0
  26. data/lib/tzinfo/timezone_definition.rb +36 -0
  27. data/lib/tzinfo/timezone_index_definition.rb +54 -0
  28. data/lib/tzinfo/timezone_info.rb +30 -0
  29. data/lib/tzinfo/timezone_offset.rb +101 -0
  30. data/lib/tzinfo/timezone_period.rb +245 -0
  31. data/lib/tzinfo/timezone_proxy.rb +105 -0
  32. data/lib/tzinfo/timezone_transition.rb +130 -0
  33. data/lib/tzinfo/timezone_transition_definition.rb +104 -0
  34. data/lib/tzinfo/transition_data_timezone_info.rb +274 -0
  35. data/lib/tzinfo/zoneinfo_country_info.rb +37 -0
  36. data/lib/tzinfo/zoneinfo_data_source.rb +496 -0
  37. data/lib/tzinfo/zoneinfo_timezone_info.rb +298 -0
  38. data/test/tc_country.rb +236 -0
  39. data/test/tc_country_index_definition.rb +69 -0
  40. data/test/tc_country_info.rb +16 -0
  41. data/test/tc_country_timezone.rb +173 -0
  42. data/test/tc_data_source.rb +218 -0
  43. data/test/tc_data_timezone.rb +99 -0
  44. data/test/tc_data_timezone_info.rb +18 -0
  45. data/test/tc_info_timezone.rb +34 -0
  46. data/test/tc_linked_timezone.rb +155 -0
  47. data/test/tc_linked_timezone_info.rb +23 -0
  48. data/test/tc_offset_rationals.rb +23 -0
  49. data/test/tc_ruby_core_support.rb +168 -0
  50. data/test/tc_ruby_country_info.rb +110 -0
  51. data/test/tc_ruby_data_source.rb +165 -0
  52. data/test/tc_time_or_datetime.rb +660 -0
  53. data/test/tc_timezone.rb +1359 -0
  54. data/test/tc_timezone_definition.rb +113 -0
  55. data/test/tc_timezone_index_definition.rb +73 -0
  56. data/test/tc_timezone_info.rb +11 -0
  57. data/test/tc_timezone_london.rb +143 -0
  58. data/test/tc_timezone_melbourne.rb +142 -0
  59. data/test/tc_timezone_new_york.rb +142 -0
  60. data/test/tc_timezone_offset.rb +126 -0
  61. data/test/tc_timezone_period.rb +555 -0
  62. data/test/tc_timezone_proxy.rb +136 -0
  63. data/test/tc_timezone_transition.rb +366 -0
  64. data/test/tc_timezone_transition_definition.rb +295 -0
  65. data/test/tc_timezone_utc.rb +27 -0
  66. data/test/tc_transition_data_timezone_info.rb +433 -0
  67. data/test/tc_zoneinfo_country_info.rb +78 -0
  68. data/test/tc_zoneinfo_data_source.rb +1204 -0
  69. data/test/tc_zoneinfo_timezone_info.rb +1234 -0
  70. data/test/test_utils.rb +188 -0
  71. data/test/ts_all.rb +7 -0
  72. data/test/ts_all_ruby.rb +5 -0
  73. data/test/ts_all_zoneinfo.rb +9 -0
  74. data/test/tzinfo-data/tzinfo/data.rb +8 -0
  75. data/test/tzinfo-data/tzinfo/data/definitions/America/Argentina/Buenos_Aires.rb +89 -0
  76. data/test/tzinfo-data/tzinfo/data/definitions/America/New_York.rb +315 -0
  77. data/test/tzinfo-data/tzinfo/data/definitions/Australia/Melbourne.rb +218 -0
  78. data/test/tzinfo-data/tzinfo/data/definitions/EST.rb +19 -0
  79. data/test/tzinfo-data/tzinfo/data/definitions/Etc/GMT__m__1.rb +21 -0
  80. data/test/tzinfo-data/tzinfo/data/definitions/Etc/GMT__p__1.rb +21 -0
  81. data/test/tzinfo-data/tzinfo/data/definitions/Etc/UTC.rb +21 -0
  82. data/test/tzinfo-data/tzinfo/data/definitions/Europe/Amsterdam.rb +261 -0
  83. data/test/tzinfo-data/tzinfo/data/definitions/Europe/Andorra.rb +186 -0
  84. data/test/tzinfo-data/tzinfo/data/definitions/Europe/London.rb +321 -0
  85. data/test/tzinfo-data/tzinfo/data/definitions/Europe/Paris.rb +265 -0
  86. data/test/tzinfo-data/tzinfo/data/definitions/Europe/Prague.rb +220 -0
  87. data/test/tzinfo-data/tzinfo/data/definitions/UTC.rb +16 -0
  88. data/test/tzinfo-data/tzinfo/data/indexes/countries.rb +927 -0
  89. data/test/tzinfo-data/tzinfo/data/indexes/timezones.rb +596 -0
  90. data/test/tzinfo-data/tzinfo/data/version.rb +14 -0
  91. data/test/zoneinfo/America/Argentina/Buenos_Aires +0 -0
  92. data/test/zoneinfo/America/New_York +0 -0
  93. data/test/zoneinfo/Australia/Melbourne +0 -0
  94. data/test/zoneinfo/EST +0 -0
  95. data/test/zoneinfo/Etc/UTC +0 -0
  96. data/test/zoneinfo/Europe/Amsterdam +0 -0
  97. data/test/zoneinfo/Europe/Andorra +0 -0
  98. data/test/zoneinfo/Europe/London +0 -0
  99. data/test/zoneinfo/Europe/Paris +0 -0
  100. data/test/zoneinfo/Europe/Prague +0 -0
  101. data/test/zoneinfo/Factory +0 -0
  102. data/test/zoneinfo/iso3166.tab +275 -0
  103. data/test/zoneinfo/leapseconds +61 -0
  104. data/test/zoneinfo/posix/Europe/London +0 -0
  105. data/test/zoneinfo/posixrules +0 -0
  106. data/test/zoneinfo/right/Europe/London +0 -0
  107. data/test/zoneinfo/zone.tab +439 -0
  108. data/test/zoneinfo/zone1970.tab +369 -0
  109. data/tzinfo.gemspec +21 -0
  110. metadata +191 -0
  111. metadata.gz.sig +0 -0
@@ -0,0 +1,1234 @@
1
+ # encoding: UTF-8
2
+
3
+ require File.join(File.expand_path(File.dirname(__FILE__)), 'test_utils')
4
+ require 'tempfile'
5
+
6
+ include TZInfo
7
+
8
+ using RubyCoreSupport::UntaintExt if RubyCoreSupport.const_defined?(:UntaintExt)
9
+
10
+ class TCZoneinfoTimezoneInfo < Minitest::Test
11
+
12
+ begin
13
+ Time.at(-2147483649)
14
+ Time.at(2147483648)
15
+ SUPPORTS_64BIT = true
16
+ rescue RangeError
17
+ SUPPORTS_64BIT = false
18
+ end
19
+
20
+ begin
21
+ Time.at(-1)
22
+ Time.at(-2147483648)
23
+ SUPPORTS_NEGATIVE = true
24
+ rescue ArgumentError
25
+ SUPPORTS_NEGATIVE = false
26
+ end
27
+
28
+ def assert_period(abbreviation, utc_offset, std_offset, dst, start_at, end_at, info)
29
+ if start_at
30
+ period = info.period_for_utc(start_at)
31
+ elsif end_at
32
+ period = info.period_for_utc(TimeOrDateTime.wrap(end_at).add_with_convert(-1).to_orig)
33
+ else
34
+ # no transitions, pick the epoch
35
+ period = info.period_for_utc(Time.utc(1970, 1, 1))
36
+ end
37
+
38
+ assert_equal(abbreviation, period.abbreviation)
39
+ assert_equal(utc_offset, period.utc_offset)
40
+ assert_equal(std_offset, period.std_offset)
41
+ assert_equal(dst, period.dst?)
42
+
43
+ if start_at
44
+ refute_nil(period.utc_start_time)
45
+ assert_equal(start_at, period.utc_start_time)
46
+ else
47
+ assert_nil(period.utc_start_time)
48
+ end
49
+
50
+ if end_at
51
+ refute_nil(period.utc_end_time)
52
+ assert_equal(end_at, period.utc_end_time)
53
+ else
54
+ assert_nil(period.utc_end_time)
55
+ end
56
+ end
57
+
58
+ def convert_times_to_i(items, key = :at)
59
+ items.each do |item|
60
+ if item[key].kind_of?(Time)
61
+ item[key] = item[key].utc.to_i
62
+ end
63
+ end
64
+ end
65
+
66
+ def select_with_32bit_values(items, key = :at)
67
+ items.select do |item|
68
+ i = item[key]
69
+ i >= -2147483648 && i <= 2147483647
70
+ end
71
+ end
72
+
73
+ def pack_int64_network_order(values)
74
+ values.collect {|value| [value >> 32, value & 0xFFFFFFFF]}.flatten.pack('NN' * values.length)
75
+ end
76
+
77
+ def pack_int64_signed_network_order(values)
78
+ # Convert to the equivalent 64-bit unsigned integer with the same bit representation
79
+ pack_int64_network_order(values.collect {|value| value < 0 ? value + 0x10000000000000000 : value})
80
+ end
81
+
82
+ def write_tzif(format, offsets, transitions, leaps = [], options = {})
83
+
84
+ # Options for testing malformed zoneinfo files.
85
+ magic = options[:magic]
86
+ section2_magic = options[:section2_magic]
87
+ abbrev_separator = options[:abbrev_separator] || "\0"
88
+ abbrev_offset_base = options[:abbrev_offset_base] || 0
89
+
90
+ unless magic
91
+ if format == 1
92
+ magic = "TZif\0"
93
+ elsif format >= 2
94
+ magic = "TZif#{format}"
95
+ else
96
+ raise ArgumentError, 'Invalid format specified'
97
+ end
98
+ end
99
+
100
+ if section2_magic.kind_of?(Proc)
101
+ section2_magic = section2_magic.call(format)
102
+ else
103
+ section2_magic = magic unless section2_magic
104
+ end
105
+
106
+ convert_times_to_i(transitions)
107
+ convert_times_to_i(leaps)
108
+
109
+ abbrevs = offsets.collect {|o| o[:abbrev]}.uniq
110
+
111
+ if abbrevs.length > 0
112
+ abbrevs = abbrevs.collect {|a| a.encode('UTF-8')} if abbrevs.first.respond_to?(:encode)
113
+
114
+ if abbrevs.first.respond_to?(:bytesize)
115
+ abbrevs_length = abbrevs.inject(0) {|sum, a| sum + a.bytesize + abbrev_separator.bytesize}
116
+ else
117
+ abbrevs_length = abbrevs.inject(0) {|sum, a| sum + a.length + abbrev_separator.length}
118
+ end
119
+ else
120
+ abbrevs_length = 0
121
+ end
122
+
123
+ b32_transitions = select_with_32bit_values(transitions)
124
+ b32_leaps = select_with_32bit_values(leaps)
125
+
126
+ Tempfile.open('tzinfo-test-zone') do |file|
127
+ file.binmode
128
+
129
+ file.write(
130
+ [magic, offsets.length, offsets.length, leaps.length,
131
+ b32_transitions.length, offsets.length, abbrevs_length].pack('a5 x15 NNNNNN'))
132
+
133
+ unless b32_transitions.empty?
134
+ file.write(b32_transitions.collect {|t| t[:at]}.pack('N' * b32_transitions.length))
135
+ file.write(b32_transitions.collect {|t| t[:offset_index]}.pack('C' * b32_transitions.length))
136
+ end
137
+
138
+ offsets.each do |offset|
139
+ index = abbrevs.index(offset[:abbrev])
140
+ abbrev_offset = abbrev_offset_base
141
+ 0.upto(index - 1) {|i| abbrev_offset += abbrevs[i].length + 1}
142
+
143
+ file.write([offset[:gmtoff], offset[:isdst] ? 1 : 0, abbrev_offset].pack('NCC'))
144
+ end
145
+
146
+ abbrevs.each do |a|
147
+ file.write(a)
148
+ file.write(abbrev_separator)
149
+ end
150
+
151
+ b32_leaps.each do |leap|
152
+ file.write([leap[:at], leap[:seconds]].pack('NN'))
153
+ end
154
+
155
+ unless offsets.empty?
156
+ file.write("\0" * offsets.length * 2)
157
+ end
158
+
159
+ if format >= 2
160
+ file.write(
161
+ [section2_magic, offsets.length, offsets.length, leaps.length,
162
+ transitions.length, offsets.length, abbrevs_length].pack('a5 x15 NNNNNN'))
163
+
164
+ unless transitions.empty?
165
+ file.write(pack_int64_signed_network_order(transitions.collect {|t| t[:at]}))
166
+ file.write(transitions.collect {|t| t[:offset_index]}.pack('C' * transitions.length))
167
+ end
168
+
169
+ offsets.each do |offset|
170
+ index = abbrevs.index(offset[:abbrev])
171
+ abbrev_offset = abbrev_offset_base
172
+ 0.upto(index - 1) {|i| abbrev_offset += abbrevs[i].length + 1}
173
+
174
+ file.write([offset[:gmtoff], offset[:isdst] ? 1 : 0, abbrev_offset].pack('NCC'))
175
+ end
176
+
177
+ abbrevs.each do |a|
178
+ file.write(a)
179
+ file.write(abbrev_separator)
180
+ end
181
+
182
+ leaps.each do |leap|
183
+ file.write(pack_int64_signed_network_order([leap[:at]]))
184
+ file.write([leap[:seconds]].pack('N'))
185
+ end
186
+
187
+ unless offsets.empty?
188
+ file.write("\0" * offsets.length * 2)
189
+ end
190
+
191
+ # Empty POSIX timezone string
192
+ file.write("\n\n")
193
+ end
194
+
195
+ file.flush
196
+
197
+ yield file.path, format
198
+ end
199
+ end
200
+
201
+ def tzif_test(offsets, transitions, leaps = [], options = {}, &block)
202
+ min_format = options[:min_format] || 1
203
+
204
+ min_format.upto(3) do |format|
205
+ write_tzif(format, offsets, transitions, leaps, options, &block)
206
+ end
207
+ end
208
+
209
+ def test_load
210
+ offsets = [
211
+ {:gmtoff => 3542, :isdst => false, :abbrev => 'LMT'},
212
+ {:gmtoff => 3600, :isdst => false, :abbrev => 'XST'},
213
+ {:gmtoff => 7200, :isdst => true, :abbrev => 'XDT'},
214
+ {:gmtoff => 0, :isdst => false, :abbrev => 'XNST'}]
215
+
216
+ transitions = [
217
+ {:at => Time.utc(1971, 1, 2), :offset_index => 1},
218
+ {:at => Time.utc(1980, 4, 22), :offset_index => 2},
219
+ {:at => Time.utc(1980, 10, 21), :offset_index => 1},
220
+ {:at => Time.utc(2000, 12, 31), :offset_index => 3}]
221
+
222
+ tzif_test(offsets, transitions) do |path, format|
223
+ info = ZoneinfoTimezoneInfo.new('Zone/One', path)
224
+ assert_equal('Zone/One', info.identifier)
225
+
226
+ assert_period(:LMT, 3542, 0, false, nil, Time.utc(1971, 1, 2), info)
227
+ assert_period(:XST, 3600, 0, false, Time.utc(1971, 1, 2), Time.utc(1980, 4, 22), info)
228
+ assert_period(:XDT, 3600, 3600, true, Time.utc(1980, 4, 22), Time.utc(1980, 10, 21), info)
229
+ assert_period(:XST, 3600, 0, false, Time.utc(1980, 10, 21), Time.utc(2000, 12, 31), info)
230
+ assert_period(:XNST, 0, 0, false, Time.utc(2000, 12, 31), nil, info)
231
+ end
232
+ end
233
+
234
+ def test_load_negative_utc_offset
235
+ offsets = [
236
+ {:gmtoff => -12492, :isdst => false, :abbrev => 'LMT'},
237
+ {:gmtoff => -12000, :isdst => false, :abbrev => 'XST'},
238
+ {:gmtoff => -8400, :isdst => true, :abbrev => 'XDT'},
239
+ {:gmtoff => -8400, :isdst => false, :abbrev => 'XNST'}]
240
+
241
+ transitions = [
242
+ {:at => Time.utc(1971, 7, 9, 3, 0, 0), :offset_index => 1},
243
+ {:at => Time.utc(1972, 10, 12, 3, 0, 0), :offset_index => 2},
244
+ {:at => Time.utc(1973, 4, 29, 3, 0, 0), :offset_index => 1},
245
+ {:at => Time.utc(1992, 4, 1, 4, 30, 0), :offset_index => 3}]
246
+
247
+ tzif_test(offsets, transitions) do |path, format|
248
+ info = ZoneinfoTimezoneInfo.new('Zone/One', path)
249
+ assert_equal('Zone/One', info.identifier)
250
+
251
+ assert_period(:LMT, -12492, 0, false, nil, Time.utc(1971, 7, 9, 3, 0, 0), info)
252
+ assert_period(:XST, -12000, 0, false, Time.utc(1971, 7, 9, 3, 0, 0), Time.utc(1972, 10, 12, 3, 0, 0), info)
253
+ assert_period(:XDT, -12000, 3600, true, Time.utc(1972, 10, 12, 3, 0, 0), Time.utc(1973, 4, 29, 3, 0, 0), info)
254
+ assert_period(:XST, -12000, 0, false, Time.utc(1973, 4, 29, 3, 0, 0), Time.utc(1992, 4, 1, 4, 30, 0), info)
255
+ assert_period(:XNST, -8400, 0, false, Time.utc(1992, 4, 1, 4, 30, 0), nil, info)
256
+ end
257
+ end
258
+
259
+ def test_load_dst_first
260
+ offsets = [
261
+ {:gmtoff => 7200, :isdst => true, :abbrev => 'XDT'},
262
+ {:gmtoff => 3542, :isdst => false, :abbrev => 'LMT'},
263
+ {:gmtoff => 3600, :isdst => false, :abbrev => 'XST'},
264
+ {:gmtoff => 0, :isdst => false, :abbrev => 'XNST'}]
265
+
266
+ transitions = [
267
+ {:at => Time.utc(1979, 1, 2), :offset_index => 2},
268
+ {:at => Time.utc(1980, 4, 22), :offset_index => 0},
269
+ {:at => Time.utc(1980, 10, 21), :offset_index => 2},
270
+ {:at => Time.utc(2000, 12, 31), :offset_index => 3}]
271
+
272
+ tzif_test(offsets, transitions) do |path, format|
273
+ info = ZoneinfoTimezoneInfo.new('Zone/Two', path)
274
+ assert_equal('Zone/Two', info.identifier)
275
+
276
+ assert_period(:LMT, 3542, 0, false, nil, Time.utc(1979, 1, 2), info)
277
+ end
278
+ end
279
+
280
+ def test_load_no_transitions
281
+ offsets = [{:gmtoff => -12094, :isdst => false, :abbrev => 'LT'}]
282
+
283
+ tzif_test(offsets, []) do |path, format|
284
+ info = ZoneinfoTimezoneInfo.new('Zone/three', path)
285
+ assert_equal('Zone/three', info.identifier)
286
+
287
+ assert_period(:LT, -12094, 0, false, nil, nil, info)
288
+ end
289
+ end
290
+
291
+ def test_load_no_offsets
292
+ offsets = []
293
+ transitions = [{:at => Time.utc(2000, 12, 31), :offset_index => 0}]
294
+
295
+ tzif_test(offsets, transitions) do |path, format|
296
+ assert_raises(InvalidZoneinfoFile) do
297
+ ZoneinfoTimezoneInfo.new('Zone', path)
298
+ end
299
+ end
300
+ end
301
+
302
+ def test_load_invalid_offset_index
303
+ offsets = [{:gmtoff => -0, :isdst => false, :abbrev => 'LMT'}]
304
+ transitions = [{:at => Time.utc(2000, 12, 31), :offset_index => 2}]
305
+
306
+ tzif_test(offsets, transitions) do |path, format|
307
+ assert_raises(InvalidZoneinfoFile) do
308
+ ZoneinfoTimezoneInfo.new('Zone', path)
309
+ end
310
+ end
311
+ end
312
+
313
+ def test_load_with_leap_seconds
314
+ offsets = [{:gmtoff => -0, :isdst => false, :abbrev => 'LMT'}]
315
+ leaps = [{:at => Time.utc(1972,6,30,23,59,60), :seconds => 1}]
316
+
317
+ tzif_test(offsets, [], leaps) do |path, format|
318
+ assert_raises(InvalidZoneinfoFile) do
319
+ ZoneinfoTimezoneInfo.new('Zone', path)
320
+ end
321
+ end
322
+ end
323
+
324
+ def test_load_invalid_magic
325
+ ['TZif4', 'tzif2', '12345'].each do |magic|
326
+ offsets = [{:gmtoff => -12094, :isdst => false, :abbrev => 'LT'}]
327
+
328
+ tzif_test(offsets, [], [], :magic => magic) do |path, format|
329
+ assert_raises(InvalidZoneinfoFile) do
330
+ ZoneinfoTimezoneInfo.new('Zone2', path)
331
+ end
332
+ end
333
+ end
334
+ end
335
+
336
+ # These tests can only be run if the platform supports 64-bit Times. When
337
+ # 64-bit support is unavailable, the second section will not be read, so no
338
+ # error will be raised.
339
+ if SUPPORTS_64BIT
340
+ def test_load_invalid_section2_magic
341
+ ['TZif4', 'tzif2', '12345'].each do |section2_magic|
342
+ offsets = [{:gmtoff => -12094, :isdst => false, :abbrev => 'LT'}]
343
+
344
+ tzif_test(offsets, [], [], :min_format => 2, :section2_magic => section2_magic) do |path, format|
345
+ assert_raises(InvalidZoneinfoFile) do
346
+ ZoneinfoTimezoneInfo.new('Zone4', path)
347
+ end
348
+ end
349
+ end
350
+ end
351
+
352
+ def test_load_mismatched_section2_magic
353
+ minus_one = Proc.new {|f| f == 2 ? "TZif\0" : "TZif#{f - 1}" }
354
+ plus_one = Proc.new {|f| "TZif#{f + 1}" }
355
+
356
+ [minus_one, plus_one].each do |section2_magic|
357
+ offsets = [{:gmtoff => -12094, :isdst => false, :abbrev => 'LT'}]
358
+
359
+ tzif_test(offsets, [], [], :min_format => 2, :section2_magic => section2_magic) do |path, format|
360
+ assert_raises(InvalidZoneinfoFile) do
361
+ ZoneinfoTimezoneInfo.new('Zone5', path)
362
+ end
363
+ end
364
+ end
365
+ end
366
+ end
367
+
368
+ def test_load_invalid_format
369
+ Tempfile.open('tzinfo-test-zone') do |file|
370
+ file.write('Invalid')
371
+ file.flush
372
+
373
+ assert_raises(InvalidZoneinfoFile) do
374
+ ZoneinfoTimezoneInfo.new('Zone3', file.path)
375
+ end
376
+ end
377
+ end
378
+
379
+ def test_load_missing_abbrev_null_termination
380
+ offsets = [
381
+ {:gmtoff => 3542, :isdst => false, :abbrev => 'LMT'},
382
+ {:gmtoff => 3600, :isdst => false, :abbrev => 'XST'}]
383
+
384
+ transitions = [
385
+ {:at => Time.utc(2000, 1, 1), :offset_index => 1}]
386
+
387
+ tzif_test(offsets, transitions, [], :abbrev_separator => '^') do |path, format|
388
+ assert_raises(InvalidZoneinfoFile) do
389
+ ZoneinfoTimezoneInfo.new('Zone', path)
390
+ end
391
+ end
392
+ end
393
+
394
+ def test_load_out_of_range_abbrev_offsets
395
+ offsets = [
396
+ {:gmtoff => 3542, :isdst => false, :abbrev => 'LMT'},
397
+ {:gmtoff => 3600, :isdst => false, :abbrev => 'XST'}]
398
+
399
+ transitions = [
400
+ {:at => Time.utc(2000, 1, 1), :offset_index => 1}]
401
+
402
+ tzif_test(offsets, transitions, [], :abbrev_offset_base => 8) do |path, format|
403
+ assert_raises(InvalidZoneinfoFile) do
404
+ ZoneinfoTimezoneInfo.new('Zone', path)
405
+ end
406
+ end
407
+ end
408
+
409
+ def test_load_before_epoch
410
+ # Some platforms don't support negative timestamps for times before the
411
+ # epoch. Check that they are returned when supported and skipped when not.
412
+
413
+ # Note the last transition before the epoch (and within the 32-bit range) is
414
+ # moved to the epoch on platforms that do not support negative timestamps.
415
+
416
+ offsets = [
417
+ {:gmtoff => 3542, :isdst => false, :abbrev => 'LMT'},
418
+ {:gmtoff => 3600, :isdst => false, :abbrev => 'XST'},
419
+ {:gmtoff => 7200, :isdst => true, :abbrev => 'XDT'},
420
+ {:gmtoff => 0, :isdst => false, :abbrev => 'XNST'}]
421
+
422
+ transitions = [
423
+ {:at => -694224000, :offset_index => 1}, # Time.utc(1948, 1, 2)
424
+ {:at => -21945600, :offset_index => 2}, # Time.utc(1969, 4, 22)
425
+ {:at => Time.utc(1970, 10, 21), :offset_index => 1},
426
+ {:at => Time.utc(2000, 12, 31), :offset_index => 3}]
427
+
428
+ tzif_test(offsets, transitions) do |path, format|
429
+ info = ZoneinfoTimezoneInfo.new('Zone/Negative', path)
430
+ assert_equal('Zone/Negative', info.identifier)
431
+
432
+ if SUPPORTS_NEGATIVE
433
+ assert_period(:LMT, 3542, 0, false, nil, Time.utc(1948, 1, 2), info)
434
+ assert_period(:XST, 3600, 0, false, Time.utc(1948, 1, 2), Time.utc(1969, 4, 22), info)
435
+ assert_period(:XDT, 3600, 3600, true, Time.utc(1969, 4, 22), Time.utc(1970, 10, 21), info)
436
+ else
437
+ assert_period(:LMT, 3542, 0, false, nil, Time.utc(1970, 1, 1), info)
438
+ assert_period(:XDT, 3600, 3600, true, Time.utc(1970, 1, 1), Time.utc(1970, 10, 21), info)
439
+ end
440
+
441
+ assert_period(:XST, 3600, 0, false, Time.utc(1970, 10, 21), Time.utc(2000, 12, 31), info)
442
+ assert_period(:XNST, 0, 0, false, Time.utc(2000, 12, 31), nil, info)
443
+ end
444
+ end
445
+
446
+ def test_load_on_epoch
447
+ offsets = [
448
+ {:gmtoff => 3542, :isdst => false, :abbrev => 'LMT'},
449
+ {:gmtoff => 3600, :isdst => false, :abbrev => 'XST'},
450
+ {:gmtoff => 7200, :isdst => true, :abbrev => 'XDT'},
451
+ {:gmtoff => 0, :isdst => false, :abbrev => 'XNST'}]
452
+
453
+ transitions = [
454
+ {:at => -694224000, :offset_index => 1}, # Time.utc(1948, 1, 2)
455
+ {:at => -21945600, :offset_index => 2}, # Time.utc(1969, 4, 22)
456
+ {:at => Time.utc(1970, 1, 1), :offset_index => 1},
457
+ {:at => Time.utc(2000, 12, 31), :offset_index => 3}]
458
+
459
+ tzif_test(offsets, transitions) do |path, format|
460
+ info = ZoneinfoTimezoneInfo.new('Zone/Negative', path)
461
+ assert_equal('Zone/Negative', info.identifier)
462
+
463
+ if SUPPORTS_NEGATIVE
464
+ assert_period(:LMT, 3542, 0, false, nil, Time.utc(1948, 1, 2), info)
465
+ assert_period(:XST, 3600, 0, false, Time.utc(1948, 1, 2), Time.utc(1969, 4, 22), info)
466
+ assert_period(:XDT, 3600, 3600, true, Time.utc(1969, 4, 22), Time.utc(1970, 1, 1), info)
467
+ else
468
+ assert_period(:LMT, 3542, 0, false, nil, Time.utc(1970, 1, 1), info)
469
+ end
470
+
471
+ assert_period(:XST, 3600, 0, false, Time.utc(1970, 1, 1), Time.utc(2000, 12, 31), info)
472
+ assert_period(:XNST, 0, 0, false, Time.utc(2000, 12, 31), nil, info)
473
+ end
474
+ end
475
+
476
+ def test_load_64bit
477
+ # Some platforms support 64-bit Times, others only 32-bit. The TZif version
478
+ # 2 and later format contains both 32-bit and 64-bit times.
479
+
480
+ # Where 64-bit is supported and a TZif 2 or later file is provided, the
481
+ # 64-bit times should be used, otherwise the 32-bit information should be
482
+ # used.
483
+
484
+ offsets = [
485
+ {:gmtoff => 3542, :isdst => false, :abbrev => 'LMT'},
486
+ {:gmtoff => 3600, :isdst => false, :abbrev => 'XST'},
487
+ {:gmtoff => 7200, :isdst => true, :abbrev => 'XDT'},
488
+ {:gmtoff => 0, :isdst => false, :abbrev => 'XNST'}]
489
+
490
+ transitions = [
491
+ {:at => -3786739200, :offset_index => 1}, # Time.utc(1850, 1, 2)
492
+ {:at => Time.utc(2003, 4, 22), :offset_index => 2},
493
+ {:at => Time.utc(2003, 10, 21), :offset_index => 1},
494
+ {:at => 2240524800, :offset_index => 3}] # Time.utc(2040, 12, 31)
495
+
496
+ tzif_test(offsets, transitions) do |path, format|
497
+ info = ZoneinfoTimezoneInfo.new('Zone/SixtyFour', path)
498
+ assert_equal('Zone/SixtyFour', info.identifier)
499
+
500
+ if SUPPORTS_64BIT && format >= 2
501
+ assert_period(:LMT, 3542, 0, false, nil, Time.utc(1850, 1, 2), info)
502
+ assert_period(:XST, 3600, 0, false, Time.utc(1850, 1, 2), Time.utc(2003, 4, 22), info)
503
+ assert_period(:XDT, 3600, 3600, true, Time.utc(2003, 4, 22), Time.utc(2003, 10, 21), info)
504
+ assert_period(:XST, 3600, 0, false, Time.utc(2003, 10, 21), Time.utc(2040, 12, 31), info)
505
+ assert_period(:XNST, 0, 0, false, Time.utc(2040, 12, 31), nil, info)
506
+ else
507
+ assert_period(:LMT, 3542, 0, false, nil, Time.utc(2003, 4, 22), info)
508
+ assert_period(:XDT, 3600, 3600, true, Time.utc(2003, 4, 22), Time.utc(2003, 10, 21), info)
509
+ assert_period(:XST, 3600, 0, false, Time.utc(2003, 10, 21), nil, info)
510
+ end
511
+ end
512
+ end
513
+
514
+ def test_load_64bit_range
515
+ # The full range of 64 bit timestamps is not currently supported because of
516
+ # the way transitions are indexed. Transitions outside the supported range
517
+ # will be ignored.
518
+
519
+ offsets = [
520
+ {:gmtoff => 3542, :isdst => false, :abbrev => 'LMT'},
521
+ {:gmtoff => 3600, :isdst => false, :abbrev => 'XST'},
522
+ {:gmtoff => 7200, :isdst => false, :abbrev => 'XNST'}]
523
+
524
+ transitions = [
525
+ {:at => -2**63, :offset_index => 1},
526
+ {:at => Time.utc(2014, 5, 27), :offset_index => 2},
527
+ {:at => 2**63 - 1, :offset_index => 0}]
528
+
529
+ tzif_test(offsets, transitions) do |path, format|
530
+ info = ZoneinfoTimezoneInfo.new('Zone/SixtyFourRange', path)
531
+ assert_equal('Zone/SixtyFourRange', info.identifier)
532
+
533
+ if SUPPORTS_64BIT && format >= 2
534
+ # When the full range is supported, the following periods will be defined:
535
+ #assert_period(:LMT, 3542, 0, false, nil, Time.at(-2**63).utc, info)
536
+ #assert_period(:XST, 3600, 0, false, Time.at(-2**63).utc, Time.utc(2014, 5, 27), info)
537
+ #assert_period(:XNST, 7200, 0, false, Time.utc(2014, 5, 27), Time.at(2**63 - 1).utc, info)
538
+ #assert_period(:LMT, 3542, 0, false, Time.at(2**63 - 1).utc, nil, info)
539
+
540
+ # Without full range support, the following periods will be defined:
541
+ assert_period(:LMT, 3542, 0, false, nil, Time.utc(2014, 5, 27), info)
542
+ assert_period(:XNST, 7200, 0, false, Time.utc(2014, 5, 27), nil, info)
543
+ else
544
+ assert_period(:LMT, 3542, 0, false, nil, Time.utc(2014, 5, 27), info)
545
+ assert_period(:XNST, 7200, 0, false, Time.utc(2014, 5, 27), nil, info)
546
+ end
547
+ end
548
+ end
549
+
550
+ def test_load_supported_64bit_range
551
+ # The full range of 64 bit timestamps is not currently supported because of
552
+ # the way transitions are indexed. Transitions outside the supported range
553
+ # will be ignored.
554
+
555
+ min_timestamp = -8520336000 # Time.utc(1700, 1, 1).to_i
556
+ max_timestamp = 16725225600 # Time.utc(2500, 1, 1).to_i
557
+
558
+ offsets = [
559
+ {:gmtoff => 3542, :isdst => false, :abbrev => 'LMT'},
560
+ {:gmtoff => 3600, :isdst => false, :abbrev => 'XST'},
561
+ {:gmtoff => 7200, :isdst => false, :abbrev => 'XNST'}]
562
+
563
+ transitions = [
564
+ {:at => min_timestamp, :offset_index => 1},
565
+ {:at => Time.utc(2014, 5, 27), :offset_index => 2},
566
+ {:at => max_timestamp - 1, :offset_index => 0}]
567
+
568
+ tzif_test(offsets, transitions) do |path, format|
569
+ info = ZoneinfoTimezoneInfo.new('Zone/SupportedSixtyFourRange', path)
570
+ assert_equal('Zone/SupportedSixtyFourRange', info.identifier)
571
+
572
+ if SUPPORTS_64BIT && format >= 2
573
+ assert_period(:LMT, 3542, 0, false, nil, Time.at(min_timestamp).utc, info)
574
+ assert_period(:XST, 3600, 0, false, Time.at(min_timestamp).utc, Time.utc(2014, 5, 27), info)
575
+ assert_period(:XNST, 7200, 0, false, Time.utc(2014, 5, 27), Time.at(max_timestamp - 1).utc, info)
576
+ assert_period(:LMT, 3542, 0, false, Time.at(max_timestamp - 1).utc, nil, info)
577
+ else
578
+ assert_period(:LMT, 3542, 0, false, nil, Time.utc(2014, 5, 27), info)
579
+ assert_period(:XNST, 7200, 0, false, Time.utc(2014, 5, 27), nil, info)
580
+ end
581
+ end
582
+ end
583
+
584
+ def test_load_32bit_range
585
+ offsets = [
586
+ {:gmtoff => 3542, :isdst => false, :abbrev => 'LMT'},
587
+ {:gmtoff => 3600, :isdst => false, :abbrev => 'XST'},
588
+ {:gmtoff => 7200, :isdst => false, :abbrev => 'XNST'}]
589
+
590
+ transitions = [
591
+ {:at => -2**31, :offset_index => 1},
592
+ {:at => Time.utc(2014, 5, 27), :offset_index => 2},
593
+ {:at => 2**31 - 1, :offset_index => 0}]
594
+
595
+ tzif_test(offsets, transitions) do |path, format|
596
+ info = ZoneinfoTimezoneInfo.new('Zone/ThirtyTwoRange', path)
597
+ assert_equal('Zone/ThirtyTwoRange', info.identifier)
598
+
599
+ if SUPPORTS_NEGATIVE
600
+ assert_period(:LMT, 3542, 0, false, nil, Time.at(-2**31).utc, info)
601
+ assert_period(:XST, 3600, 0, false, Time.at(-2**31).utc, Time.utc(2014, 5, 27), info)
602
+ assert_period(:XNST, 7200, 0, false, Time.utc(2014, 5, 27), Time.at(2**31 - 1).utc, info)
603
+ assert_period(:LMT, 3542, 0, false, Time.at(2**31 - 1).utc, nil, info)
604
+ else
605
+ assert_period(:XST, 3600, 0, false, Time.utc(1970, 1, 1), Time.utc(2014, 5, 27), info)
606
+ assert_period(:XNST, 7200, 0, false, Time.utc(2014, 5, 27), Time.at(2**31 - 1).utc, info)
607
+ assert_period(:LMT, 3542, 0, false, Time.at(2**31 - 1).utc, nil, info)
608
+ end
609
+ end
610
+ end
611
+
612
+ def test_load_std_offset_changes
613
+ # The zoneinfo files don't include the offset from standard time, so this
614
+ # has to be derived by looking at changes in the total UTC offset.
615
+
616
+ offsets = [
617
+ {:gmtoff => 3542, :isdst => false, :abbrev => 'LMT'},
618
+ {:gmtoff => 3600, :isdst => false, :abbrev => 'XST'},
619
+ {:gmtoff => 7200, :isdst => true, :abbrev => 'XDT'},
620
+ {:gmtoff => 10800, :isdst => true, :abbrev => 'XDDT'}]
621
+
622
+ transitions = [
623
+ {:at => Time.utc(2000, 1, 1), :offset_index => 1},
624
+ {:at => Time.utc(2000, 2, 1), :offset_index => 2},
625
+ {:at => Time.utc(2000, 3, 1), :offset_index => 3},
626
+ {:at => Time.utc(2000, 4, 1), :offset_index => 1}]
627
+
628
+ tzif_test(offsets, transitions) do |path, format|
629
+ info = ZoneinfoTimezoneInfo.new('Zone/DoubleDaylight', path)
630
+ assert_equal('Zone/DoubleDaylight', info.identifier)
631
+
632
+ assert_period(:LMT, 3542, 0, false, nil, Time.utc(2000, 1, 1), info)
633
+ assert_period(:XST, 3600, 0, false, Time.utc(2000, 1, 1), Time.utc(2000, 2, 1), info)
634
+ assert_period(:XDT, 3600, 3600, true, Time.utc(2000, 2, 1), Time.utc(2000, 3, 1), info)
635
+ assert_period(:XDDT, 3600, 7200, true, Time.utc(2000, 3, 1), Time.utc(2000, 4, 1), info)
636
+ assert_period(:XST, 3600, 0, false, Time.utc(2000, 4, 1), nil, info)
637
+ end
638
+ end
639
+
640
+ def test_load_std_offset_changes_jump_to_double_dst
641
+ # The zoneinfo files don't include the offset from standard time, so this
642
+ # has to be derived by looking at changes in the total UTC offset.
643
+
644
+ offsets = [
645
+ {:gmtoff => 3542, :isdst => false, :abbrev => 'LMT'},
646
+ {:gmtoff => 3600, :isdst => false, :abbrev => 'XST'},
647
+ {:gmtoff => 10800, :isdst => true, :abbrev => 'XDDT'}]
648
+
649
+ transitions = [
650
+ {:at => Time.utc(2000, 4, 1), :offset_index => 1},
651
+ {:at => Time.utc(2000, 5, 1), :offset_index => 2},
652
+ {:at => Time.utc(2000, 6, 1), :offset_index => 1}]
653
+
654
+ tzif_test(offsets, transitions) do |path, format|
655
+ info = ZoneinfoTimezoneInfo.new('Zone/DoubleDaylight', path)
656
+ assert_equal('Zone/DoubleDaylight', info.identifier)
657
+
658
+ assert_period(:LMT, 3542, 0, false, nil, Time.utc(2000, 4, 1), info)
659
+ assert_period(:XST, 3600, 0, false, Time.utc(2000, 4, 1), Time.utc(2000, 5, 1), info)
660
+ assert_period(:XDDT, 3600, 7200, true, Time.utc(2000, 5, 1), Time.utc(2000, 6, 1), info)
661
+ assert_period(:XST, 3600, 0, false, Time.utc(2000, 6, 1), nil, info)
662
+ end
663
+ end
664
+
665
+ def test_load_std_offset_changes_negative
666
+ # The zoneinfo files don't include the offset from standard time, so this
667
+ # has to be derived by looking at changes in the total UTC offset.
668
+
669
+ offsets = [
670
+ {:gmtoff => -10821, :isdst => false, :abbrev => 'LMT'},
671
+ {:gmtoff => -10800, :isdst => false, :abbrev => 'XST'},
672
+ {:gmtoff => -7200, :isdst => true, :abbrev => 'XDT'},
673
+ {:gmtoff => -3600, :isdst => true, :abbrev => 'XDDT'}]
674
+
675
+ transitions = [
676
+ {:at => Time.utc(2000, 1, 1), :offset_index => 1},
677
+ {:at => Time.utc(2000, 2, 1), :offset_index => 2},
678
+ {:at => Time.utc(2000, 3, 1), :offset_index => 3},
679
+ {:at => Time.utc(2000, 4, 1), :offset_index => 1},
680
+ {:at => Time.utc(2000, 5, 1), :offset_index => 3},
681
+ {:at => Time.utc(2000, 6, 1), :offset_index => 1}]
682
+
683
+ tzif_test(offsets, transitions) do |path, format|
684
+ info = ZoneinfoTimezoneInfo.new('Zone/DoubleDaylight', path)
685
+ assert_equal('Zone/DoubleDaylight', info.identifier)
686
+
687
+ assert_period(:LMT, -10821, 0, false, nil, Time.utc(2000, 1, 1), info)
688
+ assert_period(:XST, -10800, 0, false, Time.utc(2000, 1, 1), Time.utc(2000, 2, 1), info)
689
+ assert_period(:XDT, -10800, 3600, true, Time.utc(2000, 2, 1), Time.utc(2000, 3, 1), info)
690
+ assert_period(:XDDT, -10800, 7200, true, Time.utc(2000, 3, 1), Time.utc(2000, 4, 1), info)
691
+ assert_period(:XST, -10800, 0, false, Time.utc(2000, 4, 1), Time.utc(2000, 5, 1), info)
692
+ assert_period(:XDDT, -10800, 7200, true, Time.utc(2000, 5, 1), Time.utc(2000, 6, 1), info)
693
+ assert_period(:XST, -10800, 0, false, Time.utc(2000, 6, 1), nil, info)
694
+ end
695
+ end
696
+
697
+ def test_load_starts_two_hour_std_offset
698
+ # The zoneinfo files don't include the offset from standard time, so this
699
+ # has to be derived by looking at changes in the total UTC offset.
700
+
701
+ offsets = [
702
+ {:gmtoff => 3542, :isdst => false, :abbrev => 'LMT'},
703
+ {:gmtoff => 3600, :isdst => false, :abbrev => 'XST'},
704
+ {:gmtoff => 7200, :isdst => true, :abbrev => 'XDT'},
705
+ {:gmtoff => 10800, :isdst => true, :abbrev => 'XDDT'}]
706
+
707
+ transitions = [
708
+ {:at => Time.utc(2000, 1, 1), :offset_index => 3},
709
+ {:at => Time.utc(2000, 2, 1), :offset_index => 2},
710
+ {:at => Time.utc(2000, 3, 1), :offset_index => 1}]
711
+
712
+ tzif_test(offsets, transitions) do |path, format|
713
+ info = ZoneinfoTimezoneInfo.new('Zone/DoubleDaylight', path)
714
+ assert_equal('Zone/DoubleDaylight', info.identifier)
715
+
716
+ assert_period(:LMT, 3542, 0, false, nil, Time.utc(2000, 1, 1), info)
717
+ assert_period(:XDDT, 3600, 7200, true, Time.utc(2000, 1, 1), Time.utc(2000, 2, 1), info)
718
+ assert_period(:XDT, 3600, 3600, true, Time.utc(2000, 2, 1), Time.utc(2000, 3, 1), info)
719
+ assert_period(:XST, 3600, 0, false, Time.utc(2000, 3, 1), nil, info)
720
+ end
721
+ end
722
+
723
+ def test_load_starts_only_dst_transition_with_lmt
724
+ # The zoneinfo files don't include the offset from standard time, so this
725
+ # has to be derived by looking at changes in the total UTC offset.
726
+
727
+ offsets = [
728
+ {:gmtoff => 3542, :isdst => false, :abbrev => 'LMT'},
729
+ {:gmtoff => 7200, :isdst => true, :abbrev => 'XDT'}]
730
+
731
+ transitions = [{:at => Time.utc(2000, 1, 1), :offset_index => 1}]
732
+
733
+ tzif_test(offsets, transitions) do |path, format|
734
+ info = ZoneinfoTimezoneInfo.new('Zone/OnlyDST', path)
735
+ assert_equal('Zone/OnlyDST', info.identifier)
736
+
737
+ assert_period(:LMT, 3542, 0, false, nil, Time.utc(2000, 1, 1), info)
738
+ assert_period(:XDT, 3542, 3658, true, Time.utc(2000, 1, 1), nil, info)
739
+ end
740
+ end
741
+
742
+ def test_load_starts_only_dst_transition_without_lmt
743
+ # The zoneinfo files don't include the offset from standard time, so this
744
+ # has to be derived by looking at changes in the total UTC offset.
745
+
746
+ offsets = [{:gmtoff => 7200, :isdst => true, :abbrev => 'XDT'}]
747
+
748
+ transitions = [{:at => Time.utc(2000, 1, 1), :offset_index => 0}]
749
+
750
+ tzif_test(offsets, transitions) do |path, format|
751
+ info = ZoneinfoTimezoneInfo.new('Zone/OnlyDST', path)
752
+ assert_equal('Zone/OnlyDST', info.identifier)
753
+
754
+ assert_period(:XDT, 3600, 3600, true, nil, Time.utc(2000, 1, 1), info)
755
+ assert_period(:XDT, 3600, 3600, true, Time.utc(2000, 1, 1), nil, info)
756
+ end
757
+ end
758
+
759
+ def test_load_switch_to_dst_and_change_utc_offset
760
+ # The zoneinfo files don't include the offset from standard time, so this
761
+ # has to be derived by looking at changes in the total UTC offset.
762
+
763
+ # Switch from non-DST to DST at the same time as moving the UTC offset
764
+ # back an hour (i.e. wall clock time doesn't change).
765
+
766
+ offsets = [
767
+ {:gmtoff => 3542, :isdst => false, :abbrev => 'LMT'},
768
+ {:gmtoff => 3600, :isdst => false, :abbrev => 'YST'},
769
+ {:gmtoff => 3600, :isdst => true, :abbrev => 'XDT'}]
770
+
771
+ transitions = [
772
+ {:at => Time.utc(2000, 1, 1), :offset_index => 1},
773
+ {:at => Time.utc(2000, 2, 1), :offset_index => 2}]
774
+
775
+ tzif_test(offsets, transitions) do |path, format|
776
+ info = ZoneinfoTimezoneInfo.new('Zone/DoubleDaylight', path)
777
+ assert_equal('Zone/DoubleDaylight', info.identifier)
778
+
779
+ assert_period(:LMT, 3542, 0, false, nil, Time.utc(2000, 1, 1), info)
780
+ assert_period(:YST, 3600, 0, false, Time.utc(2000, 1, 1), Time.utc(2000, 2, 1), info)
781
+ assert_period(:XDT, 0, 3600, true, Time.utc(2000, 2, 1), nil, info)
782
+ end
783
+ end
784
+
785
+ def test_load_apia_international_dateline_change
786
+ # The zoneinfo files don't include the offset from standard time, so this
787
+ # has to be derived by looking at changes in the total UTC offset.
788
+
789
+ # Pacific/Apia moved across the International Date Line whilst observing
790
+ # daylight savings time.
791
+
792
+ offsets = [
793
+ {:gmtoff => 45184, :isdst => false, :abbrev => 'LMT'},
794
+ {:gmtoff => -39600, :isdst => false, :abbrev => '-11'},
795
+ {:gmtoff => -36000, :isdst => true, :abbrev => '-10'},
796
+ {:gmtoff => 50400, :isdst => true, :abbrev => '+14'},
797
+ {:gmtoff => 46800, :isdst => false, :abbrev => '+13'}]
798
+
799
+ transitions = [
800
+ {:at => Time.utc(2011, 4, 2, 14, 0, 0), :offset_index => 1},
801
+ {:at => Time.utc(2011, 9, 24, 14, 0, 0), :offset_index => 2},
802
+ {:at => Time.utc(2011, 12, 30, 10, 0, 0), :offset_index => 3},
803
+ {:at => Time.utc(2012, 3, 31, 14, 0, 0), :offset_index => 4}]
804
+
805
+ tzif_test(offsets, transitions) do |path, format|
806
+ info = ZoneinfoTimezoneInfo.new('Test/Pacific/Apia', path)
807
+ assert_equal('Test/Pacific/Apia', info.identifier)
808
+
809
+ assert_period( :LMT, 45184, 0, false, nil, Time.utc(2011, 4, 2, 14, 0, 0), info)
810
+ assert_period(:'-11', -39600, 0, false, Time.utc(2011, 4, 2, 14, 0, 0), Time.utc(2011, 9, 24, 14, 0, 0), info)
811
+ assert_period(:'-10', -39600, 3600, true, Time.utc(2011, 9, 24, 14, 0, 0), Time.utc(2011, 12, 30, 10, 0, 0), info)
812
+ assert_period(:'+14', 46800, 3600, true, Time.utc(2011, 12, 30, 10, 0, 0), Time.utc(2012, 3, 31, 14, 0, 0), info)
813
+ assert_period(:'+13', 46800, 0, false, Time.utc(2012, 3, 31, 14, 0, 0), nil, info)
814
+ end
815
+ end
816
+
817
+ def test_load_offset_split_for_different_utc_offset
818
+ # The zoneinfo files don't include the offset from standard time, so this
819
+ # has to be derived by looking at changes in the total UTC offset.
820
+
821
+ offsets = [
822
+ {:gmtoff => 3542, :isdst => false, :abbrev => 'LMT'},
823
+ {:gmtoff => 3600, :isdst => false, :abbrev => 'XST1'},
824
+ {:gmtoff => 7200, :isdst => false, :abbrev => 'XST2'},
825
+ {:gmtoff => 10800, :isdst => true, :abbrev => 'XDT'}]
826
+
827
+ transitions = [
828
+ {:at => Time.utc(2000, 1, 1), :offset_index => 1},
829
+ {:at => Time.utc(2000, 2, 1), :offset_index => 3},
830
+ {:at => Time.utc(2000, 3, 1), :offset_index => 1},
831
+ {:at => Time.utc(2000, 4, 1), :offset_index => 2},
832
+ {:at => Time.utc(2000, 5, 1), :offset_index => 3},
833
+ {:at => Time.utc(2000, 6, 1), :offset_index => 2},
834
+ {:at => Time.utc(2000, 7, 1), :offset_index => 1},
835
+ {:at => Time.utc(2000, 8, 1), :offset_index => 3},
836
+ {:at => Time.utc(2000, 9, 1), :offset_index => 1},
837
+ {:at => Time.utc(2000, 10, 1), :offset_index => 2},
838
+ {:at => Time.utc(2000, 11, 1), :offset_index => 3},
839
+ {:at => Time.utc(2000, 12, 1), :offset_index => 2}]
840
+
841
+ # XDT will be split and defined according to its surrounding standard time
842
+ # offsets.
843
+
844
+ tzif_test(offsets, transitions) do |path, format|
845
+ info = ZoneinfoTimezoneInfo.new('Zone/SplitUtcOffset', path)
846
+ assert_equal('Zone/SplitUtcOffset', info.identifier)
847
+
848
+ assert_period( :LMT, 3542, 0, false, nil, Time.utc(2000, 1, 1), info)
849
+ assert_period(:XST1, 3600, 0, false, Time.utc(2000, 1, 1), Time.utc(2000, 2, 1), info)
850
+ assert_period( :XDT, 3600, 7200, true, Time.utc(2000, 2, 1), Time.utc(2000, 3, 1), info)
851
+ assert_period(:XST1, 3600, 0, false, Time.utc(2000, 3, 1), Time.utc(2000, 4, 1), info)
852
+ assert_period(:XST2, 7200, 0, false, Time.utc(2000, 4, 1), Time.utc(2000, 5, 1), info)
853
+ assert_period( :XDT, 7200, 3600, true, Time.utc(2000, 5, 1), Time.utc(2000, 6, 1), info)
854
+ assert_period(:XST2, 7200, 0, false, Time.utc(2000, 6, 1), Time.utc(2000, 7, 1), info)
855
+ assert_period(:XST1, 3600, 0, false, Time.utc(2000, 7, 1), Time.utc(2000, 8, 1), info)
856
+ assert_period( :XDT, 3600, 7200, true, Time.utc(2000, 8, 1), Time.utc(2000, 9, 1), info)
857
+ assert_period(:XST1, 3600, 0, false, Time.utc(2000, 9, 1), Time.utc(2000, 10, 1), info)
858
+ assert_period(:XST2, 7200, 0, false, Time.utc(2000, 10, 1), Time.utc(2000, 11, 1), info)
859
+ assert_period( :XDT, 7200, 3600, true, Time.utc(2000, 11, 1), Time.utc(2000, 12, 1), info)
860
+ assert_period(:XST2, 7200, 0, false, Time.utc(2000, 12, 1), nil, info)
861
+
862
+ 1.upto(6) do |i|
863
+ assert_same(info.period_for_utc(Time.utc(2000, i, 1)).offset, info.period_for_utc(Time.utc(2000, i + 6, 1)).offset)
864
+ end
865
+ end
866
+ end
867
+
868
+ def test_load_offset_utc_offset_taken_from_minimum_difference_minimum_after
869
+ # The zoneinfo files don't include the offset from standard time, so this
870
+ # has to be derived by looking at changes in the total UTC offset.
871
+
872
+ offsets = [
873
+ {:gmtoff => 3542, :isdst => false, :abbrev => 'LMT'},
874
+ {:gmtoff => 3600, :isdst => false, :abbrev => 'XST1'},
875
+ {:gmtoff => 7200, :isdst => false, :abbrev => 'XST2'},
876
+ {:gmtoff => 10800, :isdst => true, :abbrev => 'XDT'}]
877
+
878
+ transitions = [
879
+ {:at => Time.utc(2000, 1, 1), :offset_index => 1},
880
+ {:at => Time.utc(2000, 2, 1), :offset_index => 3},
881
+ {:at => Time.utc(2000, 3, 1), :offset_index => 2}]
882
+
883
+ # XDT should use the closest utc_offset (7200) (and not an equivalent
884
+ # utc_offset of 3600 and std_offset of 7200).
885
+
886
+ tzif_test(offsets, transitions) do |path, format|
887
+ info = ZoneinfoTimezoneInfo.new('Zone/MinimumUtcOffset', path)
888
+ assert_equal('Zone/MinimumUtcOffset', info.identifier)
889
+
890
+ assert_period( :LMT, 3542, 0, false, nil, Time.utc(2000, 1, 1), info)
891
+ assert_period(:XST1, 3600, 0, false, Time.utc(2000, 1, 1), Time.utc(2000, 2, 1), info)
892
+ assert_period( :XDT, 7200, 3600, true, Time.utc(2000, 2, 1), Time.utc(2000, 3, 1), info)
893
+ assert_period(:XST2, 7200, 0, false, Time.utc(2000, 3, 1), nil, info)
894
+ end
895
+ end
896
+
897
+ def test_load_offset_utc_offset_taken_from_minimum_difference_minimum_before
898
+ # The zoneinfo files don't include the offset from standard time, so this
899
+ # has to be derived by looking at changes in the total UTC offset.
900
+
901
+ offsets = [
902
+ {:gmtoff => 3542, :isdst => false, :abbrev => 'LMT'},
903
+ {:gmtoff => 3600, :isdst => false, :abbrev => 'XST1'},
904
+ {:gmtoff => 7200, :isdst => false, :abbrev => 'XST2'},
905
+ {:gmtoff => 10800, :isdst => true, :abbrev => 'XDT'}]
906
+
907
+ transitions = [
908
+ {:at => Time.utc(2000, 1, 1), :offset_index => 2},
909
+ {:at => Time.utc(2000, 2, 1), :offset_index => 3},
910
+ {:at => Time.utc(2000, 3, 1), :offset_index => 1}]
911
+
912
+ # XDT should use the closest utc_offset (7200) (and not an equivalent
913
+ # utc_offset of 3600 and std_offset of 7200).
914
+
915
+ tzif_test(offsets, transitions) do |path, format|
916
+ info = ZoneinfoTimezoneInfo.new('Zone/MinimumUtcOffset', path)
917
+ assert_equal('Zone/MinimumUtcOffset', info.identifier)
918
+
919
+ assert_period( :LMT, 3542, 0, false, nil, Time.utc(2000, 1, 1), info)
920
+ assert_period(:XST2, 7200, 0, false, Time.utc(2000, 1, 1), Time.utc(2000, 2, 1), info)
921
+ assert_period( :XDT, 7200, 3600, true, Time.utc(2000, 2, 1), Time.utc(2000, 3, 1), info)
922
+ assert_period(:XST1, 3600, 0, false, Time.utc(2000, 3, 1), nil, info)
923
+ end
924
+ end
925
+
926
+ def test_load_offset_does_not_use_equal_utc_total_offset_equal_after
927
+ # The zoneinfo files don't include the offset from standard time, so this
928
+ # has to be derived by looking at changes in the total UTC offset.
929
+
930
+ offsets = [
931
+ {:gmtoff => 3542, :isdst => false, :abbrev => 'LMT'},
932
+ {:gmtoff => 3600, :isdst => false, :abbrev => 'XST1'},
933
+ {:gmtoff => 7200, :isdst => false, :abbrev => 'XST2'},
934
+ {:gmtoff => 7200, :isdst => true, :abbrev => 'XDT'}]
935
+
936
+ transitions = [
937
+ {:at => Time.utc(2000, 1, 1), :offset_index => 1},
938
+ {:at => Time.utc(2000, 2, 1), :offset_index => 3},
939
+ {:at => Time.utc(2000, 3, 1), :offset_index => 2}]
940
+
941
+ # XDT will be based on the utc_offset of XST1 even though XST2 has an
942
+ # equivalent (or greater) utc_total_offset.
943
+
944
+ tzif_test(offsets, transitions) do |path, format|
945
+ info = ZoneinfoTimezoneInfo.new('Zone/UtcOffsetEqual', path)
946
+ assert_equal('Zone/UtcOffsetEqual', info.identifier)
947
+
948
+ assert_period( :LMT, 3542, 0, false, nil, Time.utc(2000, 1, 1), info)
949
+ assert_period(:XST1, 3600, 0, false, Time.utc(2000, 1, 1), Time.utc(2000, 2, 1), info)
950
+ assert_period( :XDT, 3600, 3600, true, Time.utc(2000, 2, 1), Time.utc(2000, 3, 1), info)
951
+ assert_period(:XST2, 7200, 0, false, Time.utc(2000, 3, 1), nil, info)
952
+ end
953
+ end
954
+
955
+ def test_load_offset_does_not_use_equal_utc_total_offset_equal_before
956
+ # The zoneinfo files don't include the offset from standard time, so this
957
+ # has to be derived by looking at changes in the total UTC offset.
958
+
959
+ offsets = [
960
+ {:gmtoff => 3542, :isdst => false, :abbrev => 'LMT'},
961
+ {:gmtoff => 3600, :isdst => false, :abbrev => 'XST1'},
962
+ {:gmtoff => 7200, :isdst => false, :abbrev => 'XST2'},
963
+ {:gmtoff => 7200, :isdst => true, :abbrev => 'XDT'}]
964
+
965
+ transitions = [
966
+ {:at => Time.utc(2000, 1, 1), :offset_index => 2},
967
+ {:at => Time.utc(2000, 2, 1), :offset_index => 3},
968
+ {:at => Time.utc(2000, 3, 1), :offset_index => 1}]
969
+
970
+ # XDT will be based on the utc_offset of XST1 even though XST2 has an
971
+ # equivalent (or greater) utc_total_offset.
972
+
973
+ tzif_test(offsets, transitions) do |path, format|
974
+ info = ZoneinfoTimezoneInfo.new('Zone/UtcOffsetEqual', path)
975
+ assert_equal('Zone/UtcOffsetEqual', info.identifier)
976
+
977
+ assert_period( :LMT, 3542, 0, false, nil, Time.utc(2000, 1, 1), info)
978
+ assert_period(:XST2, 7200, 0, false, Time.utc(2000, 1, 1), Time.utc(2000, 2, 1), info)
979
+ assert_period( :XDT, 3600, 3600, true, Time.utc(2000, 2, 1), Time.utc(2000, 3, 1), info)
980
+ assert_period(:XST1, 3600, 0, false, Time.utc(2000, 3, 1), nil, info)
981
+ end
982
+ end
983
+
984
+ def test_load_offset_both_adjacent_non_dst_equal_utc_total_offset
985
+ # The zoneinfo files don't include the offset from standard time, so this
986
+ # has to be derived by looking at changes in the total UTC offset.
987
+
988
+ offsets = [
989
+ {:gmtoff => 7142, :isdst => false, :abbrev => 'LMT'},
990
+ {:gmtoff => 7200, :isdst => false, :abbrev => 'XST'},
991
+ {:gmtoff => 7200, :isdst => true, :abbrev => 'XDT'}]
992
+
993
+ transitions = [
994
+ {:at => Time.utc(2000, 1, 1), :offset_index => 1},
995
+ {:at => Time.utc(2000, 2, 1), :offset_index => 2},
996
+ {:at => Time.utc(2000, 3, 1), :offset_index => 1}]
997
+
998
+ # XDT will just assume an std_offset of +1 hour and calculate the utc_offset
999
+ # from utc_total_offset - std_offset.
1000
+
1001
+ tzif_test(offsets, transitions) do |path, format|
1002
+ info = ZoneinfoTimezoneInfo.new('Zone/AdjacentEqual', path)
1003
+ assert_equal('Zone/AdjacentEqual', info.identifier)
1004
+
1005
+ assert_period(:LMT, 7142, 0, false, nil, Time.utc(2000, 1, 1), info)
1006
+ assert_period(:XST, 7200, 0, false, Time.utc(2000, 1, 1), Time.utc(2000, 2, 1), info)
1007
+ assert_period(:XDT, 3600, 3600, true, Time.utc(2000, 2, 1), Time.utc(2000, 3, 1), info)
1008
+ assert_period(:XST, 7200, 0, false, Time.utc(2000, 3, 1), nil, info)
1009
+ end
1010
+ end
1011
+
1012
+ def test_load_offset_utc_offset_preserved_from_next
1013
+ # The zoneinfo files don't include the offset from standard time, so this
1014
+ # has to be derived by looking at changes in the total UTC offset.
1015
+
1016
+ offsets = [
1017
+ {:gmtoff => 3542, :isdst => false, :abbrev => 'LMT'},
1018
+ {:gmtoff => 3600, :isdst => false, :abbrev => 'XST1'},
1019
+ {:gmtoff => 7200, :isdst => false, :abbrev => 'XST2'},
1020
+ {:gmtoff => 10800, :isdst => true, :abbrev => 'XDT1'},
1021
+ {:gmtoff => 10800, :isdst => true, :abbrev => 'XDT2'}]
1022
+
1023
+ transitions = [
1024
+ {:at => Time.utc(2000, 1, 1), :offset_index => 1},
1025
+ {:at => Time.utc(2000, 2, 1), :offset_index => 3},
1026
+ {:at => Time.utc(2000, 3, 1), :offset_index => 4},
1027
+ {:at => Time.utc(2000, 4, 1), :offset_index => 2}]
1028
+
1029
+ # Both XDT1 and XDT2 should both use the closest utc_offset (7200) (and not
1030
+ # an equivalent utc_offset of 3600 and std_offset of 7200).
1031
+
1032
+ tzif_test(offsets, transitions) do |path, format|
1033
+ info = ZoneinfoTimezoneInfo.new('Zone/UtcOffsetPreserved', path)
1034
+ assert_equal('Zone/UtcOffsetPreserved', info.identifier)
1035
+
1036
+ assert_period( :LMT, 3542, 0, false, nil, Time.utc(2000, 1, 1), info)
1037
+ assert_period(:XST1, 3600, 0, false, Time.utc(2000, 1, 1), Time.utc(2000, 2, 1), info)
1038
+ assert_period(:XDT1, 7200, 3600, true, Time.utc(2000, 2, 1), Time.utc(2000, 3, 1), info)
1039
+ assert_period(:XDT2, 7200, 3600, true, Time.utc(2000, 3, 1), Time.utc(2000, 4, 1), info)
1040
+ assert_period(:XST2, 7200, 0, false, Time.utc(2000, 4, 1), nil, info)
1041
+ end
1042
+ end
1043
+
1044
+ def test_load_offset_utc_offset_preserved_from_previous
1045
+ # The zoneinfo files don't include the offset from standard time, so this
1046
+ # has to be derived by looking at changes in the total UTC offset.
1047
+
1048
+ offsets = [
1049
+ {:gmtoff => 3542, :isdst => false, :abbrev => 'LMT'},
1050
+ {:gmtoff => 3600, :isdst => false, :abbrev => 'XST1'},
1051
+ {:gmtoff => 7200, :isdst => false, :abbrev => 'XST2'},
1052
+ {:gmtoff => 10800, :isdst => true, :abbrev => 'XDT1'},
1053
+ {:gmtoff => 10800, :isdst => true, :abbrev => 'XDT2'}]
1054
+
1055
+ transitions = [
1056
+ {:at => Time.utc(2000, 1, 1), :offset_index => 2},
1057
+ {:at => Time.utc(2000, 2, 1), :offset_index => 3},
1058
+ {:at => Time.utc(2000, 3, 1), :offset_index => 4},
1059
+ {:at => Time.utc(2000, 4, 1), :offset_index => 1}]
1060
+
1061
+ # Both XDT1 and XDT2 should both use the closest utc_offset (7200) (and not
1062
+ # an equivalent utc_offset of 3600 and std_offset of 7200).
1063
+
1064
+ tzif_test(offsets, transitions) do |path, format|
1065
+ info = ZoneinfoTimezoneInfo.new('Zone/UtcOffsetPreserved', path)
1066
+ assert_equal('Zone/UtcOffsetPreserved', info.identifier)
1067
+
1068
+ assert_period( :LMT, 3542, 0, false, nil, Time.utc(2000, 1, 1), info)
1069
+ assert_period(:XST2, 7200, 0, false, Time.utc(2000, 1, 1), Time.utc(2000, 2, 1), info)
1070
+ assert_period(:XDT1, 7200, 3600, true, Time.utc(2000, 2, 1), Time.utc(2000, 3, 1), info)
1071
+ assert_period(:XDT2, 7200, 3600, true, Time.utc(2000, 3, 1), Time.utc(2000, 4, 1), info)
1072
+ assert_period(:XST1, 3600, 0, false, Time.utc(2000, 4, 1), nil, info)
1073
+ end
1074
+ end
1075
+
1076
+ def test_read_offset_negative_std_offset_dst
1077
+ # The zoneinfo files don't include the offset from standard time, so this
1078
+ # has to be derived by looking at changes in the total UTC offset.
1079
+
1080
+ offsets = [
1081
+ {:gmtoff => -100, :isdst => false, :abbrev => 'LMT'},
1082
+ {:gmtoff => 3600, :isdst => false, :abbrev => 'XST'},
1083
+ {:gmtoff => 0, :isdst => true, :abbrev => 'XWT'}]
1084
+
1085
+ transitions = [
1086
+ {:at => Time.utc(2000, 1, 1), :offset_index => 1},
1087
+ {:at => Time.utc(2000, 2, 1), :offset_index => 2},
1088
+ {:at => Time.utc(2000, 3, 1), :offset_index => 1},
1089
+ {:at => Time.utc(2000, 4, 1), :offset_index => 2},
1090
+ {:at => Time.utc(2000, 5, 1), :offset_index => 1}]
1091
+
1092
+ tzif_test(offsets, transitions) do |path, format|
1093
+ info = ZoneinfoTimezoneInfo.new('Zone/NegativeStdOffsetDst', path)
1094
+ assert_equal('Zone/NegativeStdOffsetDst', info.identifier)
1095
+
1096
+ assert_period(:LMT, -100, 0, false, nil, Time.utc(2000, 1, 1), info)
1097
+ assert_period(:XST, 3600, 0, false, Time.utc(2000, 1, 1), Time.utc(2000, 2, 1), info)
1098
+ assert_period(:XWT, 3600, -3600, true, Time.utc(2000, 2, 1), Time.utc(2000, 3, 1), info)
1099
+ assert_period(:XST, 3600, 0, false, Time.utc(2000, 3, 1), Time.utc(2000, 4, 1), info)
1100
+ assert_period(:XWT, 3600, -3600, true, Time.utc(2000, 4, 1), Time.utc(2000, 5, 1), info)
1101
+ assert_period(:XST, 3600, 0, false, Time.utc(2000, 5, 1), nil, info)
1102
+ end
1103
+ end
1104
+
1105
+ def test_read_offset_negative_std_offset_dst_initial_dst
1106
+ # The zoneinfo files don't include the offset from standard time, so this
1107
+ # has to be derived by looking at changes in the total UTC offset.
1108
+
1109
+ offsets = [
1110
+ {:gmtoff => -100, :isdst => false, :abbrev => 'LMT'},
1111
+ {:gmtoff => 0, :isdst => true, :abbrev => 'XWT'},
1112
+ {:gmtoff => 3600, :isdst => false, :abbrev => 'XST'}]
1113
+
1114
+ transitions = [
1115
+ {:at => Time.utc(2000, 1, 1), :offset_index => 1},
1116
+ {:at => Time.utc(2000, 2, 1), :offset_index => 2},
1117
+ {:at => Time.utc(2000, 3, 1), :offset_index => 1},
1118
+ {:at => Time.utc(2000, 4, 1), :offset_index => 2},
1119
+ {:at => Time.utc(2000, 5, 1), :offset_index => 1}]
1120
+
1121
+ tzif_test(offsets, transitions) do |path, format|
1122
+ info = ZoneinfoTimezoneInfo.new('Zone/NegativeStdOffsetDstInitialDst', path)
1123
+ assert_equal('Zone/NegativeStdOffsetDstInitialDst', info.identifier)
1124
+
1125
+ assert_period(:LMT, -100, 0, false, nil, Time.utc(2000, 1, 1), info)
1126
+ assert_period(:XWT, 3600, -3600, true, Time.utc(2000, 1, 1), Time.utc(2000, 2, 1), info)
1127
+ assert_period(:XST, 3600, 0, false, Time.utc(2000, 2, 1), Time.utc(2000, 3, 1), info)
1128
+ assert_period(:XWT, 3600, -3600, true, Time.utc(2000, 3, 1), Time.utc(2000, 4, 1), info)
1129
+ assert_period(:XST, 3600, 0, false, Time.utc(2000, 4, 1), Time.utc(2000, 5, 1), info)
1130
+ assert_period(:XWT, 3600, -3600, true, Time.utc(2000, 5, 1), nil, info)
1131
+ end
1132
+ end
1133
+
1134
+ def test_read_offset_prefer_base_offset_moves_to_dst_not_hour
1135
+ offsets = [
1136
+ {:gmtoff => -100, :isdst => false, :abbrev => 'LMT'},
1137
+ {:gmtoff => 0, :isdst => false, :abbrev => 'XST'},
1138
+ {:gmtoff => 1800, :isdst => true, :abbrev => 'XDT'},
1139
+ {:gmtoff => 1800, :isdst => false, :abbrev => 'XST'}]
1140
+
1141
+ transitions = [
1142
+ {:at => Time.utc(2000, 1, 1), :offset_index => 1},
1143
+ {:at => Time.utc(2000, 2, 1), :offset_index => 2},
1144
+ {:at => Time.utc(2000, 3, 1), :offset_index => 3}]
1145
+
1146
+ tzif_test(offsets, transitions) do |path, format|
1147
+ info = ZoneinfoTimezoneInfo.new('Zone/BaseOffsetMovesToDstNotHour', path)
1148
+ assert_equal('Zone/BaseOffsetMovesToDstNotHour', info.identifier)
1149
+
1150
+ assert_period(:LMT, -100, 0, false, nil, Time.utc(2000, 1, 1), info)
1151
+ assert_period(:XST, 0, 0, false, Time.utc(2000, 1, 1), Time.utc(2000, 2, 1), info)
1152
+ assert_period(:XDT, 0, 1800, true, Time.utc(2000, 2, 1), Time.utc(2000, 3, 1), info)
1153
+ assert_period(:XST, 1800, 0, false, Time.utc(2000, 3, 1), nil, info)
1154
+ end
1155
+ end
1156
+
1157
+ def test_read_offset_prefer_base_offset_moves_from_dst_not_hour
1158
+ offsets = [
1159
+ {:gmtoff => -100, :isdst => false, :abbrev => 'LMT'},
1160
+ {:gmtoff => 1800, :isdst => false, :abbrev => 'XST'},
1161
+ {:gmtoff => 1800, :isdst => true, :abbrev => 'XDT'},
1162
+ {:gmtoff => 0, :isdst => false, :abbrev => 'XST'}]
1163
+
1164
+ transitions = [
1165
+ {:at => Time.utc(2000, 1, 1), :offset_index => 1},
1166
+ {:at => Time.utc(2000, 2, 1), :offset_index => 2},
1167
+ {:at => Time.utc(2000, 3, 1), :offset_index => 3}]
1168
+
1169
+ tzif_test(offsets, transitions) do |path, format|
1170
+ info = ZoneinfoTimezoneInfo.new('Zone/BaseOffsetMovesFromDstNotHour', path)
1171
+ assert_equal('Zone/BaseOffsetMovesFromDstNotHour', info.identifier)
1172
+
1173
+ assert_period(:LMT, -100, 0, false, nil, Time.utc(2000, 1, 1), info)
1174
+ assert_period(:XST, 1800, 0, false, Time.utc(2000, 1, 1), Time.utc(2000, 2, 1), info)
1175
+ assert_period(:XDT, 0, 1800, true, Time.utc(2000, 2, 1), Time.utc(2000, 3, 1), info)
1176
+ assert_period(:XST, 0, 0, false, Time.utc(2000, 3, 1), nil, info)
1177
+ end
1178
+ end
1179
+
1180
+ def test_load_in_safe_mode
1181
+ offsets = [{:gmtoff => -12094, :isdst => false, :abbrev => 'LT'}]
1182
+
1183
+ tzif_test(offsets, []) do |path, format|
1184
+ # untaint only required for Ruby 1.9.2
1185
+ path.untaint
1186
+
1187
+ safe_test do
1188
+ info = ZoneinfoTimezoneInfo.new('Zone/three', path)
1189
+ assert_equal('Zone/three', info.identifier)
1190
+
1191
+ assert_period(:LT, -12094, 0, false, nil, nil, info)
1192
+ end
1193
+ end
1194
+ end
1195
+
1196
+ def test_load_encoding
1197
+ # tzfile.5 doesn't specify an encoding, but the source data is in ASCII.
1198
+ # ZoneinfoTimezoneInfo will load as UTF-8 (a superset of ASCII).
1199
+
1200
+ offsets = [
1201
+ {:gmtoff => 3542, :isdst => false, :abbrev => 'LMT'},
1202
+ {:gmtoff => 3600, :isdst => false, :abbrev => 'XST©'}]
1203
+
1204
+ transitions = [
1205
+ {:at => Time.utc(1971, 1, 2), :offset_index => 1}]
1206
+
1207
+ tzif_test(offsets, transitions) do |path, format|
1208
+ info = ZoneinfoTimezoneInfo.new('Zone/One', path)
1209
+ assert_equal('Zone/One', info.identifier)
1210
+
1211
+ assert_period(:LMT, 3542, 0, false, nil, Time.utc(1971, 1, 2), info)
1212
+ assert_period(:"XST©", 3600, 0, false, Time.utc(1971, 1, 2), nil, info)
1213
+ end
1214
+ end
1215
+
1216
+ def test_load_binmode
1217
+ offsets = [
1218
+ {:gmtoff => 3542, :isdst => false, :abbrev => 'LMT'},
1219
+ {:gmtoff => 3600, :isdst => false, :abbrev => 'XST'}]
1220
+
1221
+ # Transition time that includes CRLF (4EFF0D0A).
1222
+ # Test that this doesn't get corrupted by translating CRLF to LF.
1223
+ transitions = [
1224
+ {:at => Time.utc(2011, 12, 31, 13, 24, 26), :offset_index => 1}]
1225
+
1226
+ tzif_test(offsets, transitions) do |path, format|
1227
+ info = ZoneinfoTimezoneInfo.new('Zone/One', path)
1228
+ assert_equal('Zone/One', info.identifier)
1229
+
1230
+ assert_period(:LMT, 3542, 0, false, nil, Time.utc(2011, 12, 31, 13, 24, 26), info)
1231
+ assert_period(:XST, 3600, 0, false, Time.utc(2011, 12, 31, 13, 24, 26), nil, info)
1232
+ end
1233
+ end
1234
+ end