tzinfo 1.2.11 → 2.0.0
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.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/.yardopts +3 -0
- data/CHANGES.md +469 -431
- data/LICENSE +13 -13
- data/README.md +368 -114
- data/lib/tzinfo/country.rb +131 -129
- data/lib/tzinfo/country_timezone.rb +70 -112
- data/lib/tzinfo/data_source.rb +389 -144
- data/lib/tzinfo/data_sources/constant_offset_data_timezone_info.rb +56 -0
- data/lib/tzinfo/data_sources/country_info.rb +42 -0
- data/lib/tzinfo/data_sources/data_timezone_info.rb +91 -0
- data/lib/tzinfo/data_sources/linked_timezone_info.rb +33 -0
- data/lib/tzinfo/data_sources/ruby_data_source.rb +141 -0
- data/lib/tzinfo/data_sources/timezone_info.rb +47 -0
- data/lib/tzinfo/data_sources/transitions_data_timezone_info.rb +214 -0
- data/lib/tzinfo/data_sources/zoneinfo_data_source.rb +573 -0
- data/lib/tzinfo/data_sources/zoneinfo_reader.rb +284 -0
- data/lib/tzinfo/data_sources.rb +8 -0
- data/lib/tzinfo/data_timezone.rb +33 -47
- data/lib/tzinfo/datetime_with_offset.rb +153 -0
- data/lib/tzinfo/format1/country_definer.rb +17 -0
- data/lib/tzinfo/format1/country_index_definition.rb +64 -0
- data/lib/tzinfo/format1/timezone_definer.rb +64 -0
- data/lib/tzinfo/format1/timezone_definition.rb +39 -0
- data/lib/tzinfo/format1/timezone_index_definition.rb +77 -0
- data/lib/tzinfo/format1.rb +10 -0
- data/lib/tzinfo/format2/country_definer.rb +68 -0
- data/lib/tzinfo/format2/country_index_definer.rb +68 -0
- data/lib/tzinfo/format2/country_index_definition.rb +46 -0
- data/lib/tzinfo/format2/timezone_definer.rb +94 -0
- data/lib/tzinfo/format2/timezone_definition.rb +73 -0
- data/lib/tzinfo/format2/timezone_index_definer.rb +45 -0
- data/lib/tzinfo/format2/timezone_index_definition.rb +55 -0
- data/lib/tzinfo/format2.rb +10 -0
- data/lib/tzinfo/info_timezone.rb +26 -21
- data/lib/tzinfo/linked_timezone.rb +33 -52
- data/lib/tzinfo/offset_timezone_period.rb +42 -0
- data/lib/tzinfo/string_deduper.rb +118 -0
- data/lib/tzinfo/time_with_offset.rb +128 -0
- data/lib/tzinfo/timestamp.rb +548 -0
- data/lib/tzinfo/timestamp_with_offset.rb +85 -0
- data/lib/tzinfo/timezone.rb +979 -502
- data/lib/tzinfo/timezone_offset.rb +84 -74
- data/lib/tzinfo/timezone_period.rb +151 -217
- data/lib/tzinfo/timezone_proxy.rb +70 -79
- data/lib/tzinfo/timezone_transition.rb +77 -109
- data/lib/tzinfo/transitions_timezone_period.rb +63 -0
- data/lib/tzinfo/version.rb +7 -0
- data/lib/tzinfo/with_offset.rb +61 -0
- data/lib/tzinfo.rb +60 -40
- data.tar.gz.sig +0 -0
- metadata +51 -115
- metadata.gz.sig +2 -3
- data/Rakefile +0 -107
- data/lib/tzinfo/annual_rules.rb +0 -51
- data/lib/tzinfo/country_index_definition.rb +0 -31
- data/lib/tzinfo/country_info.rb +0 -42
- data/lib/tzinfo/data_timezone_info.rb +0 -55
- data/lib/tzinfo/linked_timezone_info.rb +0 -26
- data/lib/tzinfo/offset_rationals.rb +0 -77
- data/lib/tzinfo/posix_time_zone_parser.rb +0 -136
- data/lib/tzinfo/ruby_core_support.rb +0 -176
- data/lib/tzinfo/ruby_country_info.rb +0 -74
- data/lib/tzinfo/ruby_data_source.rb +0 -136
- data/lib/tzinfo/time_or_datetime.rb +0 -351
- data/lib/tzinfo/timezone_definition.rb +0 -36
- data/lib/tzinfo/timezone_index_definition.rb +0 -54
- data/lib/tzinfo/timezone_info.rb +0 -30
- data/lib/tzinfo/timezone_transition_definition.rb +0 -104
- data/lib/tzinfo/transition_data_timezone_info.rb +0 -274
- data/lib/tzinfo/transition_rule.rb +0 -325
- data/lib/tzinfo/zoneinfo_country_info.rb +0 -37
- data/lib/tzinfo/zoneinfo_data_source.rb +0 -504
- data/lib/tzinfo/zoneinfo_timezone_info.rb +0 -516
- data/test/assets/payload.rb +0 -1
- data/test/tc_annual_rules.rb +0 -95
- data/test/tc_country.rb +0 -240
- data/test/tc_country_index_definition.rb +0 -69
- data/test/tc_country_info.rb +0 -16
- data/test/tc_country_timezone.rb +0 -173
- data/test/tc_data_source.rb +0 -218
- data/test/tc_data_timezone.rb +0 -99
- data/test/tc_data_timezone_info.rb +0 -18
- data/test/tc_info_timezone.rb +0 -34
- data/test/tc_linked_timezone.rb +0 -155
- data/test/tc_linked_timezone_info.rb +0 -23
- data/test/tc_offset_rationals.rb +0 -23
- data/test/tc_posix_time_zone_parser.rb +0 -261
- data/test/tc_ruby_core_support.rb +0 -168
- data/test/tc_ruby_country_info.rb +0 -110
- data/test/tc_ruby_data_source.rb +0 -175
- data/test/tc_time_or_datetime.rb +0 -674
- data/test/tc_timezone.rb +0 -1361
- data/test/tc_timezone_definition.rb +0 -113
- data/test/tc_timezone_index_definition.rb +0 -73
- data/test/tc_timezone_info.rb +0 -11
- data/test/tc_timezone_london.rb +0 -143
- data/test/tc_timezone_melbourne.rb +0 -142
- data/test/tc_timezone_new_york.rb +0 -142
- data/test/tc_timezone_offset.rb +0 -126
- data/test/tc_timezone_period.rb +0 -555
- data/test/tc_timezone_proxy.rb +0 -136
- data/test/tc_timezone_transition.rb +0 -366
- data/test/tc_timezone_transition_definition.rb +0 -295
- data/test/tc_timezone_utc.rb +0 -27
- data/test/tc_transition_data_timezone_info.rb +0 -433
- data/test/tc_transition_rule.rb +0 -663
- data/test/tc_zoneinfo_country_info.rb +0 -78
- data/test/tc_zoneinfo_data_source.rb +0 -1226
- data/test/tc_zoneinfo_timezone_info.rb +0 -2149
- data/test/test_utils.rb +0 -214
- data/test/ts_all.rb +0 -7
- data/test/ts_all_ruby.rb +0 -5
- data/test/ts_all_zoneinfo.rb +0 -9
- data/test/tzinfo-data/tzinfo/data/definitions/America/Argentina/Buenos_Aires.rb +0 -89
- data/test/tzinfo-data/tzinfo/data/definitions/America/New_York.rb +0 -327
- data/test/tzinfo-data/tzinfo/data/definitions/Australia/Melbourne.rb +0 -230
- data/test/tzinfo-data/tzinfo/data/definitions/EST.rb +0 -19
- data/test/tzinfo-data/tzinfo/data/definitions/Etc/GMT__m__1.rb +0 -21
- data/test/tzinfo-data/tzinfo/data/definitions/Etc/GMT__p__1.rb +0 -21
- data/test/tzinfo-data/tzinfo/data/definitions/Etc/UTC.rb +0 -21
- data/test/tzinfo-data/tzinfo/data/definitions/Europe/Amsterdam.rb +0 -273
- data/test/tzinfo-data/tzinfo/data/definitions/Europe/Andorra.rb +0 -198
- data/test/tzinfo-data/tzinfo/data/definitions/Europe/London.rb +0 -333
- data/test/tzinfo-data/tzinfo/data/definitions/Europe/Paris.rb +0 -277
- data/test/tzinfo-data/tzinfo/data/definitions/Europe/Prague.rb +0 -235
- data/test/tzinfo-data/tzinfo/data/definitions/UTC.rb +0 -16
- data/test/tzinfo-data/tzinfo/data/indexes/countries.rb +0 -940
- data/test/tzinfo-data/tzinfo/data/indexes/timezones.rb +0 -609
- data/test/tzinfo-data/tzinfo/data/version.rb +0 -20
- data/test/tzinfo-data/tzinfo/data.rb +0 -8
- data/test/zoneinfo/America/Argentina/Buenos_Aires +0 -0
- data/test/zoneinfo/America/New_York +0 -0
- data/test/zoneinfo/Australia/Melbourne +0 -0
- data/test/zoneinfo/EST +0 -0
- data/test/zoneinfo/Etc/UTC +0 -0
- data/test/zoneinfo/Europe/Amsterdam +0 -0
- data/test/zoneinfo/Europe/Andorra +0 -0
- data/test/zoneinfo/Europe/London +0 -0
- data/test/zoneinfo/Europe/Paris +0 -0
- data/test/zoneinfo/Europe/Prague +0 -0
- data/test/zoneinfo/Factory +0 -0
- data/test/zoneinfo/iso3166.tab +0 -274
- data/test/zoneinfo/leapseconds +0 -78
- data/test/zoneinfo/posix/Europe/London +0 -0
- data/test/zoneinfo/posixrules +0 -0
- data/test/zoneinfo/right/Europe/London +0 -0
- data/test/zoneinfo/zone.tab +0 -452
- data/test/zoneinfo/zone1970.tab +0 -384
- data/tzinfo.gemspec +0 -21
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
# encoding: UTF-8
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
module TZInfo
|
|
5
|
+
module DataSources
|
|
6
|
+
# An {InvalidZoneinfoFile} exception is raised if an attempt is made to load
|
|
7
|
+
# an invalid zoneinfo file.
|
|
8
|
+
class InvalidZoneinfoFile < StandardError
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
# Reads compiled zoneinfo TZif (\0, 2 or 3) files.
|
|
12
|
+
class ZoneinfoReader #:nodoc:
|
|
13
|
+
# Initializes a new {ZoneinfoReader}.
|
|
14
|
+
#
|
|
15
|
+
# @param string_deduper [StringDeduper] a {StringDeduper} instance to use
|
|
16
|
+
# when deduping abbreviations.
|
|
17
|
+
def initialize(string_deduper)
|
|
18
|
+
@string_deduper = string_deduper
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Reads a zoneinfo structure from the given path. Returns either a
|
|
22
|
+
# {TimezoneOffset} that is constantly observed or an `Array`
|
|
23
|
+
# {TimezoneTransition}s.
|
|
24
|
+
#
|
|
25
|
+
# @param file_path [String] the path of a zoneinfo file.
|
|
26
|
+
# @return [Object] either a {TimezoneOffset} or an `Array` of
|
|
27
|
+
# {TimezoneTransition}s.
|
|
28
|
+
# @raise [SecurityError] if safe mode is enabled and `file_path` is
|
|
29
|
+
# tainted.
|
|
30
|
+
# @raise [InvalidZoneinfoFile] if `file_path`` does not refer to a valid
|
|
31
|
+
# zoneinfo file.
|
|
32
|
+
def read(file_path)
|
|
33
|
+
File.open(file_path, 'rb') { |file| parse(file) }
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
private
|
|
37
|
+
|
|
38
|
+
# Translates an unsigned 32-bit integer (as returned by unpack) to signed
|
|
39
|
+
# 32-bit.
|
|
40
|
+
#
|
|
41
|
+
# @param long [Integer] an unsigned 32-bit integer.
|
|
42
|
+
# @return [Integer] {long} translated to signed 32-bit.
|
|
43
|
+
def make_signed_int32(long)
|
|
44
|
+
long >= 0x80000000 ? long - 0x100000000 : long
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Translates a pair of unsigned 32-bit integers (as returned by unpack,
|
|
48
|
+
# most significant first) to a signed 64-bit integer.
|
|
49
|
+
#
|
|
50
|
+
# @param high [Integer] the most significant 32-bits.
|
|
51
|
+
# @param low [Integer] the least significant 32-bits.
|
|
52
|
+
# @return [Integer] {high} and {low} combined and translated to signed
|
|
53
|
+
# 64-bit.
|
|
54
|
+
def make_signed_int64(high, low)
|
|
55
|
+
unsigned = (high << 32) | low
|
|
56
|
+
unsigned >= 0x8000000000000000 ? unsigned - 0x10000000000000000 : unsigned
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Reads the given number of bytes from the given file and checks that the
|
|
60
|
+
# correct number of bytes could be read.
|
|
61
|
+
#
|
|
62
|
+
# @param file [IO] the file to read from.
|
|
63
|
+
# @param bytes [Integer] the number of bytes to read.
|
|
64
|
+
# @return [String] the bytes that were read.
|
|
65
|
+
# @raise [InvalidZoneinfoFile] if the number of bytes available didn't
|
|
66
|
+
# match the number requested.
|
|
67
|
+
def check_read(file, bytes)
|
|
68
|
+
result = file.read(bytes)
|
|
69
|
+
|
|
70
|
+
unless result && result.length == bytes
|
|
71
|
+
raise InvalidZoneinfoFile, "Expected #{bytes} bytes reading '#{file.path}', but got #{result ? result.length : 0} bytes"
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
result
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Zoneinfo files don't include the offset from standard time (std_offset)
|
|
78
|
+
# for DST periods. Derive the base offset (base_utc_offset) where DST is
|
|
79
|
+
# observed from either the previous or next non-DST period.
|
|
80
|
+
#
|
|
81
|
+
# @param transitions [Array<Hash>] an `Array` of transition hashes.
|
|
82
|
+
# @param offsets [Array<Hash>] an `Array` of offset hashes.
|
|
83
|
+
# @return [Integer] the index of the offset to be used prior to the first
|
|
84
|
+
# transition.
|
|
85
|
+
def derive_offsets(transitions, offsets)
|
|
86
|
+
# The first non-DST offset (if there is one) is the offset observed
|
|
87
|
+
# before the first transition. Fall back to the first DST offset if
|
|
88
|
+
# there are no non-DST offsets.
|
|
89
|
+
first_non_dst_offset_index = offsets.index {|o| !o[:is_dst] }
|
|
90
|
+
first_offset_index = first_non_dst_offset_index || 0
|
|
91
|
+
return first_offset_index if transitions.empty?
|
|
92
|
+
|
|
93
|
+
# Determine the base_utc_offset of the next non-dst offset at each transition.
|
|
94
|
+
base_utc_offset_from_next = nil
|
|
95
|
+
|
|
96
|
+
transitions.reverse_each do |transition|
|
|
97
|
+
offset = offsets[transition[:offset]]
|
|
98
|
+
if offset[:is_dst]
|
|
99
|
+
transition[:base_utc_offset_from_next] = base_utc_offset_from_next if base_utc_offset_from_next
|
|
100
|
+
else
|
|
101
|
+
base_utc_offset_from_next = offset[:observed_utc_offset]
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
base_utc_offset_from_previous = first_non_dst_offset_index ? offsets[first_non_dst_offset_index][:observed_utc_offset] : nil
|
|
106
|
+
defined_offsets = {}
|
|
107
|
+
|
|
108
|
+
transitions.each do |transition|
|
|
109
|
+
offset_index = transition[:offset]
|
|
110
|
+
offset = offsets[offset_index]
|
|
111
|
+
observed_utc_offset = offset[:observed_utc_offset]
|
|
112
|
+
|
|
113
|
+
if offset[:is_dst]
|
|
114
|
+
base_utc_offset_from_next = transition[:base_utc_offset_from_next]
|
|
115
|
+
|
|
116
|
+
difference_to_previous = (observed_utc_offset - (base_utc_offset_from_previous || observed_utc_offset)).abs
|
|
117
|
+
difference_to_next = (observed_utc_offset - (base_utc_offset_from_next || observed_utc_offset)).abs
|
|
118
|
+
|
|
119
|
+
base_utc_offset = if difference_to_previous == 3600
|
|
120
|
+
base_utc_offset_from_previous
|
|
121
|
+
elsif difference_to_next == 3600
|
|
122
|
+
base_utc_offset_from_next
|
|
123
|
+
elsif difference_to_previous > 0 && difference_to_next > 0
|
|
124
|
+
difference_to_previous < difference_to_next ? base_utc_offset_from_previous : base_utc_offset_from_next
|
|
125
|
+
elsif difference_to_previous > 0
|
|
126
|
+
base_utc_offset_from_previous
|
|
127
|
+
elsif difference_to_next > 0
|
|
128
|
+
base_utc_offset_from_next
|
|
129
|
+
else
|
|
130
|
+
# No difference, assume a 1 hour offset from standard time.
|
|
131
|
+
observed_utc_offset - 3600
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
if !offset[:base_utc_offset]
|
|
135
|
+
offset[:base_utc_offset] = base_utc_offset
|
|
136
|
+
defined_offsets[offset] = offset_index
|
|
137
|
+
elsif offset[:base_utc_offset] != base_utc_offset
|
|
138
|
+
# An earlier transition has already derived a different
|
|
139
|
+
# base_utc_offset. Define a new offset or reuse an existing identically
|
|
140
|
+
# defined offset.
|
|
141
|
+
new_offset = offset.dup
|
|
142
|
+
new_offset[:base_utc_offset] = base_utc_offset
|
|
143
|
+
|
|
144
|
+
offset_index = defined_offsets[new_offset]
|
|
145
|
+
|
|
146
|
+
unless offset_index
|
|
147
|
+
offsets << new_offset
|
|
148
|
+
offset_index = offsets.length - 1
|
|
149
|
+
defined_offsets[new_offset] = offset_index
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
transition[:offset] = offset_index
|
|
153
|
+
end
|
|
154
|
+
else
|
|
155
|
+
base_utc_offset_from_previous = observed_utc_offset
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
first_offset_index
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
# Parses a zoneinfo file and returns either a {TimezoneOffset} that is
|
|
163
|
+
# constantly observed or an `Array` of {TimezoneTransition}s.
|
|
164
|
+
#
|
|
165
|
+
# @param file [IO] the file to be read.
|
|
166
|
+
# @return [Object] either a {TimezoneOffset} or an `Array` of
|
|
167
|
+
# {TimezoneTransition}s.
|
|
168
|
+
# @raise [InvalidZoneinfoFile] if the file is not a valid zoneinfo file.
|
|
169
|
+
def parse(file)
|
|
170
|
+
magic, version, ttisgmtcnt, ttisstdcnt, leapcnt, timecnt, typecnt, charcnt =
|
|
171
|
+
check_read(file, 44).unpack('a4 a x15 NNNNNN')
|
|
172
|
+
|
|
173
|
+
if magic != 'TZif'
|
|
174
|
+
raise InvalidZoneinfoFile, "The file '#{file.path}' does not start with the expected header."
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
if version == '2' || version == '3'
|
|
178
|
+
# Skip the first 32-bit section and read the header of the second 64-bit section
|
|
179
|
+
file.seek(timecnt * 5 + typecnt * 6 + charcnt + leapcnt * 8 + ttisgmtcnt + ttisstdcnt, IO::SEEK_CUR)
|
|
180
|
+
|
|
181
|
+
prev_version = version
|
|
182
|
+
|
|
183
|
+
magic, version, ttisgmtcnt, ttisstdcnt, leapcnt, timecnt, typecnt, charcnt =
|
|
184
|
+
check_read(file, 44).unpack('a4 a x15 NNNNNN')
|
|
185
|
+
|
|
186
|
+
unless magic == 'TZif' && (version == prev_version)
|
|
187
|
+
raise InvalidZoneinfoFile, "The file '#{file.path}' contains an invalid 64-bit section header."
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
using_64bit = true
|
|
191
|
+
elsif version != '3' && version != '2' && version != "\0"
|
|
192
|
+
raise InvalidZoneinfoFile, "The file '#{file.path}' contains a version of the zoneinfo format that is not currently supported."
|
|
193
|
+
else
|
|
194
|
+
using_64bit = false
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
unless leapcnt == 0
|
|
198
|
+
raise InvalidZoneinfoFile, "The file '#{file.path}' contains leap second data. TZInfo requires zoneinfo files that omit leap seconds."
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
transitions = if using_64bit
|
|
202
|
+
timecnt.times.map do |i|
|
|
203
|
+
high, low = check_read(file, 8).unpack('NN'.freeze)
|
|
204
|
+
transition_time = make_signed_int64(high, low)
|
|
205
|
+
{at: transition_time}
|
|
206
|
+
end
|
|
207
|
+
else
|
|
208
|
+
timecnt.times.map do |i|
|
|
209
|
+
transition_time = make_signed_int32(check_read(file, 4).unpack('N'.freeze)[0])
|
|
210
|
+
{at: transition_time}
|
|
211
|
+
end
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
check_read(file, timecnt).unpack('C*'.freeze).each_with_index do |localtime_type, i|
|
|
215
|
+
raise InvalidZoneinfoFile, "Invalid offset referenced by transition in file '#{file.path}'." if localtime_type >= typecnt
|
|
216
|
+
transitions[i][:offset] = localtime_type
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
offsets = typecnt.times.map do |i|
|
|
220
|
+
gmtoff, isdst, abbrind = check_read(file, 6).unpack('NCC'.freeze)
|
|
221
|
+
gmtoff = make_signed_int32(gmtoff)
|
|
222
|
+
isdst = isdst == 1
|
|
223
|
+
{observed_utc_offset: gmtoff, is_dst: isdst, abbr_index: abbrind}
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
abbrev = check_read(file, charcnt)
|
|
227
|
+
|
|
228
|
+
# Derive the offsets from standard time (std_offset).
|
|
229
|
+
first_offset_index = derive_offsets(transitions, offsets)
|
|
230
|
+
|
|
231
|
+
offsets = offsets.map do |o|
|
|
232
|
+
observed_utc_offset = o[:observed_utc_offset]
|
|
233
|
+
base_utc_offset = o[:base_utc_offset]
|
|
234
|
+
|
|
235
|
+
if base_utc_offset
|
|
236
|
+
# DST offset with base_utc_offset derived by derive_offsets.
|
|
237
|
+
std_offset = observed_utc_offset - base_utc_offset
|
|
238
|
+
elsif o[:is_dst]
|
|
239
|
+
# DST offset unreferenced by a transition (offset in use before the
|
|
240
|
+
# first transition). No derived base UTC offset, so assume 1 hour
|
|
241
|
+
# DST.
|
|
242
|
+
base_utc_offset = observed_utc_offset - 3600
|
|
243
|
+
std_offset = 3600
|
|
244
|
+
else
|
|
245
|
+
# Non-DST offset.
|
|
246
|
+
base_utc_offset = observed_utc_offset
|
|
247
|
+
std_offset = 0
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
abbrev_start = o[:abbr_index]
|
|
251
|
+
raise InvalidZoneinfoFile, "Abbreviation index is out of range in file '#{file.path}'." unless abbrev_start < abbrev.length
|
|
252
|
+
|
|
253
|
+
abbrev_end = abbrev.index("\0", abbrev_start)
|
|
254
|
+
raise InvalidZoneinfoFile, "Missing abbreviation null terminator in file '#{file.path}'." unless abbrev_end
|
|
255
|
+
|
|
256
|
+
abbr = @string_deduper.dedupe(abbrev[abbrev_start...abbrev_end].force_encoding(Encoding::UTF_8).untaint)
|
|
257
|
+
|
|
258
|
+
TimezoneOffset.new(base_utc_offset, std_offset, abbr)
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
first_offset = offsets[first_offset_index]
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
if transitions.empty?
|
|
265
|
+
first_offset
|
|
266
|
+
else
|
|
267
|
+
previous_offset = first_offset
|
|
268
|
+
previous_at = nil
|
|
269
|
+
|
|
270
|
+
transitions.map do |t|
|
|
271
|
+
offset = offsets[t[:offset]]
|
|
272
|
+
at = t[:at]
|
|
273
|
+
raise InvalidZoneinfoFile, "Transition at #{at} is not later than the previous transition at #{previous_at} in file '#{file.path}'." if previous_at && previous_at >= at
|
|
274
|
+
tt = TimezoneTransition.new(offset, previous_offset, at)
|
|
275
|
+
previous_offset = offset
|
|
276
|
+
previous_at = at
|
|
277
|
+
tt
|
|
278
|
+
end
|
|
279
|
+
end
|
|
280
|
+
end
|
|
281
|
+
end
|
|
282
|
+
private_constant :ZoneinfoReader
|
|
283
|
+
end
|
|
284
|
+
end
|
data/lib/tzinfo/data_timezone.rb
CHANGED
|
@@ -1,56 +1,42 @@
|
|
|
1
|
+
# encoding: UTF-8
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
1
4
|
module TZInfo
|
|
5
|
+
# Represents time zones that are defined by rules that set out when
|
|
6
|
+
# transitions occur.
|
|
7
|
+
class DataTimezone < InfoTimezone
|
|
8
|
+
# (see Timezone#period_for)
|
|
9
|
+
def period_for(time)
|
|
10
|
+
raise ArgumentError, 'time must be specified' unless time
|
|
11
|
+
timestamp = Timestamp.for(time)
|
|
12
|
+
raise ArgumentError, 'time must have a specified utc_offset' unless timestamp.utc_offset
|
|
13
|
+
info.period_for(timestamp)
|
|
14
|
+
end
|
|
2
15
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
# Returns the TimezonePeriod for the given UTC time. utc can either be
|
|
9
|
-
# a DateTime, Time or integer timestamp (Time.to_i). Any timezone
|
|
10
|
-
# information in utc is ignored (it is treated as a UTC time).
|
|
11
|
-
#
|
|
12
|
-
# If no TimezonePeriod could be found, PeriodNotFound is raised.
|
|
13
|
-
def period_for_utc(utc)
|
|
14
|
-
info.period_for_utc(utc)
|
|
16
|
+
# (see Timezone#periods_for_local)
|
|
17
|
+
def periods_for_local(local_time)
|
|
18
|
+
raise ArgumentError, 'local_time must be specified' unless local_time
|
|
19
|
+
info.periods_for_local(Timestamp.for(local_time, :ignore))
|
|
15
20
|
end
|
|
16
|
-
|
|
17
|
-
#
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
|
|
22
|
+
# (see Timezone#transitions_up_to)
|
|
23
|
+
def transitions_up_to(to, from = nil)
|
|
24
|
+
raise ArgumentError, 'to must be specified' unless to
|
|
25
|
+
to_timestamp = Timestamp.for(to)
|
|
26
|
+
from_timestamp = from && Timestamp.for(from)
|
|
27
|
+
|
|
28
|
+
begin
|
|
29
|
+
info.transitions_up_to(to_timestamp, from_timestamp)
|
|
30
|
+
rescue ArgumentError => e
|
|
31
|
+
raise ArgumentError, e.message.gsub('_timestamp', '')
|
|
32
|
+
end
|
|
23
33
|
end
|
|
24
|
-
|
|
25
|
-
# Returns
|
|
26
|
-
# where the UTC offset of the timezone changes.
|
|
27
|
-
#
|
|
28
|
-
# Transitions are returned up to a given date and time up to a given date
|
|
29
|
-
# and time, specified in UTC (utc_to).
|
|
30
|
-
#
|
|
31
|
-
# A from date and time may also be supplied using the utc_from parameter
|
|
32
|
-
# (also specified in UTC). If utc_from is not nil, only transitions from
|
|
33
|
-
# that date and time onwards will be returned.
|
|
34
|
-
#
|
|
35
|
-
# Comparisons with utc_to are exclusive. Comparisons with utc_from are
|
|
36
|
-
# inclusive. If a transition falls precisely on utc_to, it will be excluded.
|
|
37
|
-
# If a transition falls on utc_from, it will be included.
|
|
38
|
-
#
|
|
39
|
-
# Transitions returned are ordered by when they occur, from earliest to
|
|
40
|
-
# latest.
|
|
41
|
-
#
|
|
42
|
-
# utc_to and utc_from can be specified using either DateTime, Time or
|
|
43
|
-
# integer timestamps (Time.to_i).
|
|
34
|
+
|
|
35
|
+
# Returns the canonical {Timezone} instance for this {DataTimezone}.
|
|
44
36
|
#
|
|
45
|
-
#
|
|
46
|
-
# transitions_up_to raises an ArgumentError exception.
|
|
47
|
-
def transitions_up_to(utc_to, utc_from = nil)
|
|
48
|
-
info.transitions_up_to(utc_to, utc_from)
|
|
49
|
-
end
|
|
50
|
-
|
|
51
|
-
# Returns the canonical zone for this Timezone.
|
|
37
|
+
# For a {DataTimezone}, this is always `self`.
|
|
52
38
|
#
|
|
53
|
-
#
|
|
39
|
+
# @return [Timezone] `self`.
|
|
54
40
|
def canonical_zone
|
|
55
41
|
self
|
|
56
42
|
end
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
# encoding: UTF-8
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require 'date'
|
|
5
|
+
|
|
6
|
+
module TZInfo
|
|
7
|
+
# A subclass of `DateTime` used to represent local times. {DateTimeWithOffset}
|
|
8
|
+
# holds a reference to the related {TimezoneOffset} and overrides various
|
|
9
|
+
# methods to return results appropriate for the {TimezoneOffset}. Certain
|
|
10
|
+
# operations will clear the associated {TimezoneOffset} (if the
|
|
11
|
+
# {TimezoneOffset} would not necessarily be valid for the result). Once the
|
|
12
|
+
# {TimezoneOffset} has been cleared, {DateTimeWithOffset} behaves identically
|
|
13
|
+
# to `DateTime`.
|
|
14
|
+
#
|
|
15
|
+
# Arithmetic performed on {DateTimeWithOffset} instances is _not_ time
|
|
16
|
+
# zone-aware. Regardless of whether transitions in the time zone are crossed,
|
|
17
|
+
# results of arithmetic operations will always maintain the same offset from
|
|
18
|
+
# UTC (`offset`). The associated {TimezoneOffset} will aways be cleared.
|
|
19
|
+
class DateTimeWithOffset < DateTime
|
|
20
|
+
include WithOffset
|
|
21
|
+
|
|
22
|
+
# @return [TimezoneOffset] the {TimezoneOffset} associated with this
|
|
23
|
+
# instance.
|
|
24
|
+
attr_reader :timezone_offset
|
|
25
|
+
|
|
26
|
+
# Sets the associated {TimezoneOffset}.
|
|
27
|
+
#
|
|
28
|
+
# @param timezone_offset [TimezoneOffset] a {TimezoneOffset} valid at the
|
|
29
|
+
# time and for the offset of this {DateTimeWithOffset}.
|
|
30
|
+
# @return [DateTimeWithOffset] `self`.
|
|
31
|
+
# @raise [ArgumentError] if `timezone_offset` is `nil`.
|
|
32
|
+
# @raise [ArgumentError] if `timezone_offset.observed_utc_offset` does not
|
|
33
|
+
# equal `self.offset * 86400`.
|
|
34
|
+
def set_timezone_offset(timezone_offset)
|
|
35
|
+
raise ArgumentError, 'timezone_offset must be specified' unless timezone_offset
|
|
36
|
+
raise ArgumentError, 'timezone_offset.observed_utc_offset does not match self.utc_offset' if offset * 86400 != timezone_offset.observed_utc_offset
|
|
37
|
+
@timezone_offset = timezone_offset
|
|
38
|
+
self
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# An overridden version of `DateTime#to_time` that, if there is an
|
|
42
|
+
# associated {TimezoneOffset}, returns a {DateTimeWithOffset} with that
|
|
43
|
+
# offset.
|
|
44
|
+
#
|
|
45
|
+
# @return [Time] if there is an associated {TimezoneOffset}, a
|
|
46
|
+
# {TimeWithOffset} representation of this {DateTimeWithOffset}, otherwise
|
|
47
|
+
# a `Time` representation.
|
|
48
|
+
def to_time
|
|
49
|
+
if_timezone_offset(super) do |o,t|
|
|
50
|
+
# Ruby 2.4.0 changed the behaviour of to_time so that it preserves the
|
|
51
|
+
# offset instead of converting to the system local timezone.
|
|
52
|
+
#
|
|
53
|
+
# When self has an associated TimezonePeriod, this implementation will
|
|
54
|
+
# preserve the offset on all versions of Ruby.
|
|
55
|
+
TimeWithOffset.at(t.to_i, t.subsec * 1_000_000).set_timezone_offset(o)
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# An overridden version of `DateTime#downto` that clears the associated
|
|
60
|
+
# {TimezoneOffset} of the returned or yielded instances.
|
|
61
|
+
def downto(min)
|
|
62
|
+
if block_given?
|
|
63
|
+
super {|dt| yield dt.clear_timezone_offset }
|
|
64
|
+
else
|
|
65
|
+
enum = super
|
|
66
|
+
enum.each {|dt| dt.clear_timezone_offset }
|
|
67
|
+
enum
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# An overridden version of `DateTime#england` that preserves the associated
|
|
72
|
+
# {TimezoneOffset}.
|
|
73
|
+
#
|
|
74
|
+
# @return [DateTime]
|
|
75
|
+
def england
|
|
76
|
+
# super doesn't call #new_start on MRI, so each method has to be
|
|
77
|
+
# individually overridden.
|
|
78
|
+
if_timezone_offset(super) {|o,dt| dt.set_timezone_offset(o) }
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# An overridden version of `DateTime#gregorian` that preserves the
|
|
82
|
+
# associated {TimezoneOffset}.
|
|
83
|
+
#
|
|
84
|
+
# @return [DateTime]
|
|
85
|
+
def gregorian
|
|
86
|
+
# super doesn't call #new_start on MRI, so each method has to be
|
|
87
|
+
# individually overridden.
|
|
88
|
+
if_timezone_offset(super) {|o,dt| dt.set_timezone_offset(o) }
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# An overridden version of `DateTime#italy` that preserves the associated
|
|
92
|
+
# {TimezoneOffset}.
|
|
93
|
+
#
|
|
94
|
+
# @return [DateTime]
|
|
95
|
+
def italy
|
|
96
|
+
# super doesn't call #new_start on MRI, so each method has to be
|
|
97
|
+
# individually overridden.
|
|
98
|
+
if_timezone_offset(super) {|o,dt| dt.set_timezone_offset(o) }
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# An overridden version of `DateTime#julian` that preserves the associated
|
|
102
|
+
# {TimezoneOffset}.
|
|
103
|
+
#
|
|
104
|
+
# @return [DateTime]
|
|
105
|
+
def julian
|
|
106
|
+
# super doesn't call #new_start on MRI, so each method has to be
|
|
107
|
+
# individually overridden.
|
|
108
|
+
if_timezone_offset(super) {|o,dt| dt.set_timezone_offset(o) }
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# An overridden version of `DateTime#new_start` that preserves the
|
|
112
|
+
# associated {TimezoneOffset}.
|
|
113
|
+
#
|
|
114
|
+
# @return [DateTime]
|
|
115
|
+
def new_start(start = Date::ITALY)
|
|
116
|
+
if_timezone_offset(super) {|o,dt| dt.set_timezone_offset(o) }
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# An overridden version of `DateTime#step` that clears the associated
|
|
120
|
+
# {TimezoneOffset} of the returned or yielded instances.
|
|
121
|
+
def step(limit, step = 1)
|
|
122
|
+
if block_given?
|
|
123
|
+
super {|dt| yield dt.clear_timezone_offset }
|
|
124
|
+
else
|
|
125
|
+
enum = super
|
|
126
|
+
enum.each {|dt| dt.clear_timezone_offset }
|
|
127
|
+
enum
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
# An overridden version of `DateTime#upto` that clears the associated
|
|
132
|
+
# {TimezoneOffset} of the returned or yielded instances.
|
|
133
|
+
def upto(max)
|
|
134
|
+
if block_given?
|
|
135
|
+
super {|dt| yield dt.clear_timezone_offset }
|
|
136
|
+
else
|
|
137
|
+
enum = super
|
|
138
|
+
enum.each {|dt| dt.clear_timezone_offset }
|
|
139
|
+
enum
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
protected
|
|
144
|
+
|
|
145
|
+
# Clears the associated {TimezoneOffset}.
|
|
146
|
+
#
|
|
147
|
+
# @return [DateTimeWithOffset] `self`.
|
|
148
|
+
def clear_timezone_offset
|
|
149
|
+
@timezone_offset = nil
|
|
150
|
+
self
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# encoding: UTF-8
|
|
2
|
+
|
|
3
|
+
module TZInfo
|
|
4
|
+
module Format1
|
|
5
|
+
# Instances of {Format1::CountryDefiner} are yielded to the format 1 version
|
|
6
|
+
# of `TZInfo::Data::Indexes::Countries` by {CountryIndexDefinition} to allow
|
|
7
|
+
# the zones of a country to be specified.
|
|
8
|
+
#
|
|
9
|
+
# @private
|
|
10
|
+
class CountryDefiner < Format2::CountryDefiner #:nodoc:
|
|
11
|
+
# Initializes a new {CountryDefiner}.
|
|
12
|
+
def initialize(identifier_deduper, description_deduper)
|
|
13
|
+
super(nil, identifier_deduper, description_deduper)
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# encoding: UTF-8
|
|
2
|
+
|
|
3
|
+
module TZInfo
|
|
4
|
+
module Format1
|
|
5
|
+
# The format 1 TZInfo::Data country index file includes
|
|
6
|
+
# {Format1::CountryIndexDefinition}, which provides a
|
|
7
|
+
# {CountryIndexDefinition::ClassMethods#country country} method used to
|
|
8
|
+
# define each country in the index.
|
|
9
|
+
#
|
|
10
|
+
# @private
|
|
11
|
+
module CountryIndexDefinition #:nodoc:
|
|
12
|
+
# Adds class methods to the includee and initializes class instance
|
|
13
|
+
# variables.
|
|
14
|
+
#
|
|
15
|
+
# @param base [Module] the includee.
|
|
16
|
+
def self.append_features(base)
|
|
17
|
+
super
|
|
18
|
+
base.extend(ClassMethods)
|
|
19
|
+
base.instance_eval { @countries = {} }
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Class methods for inclusion.
|
|
23
|
+
#
|
|
24
|
+
# @private
|
|
25
|
+
module ClassMethods #:nodoc:
|
|
26
|
+
# @return [Hash<String, DataSources::CountryInfo>] a frozen `Hash`
|
|
27
|
+
# of all the countries that have been defined in the index keyed by
|
|
28
|
+
# their codes.
|
|
29
|
+
def countries
|
|
30
|
+
@description_deduper = nil
|
|
31
|
+
@countries.freeze
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
private
|
|
35
|
+
|
|
36
|
+
# Defines a country with an ISO 3166-1 alpha-2 country code and name.
|
|
37
|
+
#
|
|
38
|
+
# @param code [String] the ISO 3166-1 alpha-2 country code.
|
|
39
|
+
# @param name [String] the name of the country.
|
|
40
|
+
# @yield [definer] (optional) to obtain the time zones for the country.
|
|
41
|
+
# @yieldparam definer [CountryDefiner] a {CountryDefiner} instance.
|
|
42
|
+
def country(code, name)
|
|
43
|
+
@description_deduper ||= StringDeduper.new
|
|
44
|
+
|
|
45
|
+
zones = if block_given?
|
|
46
|
+
definer = CountryDefiner.new(StringDeduper.global, @description_deduper)
|
|
47
|
+
yield definer
|
|
48
|
+
definer.timezones
|
|
49
|
+
else
|
|
50
|
+
[]
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
@countries[code.freeze] = DataSources::CountryInfo.new(code, name, zones)
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Alias used by TZInfo::Data format1 releases.
|
|
60
|
+
#
|
|
61
|
+
# @private
|
|
62
|
+
CountryIndexDefinition = Format1::CountryIndexDefinition #:nodoc:
|
|
63
|
+
private_constant :CountryIndexDefinition
|
|
64
|
+
end
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# encoding: UTF-8
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
module TZInfo
|
|
5
|
+
module Format1
|
|
6
|
+
# Instances of {Format1::TimezoneDefiner} are yielded to TZInfo::Data
|
|
7
|
+
# format 1 modules by {TimezoneDefinition} to allow the offsets and
|
|
8
|
+
# transitions of the time zone to be specified.
|
|
9
|
+
#
|
|
10
|
+
# @private
|
|
11
|
+
class TimezoneDefiner < Format2::TimezoneDefiner #:nodoc:
|
|
12
|
+
undef_method :subsequent_rules
|
|
13
|
+
|
|
14
|
+
# Defines an offset.
|
|
15
|
+
#
|
|
16
|
+
# @param id [Symbol] an arbitrary value used identify the offset in
|
|
17
|
+
# subsequent calls to transition. It must be unique.
|
|
18
|
+
# @param utc_offset [Integer] the base offset from UTC of the zone in
|
|
19
|
+
# seconds. This does not include daylight savings time.
|
|
20
|
+
# @param std_offset [Integer] the daylight savings offset from the base
|
|
21
|
+
# offset in seconds. Typically either 0 or 3600.
|
|
22
|
+
# @param abbreviation [Symbol] an abbreviation for the offset, for
|
|
23
|
+
# example, `:EST` or `:EDT`.
|
|
24
|
+
# @raise [ArgumentError] if another offset has already been defined with
|
|
25
|
+
# the given id.
|
|
26
|
+
def offset(id, utc_offset, std_offset, abbreviation)
|
|
27
|
+
super(id, utc_offset, std_offset, abbreviation.to_s)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Defines a transition to a given offset.
|
|
31
|
+
#
|
|
32
|
+
# Transitions must be defined in increasing time order.
|
|
33
|
+
#
|
|
34
|
+
# @param year [Integer] the UTC year in which the transition occurs. Used
|
|
35
|
+
# in earlier versions of TZInfo, but now ignored.
|
|
36
|
+
# @param month [Integer] the UTC month in which the transition occurs.
|
|
37
|
+
# Used in earlier versions of TZInfo, but now ignored.
|
|
38
|
+
# @param offset_id [Symbol] references the id of a previously defined
|
|
39
|
+
# offset (see #offset).
|
|
40
|
+
# @param timestamp_value [Integer] the time the transition occurs as an
|
|
41
|
+
# Integer number of seconds since 1970-01-01 00:00:00 UTC ignoring leap
|
|
42
|
+
# seconds (i.e. each day is treated as if it were 86,400 seconds long).
|
|
43
|
+
# @param datetime_numerator [Integer] the time of the transition as the
|
|
44
|
+
# numerator of the `Rational` returned by `DateTime#ajd`. Used in
|
|
45
|
+
# earlier versions of TZInfo, but now ignored.
|
|
46
|
+
# @param datetime_denominator [Integer] the time of the transition as the
|
|
47
|
+
# denominator of the `Rational` returned by `DateTime#ajd`. Used in
|
|
48
|
+
# earlier versions of TZInfo, but now ignored.
|
|
49
|
+
# @raise [ArgumentError] if `offset_id` does not reference a defined
|
|
50
|
+
# offset.
|
|
51
|
+
# @raise [ArgumentError] if `timestamp_value` is not greater than the
|
|
52
|
+
# `timestamp_value` of the previously defined transition.
|
|
53
|
+
# @raise [ArgumentError] if `datetime_numerator` is specified, but
|
|
54
|
+
# `datetime_denominator` is not. In older versions of TZInfo, it was
|
|
55
|
+
# possible to define a transition with the `DateTime` numerator as the
|
|
56
|
+
# 4th parameter and the denominator as the 5th parameter. This style of
|
|
57
|
+
# definition is not used in released versions of TZInfo::Data.
|
|
58
|
+
def transition(year, month, offset_id, timestamp_value, datetime_numerator = nil, datetime_denominator = nil)
|
|
59
|
+
raise ArgumentError, 'DateTime-only transitions are not supported' if datetime_numerator && !datetime_denominator
|
|
60
|
+
super(offset_id, timestamp_value)
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|