tzinfo 1.2.5

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