tzinfo 1.2.10 → 2.0.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (152) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/.yardopts +3 -0
  4. data/CHANGES.md +583 -391
  5. data/LICENSE +13 -13
  6. data/README.md +368 -114
  7. data/lib/tzinfo/annual_rules.rb +32 -12
  8. data/lib/tzinfo/country.rb +141 -129
  9. data/lib/tzinfo/country_timezone.rb +70 -112
  10. data/lib/tzinfo/data_source.rb +400 -144
  11. data/lib/tzinfo/data_sources/constant_offset_data_timezone_info.rb +56 -0
  12. data/lib/tzinfo/data_sources/country_info.rb +42 -0
  13. data/lib/tzinfo/data_sources/data_timezone_info.rb +91 -0
  14. data/lib/tzinfo/data_sources/linked_timezone_info.rb +33 -0
  15. data/lib/tzinfo/data_sources/posix_time_zone_parser.rb +177 -0
  16. data/lib/tzinfo/data_sources/ruby_data_source.rb +141 -0
  17. data/lib/tzinfo/data_sources/timezone_info.rb +47 -0
  18. data/lib/tzinfo/data_sources/transitions_data_timezone_info.rb +214 -0
  19. data/lib/tzinfo/data_sources/zoneinfo_data_source.rb +592 -0
  20. data/lib/tzinfo/data_sources/zoneinfo_reader.rb +482 -0
  21. data/lib/tzinfo/data_sources.rb +8 -0
  22. data/lib/tzinfo/data_timezone.rb +33 -47
  23. data/lib/tzinfo/datetime_with_offset.rb +153 -0
  24. data/lib/tzinfo/format1/country_definer.rb +17 -0
  25. data/lib/tzinfo/format1/country_index_definition.rb +64 -0
  26. data/lib/tzinfo/format1/timezone_definer.rb +64 -0
  27. data/lib/tzinfo/format1/timezone_definition.rb +39 -0
  28. data/lib/tzinfo/format1/timezone_index_definition.rb +77 -0
  29. data/lib/tzinfo/format1.rb +10 -0
  30. data/lib/tzinfo/format2/country_definer.rb +68 -0
  31. data/lib/tzinfo/format2/country_index_definer.rb +68 -0
  32. data/lib/tzinfo/format2/country_index_definition.rb +46 -0
  33. data/lib/tzinfo/format2/timezone_definer.rb +94 -0
  34. data/lib/tzinfo/format2/timezone_definition.rb +73 -0
  35. data/lib/tzinfo/format2/timezone_index_definer.rb +45 -0
  36. data/lib/tzinfo/format2/timezone_index_definition.rb +55 -0
  37. data/lib/tzinfo/format2.rb +10 -0
  38. data/lib/tzinfo/info_timezone.rb +26 -21
  39. data/lib/tzinfo/linked_timezone.rb +33 -52
  40. data/lib/tzinfo/offset_timezone_period.rb +42 -0
  41. data/lib/tzinfo/ruby_core_support.rb +24 -155
  42. data/lib/tzinfo/string_deduper.rb +118 -0
  43. data/lib/tzinfo/time_with_offset.rb +154 -0
  44. data/lib/tzinfo/timestamp.rb +552 -0
  45. data/lib/tzinfo/timestamp_with_offset.rb +85 -0
  46. data/lib/tzinfo/timezone.rb +989 -502
  47. data/lib/tzinfo/timezone_offset.rb +84 -74
  48. data/lib/tzinfo/timezone_period.rb +151 -217
  49. data/lib/tzinfo/timezone_proxy.rb +70 -79
  50. data/lib/tzinfo/timezone_transition.rb +77 -109
  51. data/lib/tzinfo/transition_rule.rb +207 -77
  52. data/lib/tzinfo/transitions_timezone_period.rb +63 -0
  53. data/lib/tzinfo/version.rb +7 -0
  54. data/lib/tzinfo/with_offset.rb +61 -0
  55. data/lib/tzinfo.rb +78 -40
  56. data.tar.gz.sig +0 -0
  57. metadata +50 -105
  58. metadata.gz.sig +0 -0
  59. data/Rakefile +0 -107
  60. data/lib/tzinfo/country_index_definition.rb +0 -31
  61. data/lib/tzinfo/country_info.rb +0 -42
  62. data/lib/tzinfo/data_timezone_info.rb +0 -55
  63. data/lib/tzinfo/linked_timezone_info.rb +0 -26
  64. data/lib/tzinfo/offset_rationals.rb +0 -77
  65. data/lib/tzinfo/posix_time_zone_parser.rb +0 -136
  66. data/lib/tzinfo/ruby_country_info.rb +0 -74
  67. data/lib/tzinfo/ruby_data_source.rb +0 -140
  68. data/lib/tzinfo/time_or_datetime.rb +0 -351
  69. data/lib/tzinfo/timezone_definition.rb +0 -36
  70. data/lib/tzinfo/timezone_index_definition.rb +0 -54
  71. data/lib/tzinfo/timezone_info.rb +0 -30
  72. data/lib/tzinfo/timezone_transition_definition.rb +0 -104
  73. data/lib/tzinfo/transition_data_timezone_info.rb +0 -274
  74. data/lib/tzinfo/zoneinfo_country_info.rb +0 -37
  75. data/lib/tzinfo/zoneinfo_data_source.rb +0 -512
  76. data/lib/tzinfo/zoneinfo_timezone_info.rb +0 -520
  77. data/test/assets/payload.rb +0 -1
  78. data/test/tc_annual_rules.rb +0 -95
  79. data/test/tc_country.rb +0 -238
  80. data/test/tc_country_index_definition.rb +0 -69
  81. data/test/tc_country_info.rb +0 -16
  82. data/test/tc_country_timezone.rb +0 -173
  83. data/test/tc_data_source.rb +0 -218
  84. data/test/tc_data_timezone.rb +0 -99
  85. data/test/tc_data_timezone_info.rb +0 -18
  86. data/test/tc_info_timezone.rb +0 -34
  87. data/test/tc_linked_timezone.rb +0 -155
  88. data/test/tc_linked_timezone_info.rb +0 -23
  89. data/test/tc_offset_rationals.rb +0 -23
  90. data/test/tc_posix_time_zone_parser.rb +0 -261
  91. data/test/tc_ruby_core_support.rb +0 -168
  92. data/test/tc_ruby_country_info.rb +0 -110
  93. data/test/tc_ruby_data_source.rb +0 -173
  94. data/test/tc_time_or_datetime.rb +0 -674
  95. data/test/tc_timezone.rb +0 -1361
  96. data/test/tc_timezone_definition.rb +0 -113
  97. data/test/tc_timezone_index_definition.rb +0 -73
  98. data/test/tc_timezone_info.rb +0 -11
  99. data/test/tc_timezone_london.rb +0 -143
  100. data/test/tc_timezone_melbourne.rb +0 -142
  101. data/test/tc_timezone_new_york.rb +0 -142
  102. data/test/tc_timezone_offset.rb +0 -126
  103. data/test/tc_timezone_period.rb +0 -555
  104. data/test/tc_timezone_proxy.rb +0 -136
  105. data/test/tc_timezone_transition.rb +0 -366
  106. data/test/tc_timezone_transition_definition.rb +0 -295
  107. data/test/tc_timezone_utc.rb +0 -27
  108. data/test/tc_transition_data_timezone_info.rb +0 -433
  109. data/test/tc_transition_rule.rb +0 -663
  110. data/test/tc_zoneinfo_country_info.rb +0 -78
  111. data/test/tc_zoneinfo_data_source.rb +0 -1223
  112. data/test/tc_zoneinfo_timezone_info.rb +0 -2153
  113. data/test/test_utils.rb +0 -208
  114. data/test/ts_all.rb +0 -7
  115. data/test/ts_all_ruby.rb +0 -5
  116. data/test/ts_all_zoneinfo.rb +0 -9
  117. data/test/tzinfo-data/tzinfo/data/definitions/America/Argentina/Buenos_Aires.rb +0 -89
  118. data/test/tzinfo-data/tzinfo/data/definitions/America/New_York.rb +0 -327
  119. data/test/tzinfo-data/tzinfo/data/definitions/Australia/Melbourne.rb +0 -230
  120. data/test/tzinfo-data/tzinfo/data/definitions/EST.rb +0 -19
  121. data/test/tzinfo-data/tzinfo/data/definitions/Etc/GMT__m__1.rb +0 -21
  122. data/test/tzinfo-data/tzinfo/data/definitions/Etc/GMT__p__1.rb +0 -21
  123. data/test/tzinfo-data/tzinfo/data/definitions/Etc/UTC.rb +0 -21
  124. data/test/tzinfo-data/tzinfo/data/definitions/Europe/Amsterdam.rb +0 -273
  125. data/test/tzinfo-data/tzinfo/data/definitions/Europe/Andorra.rb +0 -198
  126. data/test/tzinfo-data/tzinfo/data/definitions/Europe/London.rb +0 -333
  127. data/test/tzinfo-data/tzinfo/data/definitions/Europe/Paris.rb +0 -277
  128. data/test/tzinfo-data/tzinfo/data/definitions/Europe/Prague.rb +0 -235
  129. data/test/tzinfo-data/tzinfo/data/definitions/UTC.rb +0 -16
  130. data/test/tzinfo-data/tzinfo/data/indexes/countries.rb +0 -940
  131. data/test/tzinfo-data/tzinfo/data/indexes/timezones.rb +0 -609
  132. data/test/tzinfo-data/tzinfo/data/version.rb +0 -20
  133. data/test/tzinfo-data/tzinfo/data.rb +0 -8
  134. data/test/zoneinfo/America/Argentina/Buenos_Aires +0 -0
  135. data/test/zoneinfo/America/New_York +0 -0
  136. data/test/zoneinfo/Australia/Melbourne +0 -0
  137. data/test/zoneinfo/EST +0 -0
  138. data/test/zoneinfo/Etc/UTC +0 -0
  139. data/test/zoneinfo/Europe/Amsterdam +0 -0
  140. data/test/zoneinfo/Europe/Andorra +0 -0
  141. data/test/zoneinfo/Europe/London +0 -0
  142. data/test/zoneinfo/Europe/Paris +0 -0
  143. data/test/zoneinfo/Europe/Prague +0 -0
  144. data/test/zoneinfo/Factory +0 -0
  145. data/test/zoneinfo/iso3166.tab +0 -274
  146. data/test/zoneinfo/leapseconds +0 -78
  147. data/test/zoneinfo/posix/Europe/London +0 -0
  148. data/test/zoneinfo/posixrules +0 -0
  149. data/test/zoneinfo/right/Europe/London +0 -0
  150. data/test/zoneinfo/zone.tab +0 -452
  151. data/test/zoneinfo/zone1970.tab +0 -384
  152. data/tzinfo.gemspec +0 -21
@@ -1,520 +0,0 @@
1
- module TZInfo
2
- # Use send as a workaround for erroneous 'wrong number of arguments' errors
3
- # with JRuby 9.0.5.0 when calling methods with Java implementations. See #114.
4
- send(:using, RubyCoreSupport::UntaintExt) if RubyCoreSupport.const_defined?(:UntaintExt)
5
-
6
- # An InvalidZoneinfoFile exception is raised if an attempt is made to load an
7
- # invalid zoneinfo file.
8
- class InvalidZoneinfoFile < StandardError
9
- end
10
-
11
- # Represents a timezone defined by a compiled zoneinfo TZif (\0, 2 or 3) file.
12
- #
13
- # @private
14
- class ZoneinfoTimezoneInfo < TransitionDataTimezoneInfo #:nodoc:
15
- # The year to generate transitions up to.
16
- #
17
- # @private
18
- GENERATE_UP_TO = RubyCoreSupport.time_supports_64bit ? Time.now.utc.year + 100 : 2037
19
-
20
- # Minimum supported timestamp (inclusive).
21
- #
22
- # Time.utc(1700, 1, 1).to_i
23
- MIN_TIMESTAMP = -8520336000
24
-
25
- # Maximum supported timestamp (exclusive).
26
- #
27
- # Time.utc(2500, 1, 1).to_i
28
- MAX_TIMESTAMP = 16725225600
29
-
30
- # Constructs the new ZoneinfoTimezoneInfo with an identifier, path
31
- # to the file and parser to use to parse the POSIX-like TZ string.
32
- def initialize(identifier, file_path, posix_tz_parser)
33
- super(identifier)
34
-
35
- File.open(file_path, 'rb') do |file|
36
- parse(file, posix_tz_parser)
37
- end
38
- end
39
-
40
- private
41
- # Unpack will return unsigned 32-bit integers. Translate to
42
- # signed 32-bit.
43
- def make_signed_int32(long)
44
- long >= 0x80000000 ? long - 0x100000000 : long
45
- end
46
-
47
- # Unpack will return a 64-bit integer as two unsigned 32-bit integers
48
- # (most significant first). Translate to signed 64-bit
49
- def make_signed_int64(high, low)
50
- unsigned = (high << 32) | low
51
- unsigned >= 0x8000000000000000 ? unsigned - 0x10000000000000000 : unsigned
52
- end
53
-
54
- # Read bytes from file and check that the correct number of bytes could
55
- # be read. Raises InvalidZoneinfoFile if the number of bytes didn't match
56
- # the number requested.
57
- def check_read(file, bytes)
58
- result = file.read(bytes)
59
-
60
- unless result && result.length == bytes
61
- raise InvalidZoneinfoFile, "Expected #{bytes} bytes reading '#{file.path}', but got #{result ? result.length : 0} bytes"
62
- end
63
-
64
- result
65
- end
66
-
67
- # Zoneinfo files don't include the offset from standard time (std_offset)
68
- # for DST periods. Derive the base offset (utc_offset) where DST is
69
- # observed from either the previous or next non-DST period.
70
- #
71
- # Returns the index of the offset to be used prior to the first
72
- # transition.
73
- def derive_offsets(transitions, offsets)
74
- # The first non-DST offset (if there is one) is the offset observed
75
- # before the first transition. Fallback to the first DST offset if there
76
- # are no non-DST offsets.
77
- first_non_dst_offset_index = offsets.index {|o| !o[:is_dst] }
78
- first_offset_index = first_non_dst_offset_index || 0
79
- return first_offset_index if transitions.empty?
80
-
81
- # Determine the utc_offset of the next non-dst offset at each transition.
82
- utc_offset_from_next = nil
83
-
84
- transitions.reverse_each do |transition|
85
- offset = offsets[transition[:offset]]
86
- if offset[:is_dst]
87
- transition[:utc_offset_from_next] = utc_offset_from_next if utc_offset_from_next
88
- else
89
- utc_offset_from_next = offset[:utc_total_offset]
90
- end
91
- end
92
-
93
- utc_offset_from_previous = first_non_dst_offset_index ? offsets[first_non_dst_offset_index][:utc_total_offset] : nil
94
- defined_offsets = {}
95
-
96
- transitions.each do |transition|
97
- offset_index = transition[:offset]
98
- offset = offsets[offset_index]
99
- utc_total_offset = offset[:utc_total_offset]
100
-
101
- if offset[:is_dst]
102
- utc_offset_from_next = transition[:utc_offset_from_next]
103
-
104
- difference_to_previous = (utc_total_offset - (utc_offset_from_previous || utc_total_offset)).abs
105
- difference_to_next = (utc_total_offset - (utc_offset_from_next || utc_total_offset)).abs
106
-
107
- utc_offset = if difference_to_previous == 3600
108
- utc_offset_from_previous
109
- elsif difference_to_next == 3600
110
- utc_offset_from_next
111
- elsif difference_to_previous > 0 && difference_to_next > 0
112
- difference_to_previous < difference_to_next ? utc_offset_from_previous : utc_offset_from_next
113
- elsif difference_to_previous > 0
114
- utc_offset_from_previous
115
- elsif difference_to_next > 0
116
- utc_offset_from_next
117
- else
118
- # No difference, assume a 1 hour offset from standard time.
119
- utc_total_offset - 3600
120
- end
121
-
122
- if !offset[:utc_offset]
123
- offset[:utc_offset] = utc_offset
124
- defined_offsets[offset] = offset_index
125
- elsif offset[:utc_offset] != utc_offset
126
- # An earlier transition has already derived a different
127
- # utc_offset. Define a new offset or reuse an existing identically
128
- # defined offset.
129
- new_offset = offset.dup
130
- new_offset[:utc_offset] = utc_offset
131
-
132
- offset_index = defined_offsets[new_offset]
133
-
134
- unless offset_index
135
- offsets << new_offset
136
- offset_index = offsets.length - 1
137
- defined_offsets[new_offset] = offset_index
138
- end
139
-
140
- transition[:offset] = offset_index
141
- end
142
- else
143
- utc_offset_from_previous = utc_total_offset
144
- end
145
- end
146
-
147
- first_offset_index
148
- end
149
-
150
- # Remove transitions before a minimum supported value. If there is not a
151
- # transition exactly on the minimum supported value move the latest from
152
- # before up to the minimum supported value.
153
- def remove_unsupported_negative_transitions(transitions, min_supported)
154
- result = transitions.drop_while {|t| t[:at] < min_supported }
155
- if result.empty? || (result[0][:at] > min_supported && result.length < transitions.length)
156
- last_before = transitions[-1 - result.length]
157
- last_before[:at] = min_supported
158
- [last_before] + result
159
- else
160
- result
161
- end
162
- end
163
-
164
- # Determines if the offset from a transition matches the offset from a
165
- # rule. This is a looser match than TimezoneOffset#==, not requiring that
166
- # the utc_offset and std_offset both match (which have to be derived for
167
- # transitions, but are known for rules.
168
- def offset_matches_rule?(offset, rule_offset)
169
- offset[:utc_total_offset] == rule_offset.utc_total_offset &&
170
- offset[:is_dst] == rule_offset.dst? &&
171
- offset[:abbr] == rule_offset.abbreviation.to_s
172
- end
173
-
174
- # Determins if the offset from a transition exactly matches the offset
175
- # from a rule.
176
- def offset_equals_rule?(offset, rule_offset)
177
- offset_matches_rule?(offset, rule_offset) &&
178
- (offset[:utc_offset] || (offset[:is_dst] ? offset[:utc_total_offset] - 3600 : offset[:utc_total_offset])) == rule_offset.utc_offset
179
- end
180
-
181
- # Finds an offset hash that is an exact match to the rule offset specified.
182
- def find_existing_offset_index(offsets, rule_offset)
183
- offsets.find_index {|o| offset_equals_rule?(o, rule_offset) }
184
- end
185
-
186
- # Gets an existing matching offset index or adds a new offset hash for a
187
- # rule offset.
188
- def get_rule_offset_index(offsets, offset)
189
- index = find_existing_offset_index(offsets, offset)
190
- unless index
191
- index = offsets.length
192
- offsets << {:utc_total_offset => offset.utc_total_offset, :utc_offset => offset.utc_offset, :is_dst => offset.dst?, :abbr => offset.abbreviation}
193
- end
194
- index
195
- end
196
-
197
- # Gets a hash mapping rule offsets to indexes in offsets, creating new
198
- # offset hashes if required.
199
- def get_rule_offset_indexes(offsets, annual_rules)
200
- {
201
- annual_rules.std_offset => get_rule_offset_index(offsets, annual_rules.std_offset),
202
- annual_rules.dst_offset => get_rule_offset_index(offsets, annual_rules.dst_offset)
203
- }
204
- end
205
-
206
- # Converts an array of rule transitions to hashes.
207
- def convert_transitions_to_hashes(offset_indexes, transitions)
208
- transitions.map {|t| {:at => t.at.to_i, :offset => offset_indexes[t.offset]} }
209
- end
210
-
211
- # Apply the rules from the TZ string when there were no defined
212
- # transitions. Checks for a matching offset. Returns the rules-based
213
- # constant offset or generates transitions from 1970 until 100 years into
214
- # the future (at the time of loading zoneinfo_timezone_info.rb) or 2037 if
215
- # limited to 32-bit Times.
216
- def apply_rules_without_transitions(file, offsets, first_offset_index, rules)
217
- first_offset = offsets[first_offset_index]
218
-
219
- if rules.kind_of?(TimezoneOffset)
220
- unless offset_matches_rule?(first_offset, rules)
221
- raise InvalidZoneinfoFile, "Constant offset POSIX-style TZ string does not match constant offset in file '#{file.path}'."
222
- end
223
-
224
- first_offset[:utc_offset] = rules.utc_offset
225
- []
226
- else
227
- transitions = 1970.upto(GENERATE_UP_TO).map {|y| rules.transitions(y) }.flatten
228
- first_transition = transitions[0]
229
-
230
- if offset_matches_rule?(first_offset, first_transition.previous_offset)
231
- # Correct the first offset if it isn't an exact match.
232
- first_offset[:utc_offset] = first_transition.previous_offset.utc_offset
233
- else
234
- # Not transitioning from the designated first offset.
235
- if offset_matches_rule?(first_offset, first_transition.offset)
236
- # Correct the first offset if it isn't an exact match.
237
- first_offset[:utc_offset] = first_transition.offset.utc_offset
238
-
239
- # Skip an unnecessary transition to the first offset.
240
- transitions.shift
241
- end
242
-
243
- # If the first offset doesn't match either the offset or previous
244
- # offset, then it will be retained.
245
- end
246
-
247
- offset_indexes = get_rule_offset_indexes(offsets, rules)
248
- convert_transitions_to_hashes(offset_indexes, transitions)
249
- end
250
- end
251
-
252
- # Validates the rules offset against the offset of the last defined
253
- # transition. Replaces the transition with an equivalent using the rules
254
- # offset if the rules give a different definition for the base offset.
255
- def replace_last_transition_offset_if_valid_and_needed(file, transitions, offsets)
256
- last_transition = transitions.last
257
- last_offset = offsets[last_transition[:offset]]
258
- rule_offset = yield last_offset
259
-
260
- unless offset_matches_rule?(last_offset, rule_offset)
261
- raise InvalidZoneinfoFile, "Offset from POSIX-style TZ string does not match final transition in file '#{file.path}'."
262
- end
263
-
264
- # The total_utc_offset and abbreviation must always be the same. The
265
- # base utc_offset and std_offset might differ. In which case the rule
266
- # should be used as it will be more precise.
267
- last_offset[:utc_offset] = rule_offset.utc_offset
268
- last_transition
269
- end
270
-
271
- # todo: port over validate_and_fix_last_defined_transition_offset
272
- # when fixing the previous offset will need to define a new one
273
-
274
- # Validates the offset indicated to be observed by the rules before the
275
- # first generated transition against the offset of the last defined
276
- # transition.
277
- #
278
- # Fix the last defined transition if it differ on just base/std offsets
279
- # (which are derived). Raise an error if the observed UTC offset or
280
- # abbreviations differ.
281
- def validate_and_fix_last_defined_transition_offset(file, offsets, last_defined, first_rule_offset)
282
- offset_of_last_defined = offsets[last_defined[:offset]]
283
-
284
- if offset_equals_rule?(offset_of_last_defined, first_rule_offset)
285
- last_defined
286
- else
287
- if offset_matches_rule?(offset_of_last_defined, first_rule_offset)
288
- # The same overall offset, but differing in the base or std
289
- # offset (which are derived). Correct by using the rule.
290
-
291
- offset_index = get_rule_offset_index(offsets, first_rule_offset)
292
- {:at => last_defined[:at], :offset => offset_index}
293
- else
294
- raise InvalidZoneinfoFile, "The first offset indicated by the POSIX-style TZ string did not match the final defined offset in file '#{file.path}'."
295
- end
296
- end
297
- end
298
-
299
- # Apply the rules from the TZ string when there were defined transitions.
300
- # Checks for a matching offset with the last transition. Redefines the
301
- # last transition if required and if the rules don't specific a constant
302
- # offset, generates transitions until 100 years into the future (at the
303
- # time of loading zoneinfo_timezone_info.rb) or 2037 if limited to 32-bit
304
- # Times.
305
- def apply_rules_with_transitions(file, transitions, offsets, first_offset_index, rules)
306
- last_defined = transitions[-1]
307
-
308
- if rules.kind_of?(TimezoneOffset)
309
- transitions[-1] = validate_and_fix_last_defined_transition_offset(file, offsets, last_defined, rules)
310
- else
311
- previous_offset_index = transitions.length > 1 ? transitions[-2][:offset] : first_offset_index
312
- previous_offset = offsets[previous_offset_index]
313
- last_year = (Time.at(last_defined[:at]).utc + previous_offset[:utc_total_offset]).year
314
-
315
- if last_year <= GENERATE_UP_TO
316
- last_defined_offset = offsets[last_defined[:offset]]
317
-
318
- generated = rules.transitions(last_year).find_all do |t|
319
- t.at > last_defined[:at] && !offset_matches_rule?(last_defined_offset, t.offset)
320
- end
321
-
322
- generated += (last_year + 1).upto(GENERATE_UP_TO).map {|y| rules.transitions(y) }.flatten
323
-
324
- unless generated.empty?
325
- transitions[-1] = validate_and_fix_last_defined_transition_offset(file, offsets, last_defined, generated[0].previous_offset)
326
- rule_offset_indexes = get_rule_offset_indexes(offsets, rules)
327
- transitions.concat(convert_transitions_to_hashes(rule_offset_indexes, generated))
328
- end
329
- end
330
- end
331
- end
332
-
333
- # Defines an offset for the timezone based on the given index and offset
334
- # Hash.
335
- def define_offset(index, offset)
336
- utc_total_offset = offset[:utc_total_offset]
337
- utc_offset = offset[:utc_offset]
338
-
339
- if utc_offset
340
- # DST offset with base utc_offset derived by derive_offsets.
341
- std_offset = utc_total_offset - utc_offset
342
- elsif offset[:is_dst]
343
- # DST offset unreferenced by a transition (offset in use before the
344
- # first transition). No derived base UTC offset, so assume 1 hour
345
- # DST.
346
- utc_offset = utc_total_offset - 3600
347
- std_offset = 3600
348
- else
349
- # Non-DST offset.
350
- utc_offset = utc_total_offset
351
- std_offset = 0
352
- end
353
-
354
- offset index, utc_offset, std_offset, offset[:abbr].untaint.to_sym
355
- end
356
-
357
- # Parses a zoneinfo file and intializes the DataTimezoneInfo structures.
358
- def parse(file, posix_tz_parser)
359
- magic, version, ttisutccnt, ttisstdcnt, leapcnt, timecnt, typecnt, charcnt =
360
- check_read(file, 44).unpack('a4 a x15 NNNNNN')
361
-
362
- if magic != 'TZif'
363
- raise InvalidZoneinfoFile, "The file '#{file.path}' does not start with the expected header."
364
- end
365
-
366
- if version == '2' || version == '3'
367
- # Skip the first 32-bit section and read the header of the second
368
- # 64-bit section. The 64-bit section is always used even if the
369
- # runtime platform doesn't support 64-bit timestamps. In "slim" format
370
- # zoneinfo files the 32-bit section will be empty.
371
- file.seek(timecnt * 5 + typecnt * 6 + charcnt + leapcnt * 8 + ttisstdcnt + ttisutccnt, IO::SEEK_CUR)
372
-
373
- prev_version = version
374
-
375
- magic, version, ttisutccnt, ttisstdcnt, leapcnt, timecnt, typecnt, charcnt =
376
- check_read(file, 44).unpack('a4 a x15 NNNNNN')
377
-
378
- unless magic == 'TZif' && (version == prev_version)
379
- raise InvalidZoneinfoFile, "The file '#{file.path}' contains an invalid 64-bit section header."
380
- end
381
-
382
- using_64bit = true
383
- elsif version != '3' && version != '2' && version != "\0"
384
- raise InvalidZoneinfoFile, "The file '#{file.path}' contains a version of the zoneinfo format that is not currently supported."
385
- else
386
- using_64bit = false
387
- end
388
-
389
- unless leapcnt == 0
390
- raise InvalidZoneinfoFile, "The zoneinfo file '#{file.path}' contains leap second data. TZInfo requires zoneinfo files that omit leap seconds."
391
- end
392
-
393
- transitions = []
394
-
395
- if using_64bit
396
- timecnt.times do |i|
397
- high, low = check_read(file, 8).unpack('NN'.freeze)
398
- transition_time = make_signed_int64(high, low)
399
- transitions << {:at => transition_time}
400
- end
401
- else
402
- timecnt.times do |i|
403
- transition_time = make_signed_int32(check_read(file, 4).unpack('N'.freeze)[0])
404
- transitions << {:at => transition_time}
405
- end
406
- end
407
-
408
- timecnt.times do |i|
409
- localtime_type = check_read(file, 1).unpack('C'.freeze)[0]
410
- transitions[i][:offset] = localtime_type
411
- end
412
-
413
- offsets = []
414
-
415
- typecnt.times do |i|
416
- gmtoff, isdst, abbrind = check_read(file, 6).unpack('NCC'.freeze)
417
- gmtoff = make_signed_int32(gmtoff)
418
- isdst = isdst == 1
419
- offset = {:utc_total_offset => gmtoff, :is_dst => isdst, :abbr_index => abbrind}
420
-
421
- unless isdst
422
- offset[:utc_offset] = gmtoff
423
- end
424
-
425
- offsets << offset
426
- end
427
-
428
- abbrev = check_read(file, charcnt)
429
-
430
- if using_64bit
431
- # Skip to the POSIX-style TZ string.
432
- file.seek(ttisstdcnt + ttisutccnt, IO::SEEK_CUR) # + leapcnt * 8, but leapcnt is checked above and guaranteed to be 0.
433
- tz_string_start = check_read(file, 1)
434
- raise InvalidZoneinfoFile, "Expected newline starting POSIX-style TZ string in file '#{file.path}'." unless tz_string_start == "\n"
435
- tz_string = RubyCoreSupport.force_encoding(file.readline("\n"), 'UTF-8')
436
- raise InvalidZoneinfoFile, "Expected newline ending POSIX-style TZ string in file '#{file.path}'." unless tz_string.chomp!("\n")
437
-
438
- begin
439
- rules = posix_tz_parser.parse(tz_string)
440
- rescue InvalidPosixTimeZone => e
441
- raise InvalidZoneinfoFile, "Failed to parse POSIX-style TZ string in file '#{file.path}': #{e}"
442
- end
443
- else
444
- rules = nil
445
- end
446
-
447
- offsets.each do |o|
448
- abbrev_start = o[:abbr_index]
449
- raise InvalidZoneinfoFile, "Abbreviation index is out of range in file '#{file.path}'" unless abbrev_start < abbrev.length
450
-
451
- abbrev_end = abbrev.index("\0", abbrev_start)
452
- raise InvalidZoneinfoFile, "Missing abbreviation null terminator in file '#{file.path}'" unless abbrev_end
453
-
454
- o[:abbr] = RubyCoreSupport.force_encoding(abbrev[abbrev_start...abbrev_end], 'UTF-8')
455
- end
456
-
457
- transitions.each do |t|
458
- if t[:offset] < 0 || t[:offset] >= offsets.length
459
- raise InvalidZoneinfoFile, "Invalid offset referenced by transition in file '#{file.path}'."
460
- end
461
- end
462
-
463
- # Derive the offsets from standard time (std_offset).
464
- first_offset_index = derive_offsets(transitions, offsets)
465
-
466
- # Filter out transitions that are not supported by Time on this
467
- # platform.
468
- unless transitions.empty?
469
- if !RubyCoreSupport.time_supports_negative
470
- transitions = remove_unsupported_negative_transitions(transitions, 0)
471
- elsif !RubyCoreSupport.time_supports_64bit
472
- transitions = remove_unsupported_negative_transitions(transitions, -2**31)
473
- else
474
- # Ignore transitions that occur outside of a defined window. The
475
- # transition index cannot handle a large range of transition times.
476
- #
477
- # This is primarily intended to ignore the far in the past
478
- # transition added in zic 2014c (at timestamp -2**63 in zic 2014c
479
- # and at the approximate time of the big bang from zic 2014d).
480
- #
481
- # Assumes MIN_TIMESTAMP is less than -2**31.
482
- transitions = remove_unsupported_negative_transitions(transitions, MIN_TIMESTAMP)
483
- end
484
-
485
- if !RubyCoreSupport.time_supports_64bit
486
- i = transitions.find_index {|t| t[:at] >= 2**31 }
487
- had_later_transition = !!i
488
- transitions = transitions.first(i) if i
489
- else
490
- had_later_transition = false
491
- end
492
- end
493
-
494
- if rules && !had_later_transition
495
- if transitions.empty?
496
- transitions = apply_rules_without_transitions(file, offsets, first_offset_index, rules)
497
- else
498
- apply_rules_with_transitions(file, transitions, offsets, first_offset_index, rules)
499
- end
500
- end
501
-
502
- define_offset(first_offset_index, offsets[first_offset_index])
503
-
504
- used_offset_indexes = transitions.map {|t| t[:offset] }.to_set
505
-
506
- offsets.each_with_index do |o, i|
507
- define_offset(i, o) if i != first_offset_index && used_offset_indexes.include?(i)
508
- end
509
-
510
- # Ignore transitions that occur outside of a defined window. The
511
- # transition index cannot handle a large range of transition times.
512
- transitions.each do |t|
513
- at = t[:at]
514
- break if at >= MAX_TIMESTAMP
515
- time = Time.at(at).utc
516
- transition time.year, time.mon, t[:offset], at
517
- end
518
- end
519
- end
520
- end
@@ -1 +0,0 @@
1
- raise 'This should never be executed'
@@ -1,95 +0,0 @@
1
- require File.join(File.expand_path(File.dirname(__FILE__)), 'test_utils')
2
-
3
- include TZInfo
4
-
5
- class TCAnnualRules < Minitest::Test
6
-
7
- def test_initialize
8
- std_offset = TimezoneOffset.new(0, 0, 'GMT')
9
- dst_offset = TimezoneOffset.new(0, 3600, 'BST')
10
- dst_start_rule = FakeAlwaysDateAdjustmentRule.new(3, 1)
11
- dst_end_rule = FakeAlwaysDateAdjustmentRule.new(10, 1)
12
-
13
- rules = AnnualRules.new(std_offset, dst_offset, dst_start_rule, dst_end_rule)
14
- assert_same(std_offset, rules.std_offset)
15
- assert_same(dst_offset, rules.dst_offset)
16
- assert_same(dst_start_rule, rules.dst_start_rule)
17
- assert_same(dst_end_rule, rules.dst_end_rule)
18
- end
19
-
20
- [2020, 2021].each do |year|
21
- define_method "test_transitions_for_dst_mid_year_#{year}" do
22
- std_offset = TimezoneOffset.new(3600, 0, 'TEST')
23
- dst_offset = TimezoneOffset.new(3600, 3600, 'TESTS')
24
-
25
- rules = AnnualRules.new(
26
- std_offset,
27
- dst_offset,
28
- FakeAlwaysDateAdjustmentRule.new(3, 21),
29
- FakeAlwaysDateAdjustmentRule.new(10, 22)
30
- )
31
-
32
- result = rules.transitions(year)
33
-
34
- expected = [
35
- AnnualRules::Transition.new(dst_offset, std_offset, TimeOrDateTime.wrap(Time.utc(year, 3, 21, 0, 0, 0) - 3600)),
36
- AnnualRules::Transition.new(std_offset, dst_offset, TimeOrDateTime.wrap(Time.utc(year, 10, 22, 0, 0, 0) - 7200))
37
- ]
38
-
39
- assert_equal(expected, result)
40
- end
41
-
42
- define_method "test_transitions_for_dst_start_and_end_of_year_#{year}" do
43
- std_offset = TimezoneOffset.new(3600, 0, 'TEST')
44
- dst_offset = TimezoneOffset.new(3600, 3600, 'TESTS')
45
-
46
- rules = AnnualRules.new(
47
- std_offset,
48
- dst_offset,
49
- FakeAlwaysDateAdjustmentRule.new(10, 22),
50
- FakeAlwaysDateAdjustmentRule.new(3, 21)
51
- )
52
-
53
- result = rules.transitions(year)
54
-
55
- expected = [
56
- AnnualRules::Transition.new(std_offset, dst_offset, TimeOrDateTime.wrap(Time.utc(year, 3, 21, 0, 0, 0) - 7200)),
57
- AnnualRules::Transition.new(dst_offset, std_offset, TimeOrDateTime.wrap(Time.utc(year, 10, 22, 0, 0, 0) - 3600))
58
- ]
59
-
60
- assert_equal(expected, result)
61
- end
62
-
63
- define_method "test_transitions_for_negative_dst_start_and_end_of_year_#{year}" do
64
- std_offset = TimezoneOffset.new(7200, 0, 'TEST')
65
- dst_offset = TimezoneOffset.new(7200, -3600, 'TESTW')
66
-
67
- rules = AnnualRules.new(
68
- std_offset,
69
- dst_offset,
70
- FakeAlwaysDateAdjustmentRule.new(10, 22),
71
- FakeAlwaysDateAdjustmentRule.new(3, 21)
72
- )
73
-
74
- result = rules.transitions(year)
75
-
76
- expected = [
77
- AnnualRules::Transition.new(std_offset, dst_offset, TimeOrDateTime.wrap(Time.utc(year, 3, 21, 0, 0, 0) - 3600)),
78
- AnnualRules::Transition.new(dst_offset, std_offset, TimeOrDateTime.wrap(Time.utc(year, 10, 22, 0, 0, 0) - 7200))
79
- ]
80
-
81
- assert_equal(expected, result)
82
- end
83
- end
84
-
85
- class FakeAlwaysDateAdjustmentRule
86
- def initialize(month, day)
87
- @month = month
88
- @day = day
89
- end
90
-
91
- def at(offset, year)
92
- TimeOrDateTime.wrap(Time.utc(year, @month, @day, 0, 0, 0) - offset.utc_total_offset)
93
- end
94
- end
95
- end