tzinfo 1.2.7 → 2.0.2
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.tar.gz.sig +0 -0
- data/.yardopts +3 -0
- data/CHANGES.md +489 -382
- data/LICENSE +12 -12
- data/README.md +368 -114
- data/lib/tzinfo.rb +59 -29
- data/lib/tzinfo/country.rb +141 -129
- data/lib/tzinfo/country_timezone.rb +70 -112
- data/lib/tzinfo/data_source.rb +389 -144
- data/lib/tzinfo/data_sources.rb +8 -0
- 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 +145 -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 +577 -0
- data/lib/tzinfo/data_sources/zoneinfo_reader.rb +288 -0
- data/lib/tzinfo/data_timezone.rb +33 -47
- data/lib/tzinfo/datetime_with_offset.rb +153 -0
- data/lib/tzinfo/format1.rb +10 -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/format2.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/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 +989 -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/untaint_ext.rb +18 -0
- data/lib/tzinfo/version.rb +7 -0
- data/lib/tzinfo/with_offset.rb +61 -0
- metadata +42 -98
- metadata.gz.sig +0 -0
- data/Rakefile +0 -107
- 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/ruby_core_support.rb +0 -169
- data/lib/tzinfo/ruby_country_info.rb +0 -74
- data/lib/tzinfo/ruby_data_source.rb +0 -140
- data/lib/tzinfo/time_or_datetime.rb +0 -340
- 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/zoneinfo_country_info.rb +0 -37
- data/lib/tzinfo/zoneinfo_data_source.rb +0 -496
- data/lib/tzinfo/zoneinfo_timezone_info.rb +0 -300
- data/test/tc_country.rb +0 -238
- 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_ruby_core_support.rb +0 -168
- data/test/tc_ruby_country_info.rb +0 -110
- data/test/tc_ruby_data_source.rb +0 -167
- data/test/tc_time_or_datetime.rb +0 -660
- 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_zoneinfo_country_info.rb +0 -78
- data/test/tc_zoneinfo_data_source.rb +0 -1204
- data/test/tc_zoneinfo_timezone_info.rb +0 -1236
- data/test/test_utils.rb +0 -192
- 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.rb +0 -8
- 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 -315
- data/test/tzinfo-data/tzinfo/data/definitions/Australia/Melbourne.rb +0 -218
- 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 -261
- data/test/tzinfo-data/tzinfo/data/definitions/Europe/Andorra.rb +0 -186
- data/test/tzinfo-data/tzinfo/data/definitions/Europe/London.rb +0 -321
- data/test/tzinfo-data/tzinfo/data/definitions/Europe/Paris.rb +0 -265
- data/test/tzinfo-data/tzinfo/data/definitions/Europe/Prague.rb +0 -220
- data/test/tzinfo-data/tzinfo/data/definitions/UTC.rb +0 -16
- data/test/tzinfo-data/tzinfo/data/indexes/countries.rb +0 -927
- data/test/tzinfo-data/tzinfo/data/indexes/timezones.rb +0 -596
- data/test/tzinfo-data/tzinfo/data/version.rb +0 -14
- 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 -275
- data/test/zoneinfo/leapseconds +0 -61
- 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 -439
- data/test/zoneinfo/zone1970.tab +0 -369
- data/tzinfo.gemspec +0 -21
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
# encoding: UTF-8
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
module TZInfo
|
|
5
|
+
# Use send as a workaround for erroneous 'wrong number of arguments' errors
|
|
6
|
+
# with JRuby 9.0.5.0 when calling methods with Java implementations. See #114.
|
|
7
|
+
send(:using, UntaintExt) if TZInfo.const_defined?(:UntaintExt)
|
|
8
|
+
|
|
9
|
+
module DataSources
|
|
10
|
+
# An {InvalidZoneinfoFile} exception is raised if an attempt is made to load
|
|
11
|
+
# an invalid zoneinfo file.
|
|
12
|
+
class InvalidZoneinfoFile < StandardError
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# Reads compiled zoneinfo TZif (\0, 2 or 3) files.
|
|
16
|
+
class ZoneinfoReader #:nodoc:
|
|
17
|
+
# Initializes a new {ZoneinfoReader}.
|
|
18
|
+
#
|
|
19
|
+
# @param string_deduper [StringDeduper] a {StringDeduper} instance to use
|
|
20
|
+
# when deduping abbreviations.
|
|
21
|
+
def initialize(string_deduper)
|
|
22
|
+
@string_deduper = string_deduper
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Reads a zoneinfo structure from the given path. Returns either a
|
|
26
|
+
# {TimezoneOffset} that is constantly observed or an `Array`
|
|
27
|
+
# {TimezoneTransition}s.
|
|
28
|
+
#
|
|
29
|
+
# @param file_path [String] the path of a zoneinfo file.
|
|
30
|
+
# @return [Object] either a {TimezoneOffset} or an `Array` of
|
|
31
|
+
# {TimezoneTransition}s.
|
|
32
|
+
# @raise [SecurityError] if safe mode is enabled and `file_path` is
|
|
33
|
+
# tainted.
|
|
34
|
+
# @raise [InvalidZoneinfoFile] if `file_path`` does not refer to a valid
|
|
35
|
+
# zoneinfo file.
|
|
36
|
+
def read(file_path)
|
|
37
|
+
File.open(file_path, 'rb') { |file| parse(file) }
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
private
|
|
41
|
+
|
|
42
|
+
# Translates an unsigned 32-bit integer (as returned by unpack) to signed
|
|
43
|
+
# 32-bit.
|
|
44
|
+
#
|
|
45
|
+
# @param long [Integer] an unsigned 32-bit integer.
|
|
46
|
+
# @return [Integer] {long} translated to signed 32-bit.
|
|
47
|
+
def make_signed_int32(long)
|
|
48
|
+
long >= 0x80000000 ? long - 0x100000000 : long
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Translates a pair of unsigned 32-bit integers (as returned by unpack,
|
|
52
|
+
# most significant first) to a signed 64-bit integer.
|
|
53
|
+
#
|
|
54
|
+
# @param high [Integer] the most significant 32-bits.
|
|
55
|
+
# @param low [Integer] the least significant 32-bits.
|
|
56
|
+
# @return [Integer] {high} and {low} combined and translated to signed
|
|
57
|
+
# 64-bit.
|
|
58
|
+
def make_signed_int64(high, low)
|
|
59
|
+
unsigned = (high << 32) | low
|
|
60
|
+
unsigned >= 0x8000000000000000 ? unsigned - 0x10000000000000000 : unsigned
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Reads the given number of bytes from the given file and checks that the
|
|
64
|
+
# correct number of bytes could be read.
|
|
65
|
+
#
|
|
66
|
+
# @param file [IO] the file to read from.
|
|
67
|
+
# @param bytes [Integer] the number of bytes to read.
|
|
68
|
+
# @return [String] the bytes that were read.
|
|
69
|
+
# @raise [InvalidZoneinfoFile] if the number of bytes available didn't
|
|
70
|
+
# match the number requested.
|
|
71
|
+
def check_read(file, bytes)
|
|
72
|
+
result = file.read(bytes)
|
|
73
|
+
|
|
74
|
+
unless result && result.length == bytes
|
|
75
|
+
raise InvalidZoneinfoFile, "Expected #{bytes} bytes reading '#{file.path}', but got #{result ? result.length : 0} bytes"
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
result
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Zoneinfo files don't include the offset from standard time (std_offset)
|
|
82
|
+
# for DST periods. Derive the base offset (base_utc_offset) where DST is
|
|
83
|
+
# observed from either the previous or next non-DST period.
|
|
84
|
+
#
|
|
85
|
+
# @param transitions [Array<Hash>] an `Array` of transition hashes.
|
|
86
|
+
# @param offsets [Array<Hash>] an `Array` of offset hashes.
|
|
87
|
+
# @return [Integer] the index of the offset to be used prior to the first
|
|
88
|
+
# transition.
|
|
89
|
+
def derive_offsets(transitions, offsets)
|
|
90
|
+
# The first non-DST offset (if there is one) is the offset observed
|
|
91
|
+
# before the first transition. Fall back to the first DST offset if
|
|
92
|
+
# there are no non-DST offsets.
|
|
93
|
+
first_non_dst_offset_index = offsets.index {|o| !o[:is_dst] }
|
|
94
|
+
first_offset_index = first_non_dst_offset_index || 0
|
|
95
|
+
return first_offset_index if transitions.empty?
|
|
96
|
+
|
|
97
|
+
# Determine the base_utc_offset of the next non-dst offset at each transition.
|
|
98
|
+
base_utc_offset_from_next = nil
|
|
99
|
+
|
|
100
|
+
transitions.reverse_each do |transition|
|
|
101
|
+
offset = offsets[transition[:offset]]
|
|
102
|
+
if offset[:is_dst]
|
|
103
|
+
transition[:base_utc_offset_from_next] = base_utc_offset_from_next if base_utc_offset_from_next
|
|
104
|
+
else
|
|
105
|
+
base_utc_offset_from_next = offset[:observed_utc_offset]
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
base_utc_offset_from_previous = first_non_dst_offset_index ? offsets[first_non_dst_offset_index][:observed_utc_offset] : nil
|
|
110
|
+
defined_offsets = {}
|
|
111
|
+
|
|
112
|
+
transitions.each do |transition|
|
|
113
|
+
offset_index = transition[:offset]
|
|
114
|
+
offset = offsets[offset_index]
|
|
115
|
+
observed_utc_offset = offset[:observed_utc_offset]
|
|
116
|
+
|
|
117
|
+
if offset[:is_dst]
|
|
118
|
+
base_utc_offset_from_next = transition[:base_utc_offset_from_next]
|
|
119
|
+
|
|
120
|
+
difference_to_previous = (observed_utc_offset - (base_utc_offset_from_previous || observed_utc_offset)).abs
|
|
121
|
+
difference_to_next = (observed_utc_offset - (base_utc_offset_from_next || observed_utc_offset)).abs
|
|
122
|
+
|
|
123
|
+
base_utc_offset = if difference_to_previous == 3600
|
|
124
|
+
base_utc_offset_from_previous
|
|
125
|
+
elsif difference_to_next == 3600
|
|
126
|
+
base_utc_offset_from_next
|
|
127
|
+
elsif difference_to_previous > 0 && difference_to_next > 0
|
|
128
|
+
difference_to_previous < difference_to_next ? base_utc_offset_from_previous : base_utc_offset_from_next
|
|
129
|
+
elsif difference_to_previous > 0
|
|
130
|
+
base_utc_offset_from_previous
|
|
131
|
+
elsif difference_to_next > 0
|
|
132
|
+
base_utc_offset_from_next
|
|
133
|
+
else
|
|
134
|
+
# No difference, assume a 1 hour offset from standard time.
|
|
135
|
+
observed_utc_offset - 3600
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
if !offset[:base_utc_offset]
|
|
139
|
+
offset[:base_utc_offset] = base_utc_offset
|
|
140
|
+
defined_offsets[offset] = offset_index
|
|
141
|
+
elsif offset[:base_utc_offset] != base_utc_offset
|
|
142
|
+
# An earlier transition has already derived a different
|
|
143
|
+
# base_utc_offset. Define a new offset or reuse an existing identically
|
|
144
|
+
# defined offset.
|
|
145
|
+
new_offset = offset.dup
|
|
146
|
+
new_offset[:base_utc_offset] = base_utc_offset
|
|
147
|
+
|
|
148
|
+
offset_index = defined_offsets[new_offset]
|
|
149
|
+
|
|
150
|
+
unless offset_index
|
|
151
|
+
offsets << new_offset
|
|
152
|
+
offset_index = offsets.length - 1
|
|
153
|
+
defined_offsets[new_offset] = offset_index
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
transition[:offset] = offset_index
|
|
157
|
+
end
|
|
158
|
+
else
|
|
159
|
+
base_utc_offset_from_previous = observed_utc_offset
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
first_offset_index
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
# Parses a zoneinfo file and returns either a {TimezoneOffset} that is
|
|
167
|
+
# constantly observed or an `Array` of {TimezoneTransition}s.
|
|
168
|
+
#
|
|
169
|
+
# @param file [IO] the file to be read.
|
|
170
|
+
# @return [Object] either a {TimezoneOffset} or an `Array` of
|
|
171
|
+
# {TimezoneTransition}s.
|
|
172
|
+
# @raise [InvalidZoneinfoFile] if the file is not a valid zoneinfo file.
|
|
173
|
+
def parse(file)
|
|
174
|
+
magic, version, ttisgmtcnt, ttisstdcnt, leapcnt, timecnt, typecnt, charcnt =
|
|
175
|
+
check_read(file, 44).unpack('a4 a x15 NNNNNN')
|
|
176
|
+
|
|
177
|
+
if magic != 'TZif'
|
|
178
|
+
raise InvalidZoneinfoFile, "The file '#{file.path}' does not start with the expected header."
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
if version == '2' || version == '3'
|
|
182
|
+
# Skip the first 32-bit section and read the header of the second 64-bit section
|
|
183
|
+
file.seek(timecnt * 5 + typecnt * 6 + charcnt + leapcnt * 8 + ttisgmtcnt + ttisstdcnt, IO::SEEK_CUR)
|
|
184
|
+
|
|
185
|
+
prev_version = version
|
|
186
|
+
|
|
187
|
+
magic, version, ttisgmtcnt, ttisstdcnt, leapcnt, timecnt, typecnt, charcnt =
|
|
188
|
+
check_read(file, 44).unpack('a4 a x15 NNNNNN')
|
|
189
|
+
|
|
190
|
+
unless magic == 'TZif' && (version == prev_version)
|
|
191
|
+
raise InvalidZoneinfoFile, "The file '#{file.path}' contains an invalid 64-bit section header."
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
using_64bit = true
|
|
195
|
+
elsif version != '3' && version != '2' && version != "\0"
|
|
196
|
+
raise InvalidZoneinfoFile, "The file '#{file.path}' contains a version of the zoneinfo format that is not currently supported."
|
|
197
|
+
else
|
|
198
|
+
using_64bit = false
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
unless leapcnt == 0
|
|
202
|
+
raise InvalidZoneinfoFile, "The file '#{file.path}' contains leap second data. TZInfo requires zoneinfo files that omit leap seconds."
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
transitions = if using_64bit
|
|
206
|
+
timecnt.times.map do |i|
|
|
207
|
+
high, low = check_read(file, 8).unpack('NN'.freeze)
|
|
208
|
+
transition_time = make_signed_int64(high, low)
|
|
209
|
+
{at: transition_time}
|
|
210
|
+
end
|
|
211
|
+
else
|
|
212
|
+
timecnt.times.map do |i|
|
|
213
|
+
transition_time = make_signed_int32(check_read(file, 4).unpack('N'.freeze)[0])
|
|
214
|
+
{at: transition_time}
|
|
215
|
+
end
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
check_read(file, timecnt).unpack('C*'.freeze).each_with_index do |localtime_type, i|
|
|
219
|
+
raise InvalidZoneinfoFile, "Invalid offset referenced by transition in file '#{file.path}'." if localtime_type >= typecnt
|
|
220
|
+
transitions[i][:offset] = localtime_type
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
offsets = typecnt.times.map do |i|
|
|
224
|
+
gmtoff, isdst, abbrind = check_read(file, 6).unpack('NCC'.freeze)
|
|
225
|
+
gmtoff = make_signed_int32(gmtoff)
|
|
226
|
+
isdst = isdst == 1
|
|
227
|
+
{observed_utc_offset: gmtoff, is_dst: isdst, abbr_index: abbrind}
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
abbrev = check_read(file, charcnt)
|
|
231
|
+
|
|
232
|
+
# Derive the offsets from standard time (std_offset).
|
|
233
|
+
first_offset_index = derive_offsets(transitions, offsets)
|
|
234
|
+
|
|
235
|
+
offsets = offsets.map do |o|
|
|
236
|
+
observed_utc_offset = o[:observed_utc_offset]
|
|
237
|
+
base_utc_offset = o[:base_utc_offset]
|
|
238
|
+
|
|
239
|
+
if base_utc_offset
|
|
240
|
+
# DST offset with base_utc_offset derived by derive_offsets.
|
|
241
|
+
std_offset = observed_utc_offset - base_utc_offset
|
|
242
|
+
elsif o[:is_dst]
|
|
243
|
+
# DST offset unreferenced by a transition (offset in use before the
|
|
244
|
+
# first transition). No derived base UTC offset, so assume 1 hour
|
|
245
|
+
# DST.
|
|
246
|
+
base_utc_offset = observed_utc_offset - 3600
|
|
247
|
+
std_offset = 3600
|
|
248
|
+
else
|
|
249
|
+
# Non-DST offset.
|
|
250
|
+
base_utc_offset = observed_utc_offset
|
|
251
|
+
std_offset = 0
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
abbrev_start = o[:abbr_index]
|
|
255
|
+
raise InvalidZoneinfoFile, "Abbreviation index is out of range in file '#{file.path}'." unless abbrev_start < abbrev.length
|
|
256
|
+
|
|
257
|
+
abbrev_end = abbrev.index("\0", abbrev_start)
|
|
258
|
+
raise InvalidZoneinfoFile, "Missing abbreviation null terminator in file '#{file.path}'." unless abbrev_end
|
|
259
|
+
|
|
260
|
+
abbr = @string_deduper.dedupe(abbrev[abbrev_start...abbrev_end].force_encoding(Encoding::UTF_8).untaint)
|
|
261
|
+
|
|
262
|
+
TimezoneOffset.new(base_utc_offset, std_offset, abbr)
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
first_offset = offsets[first_offset_index]
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
if transitions.empty?
|
|
269
|
+
first_offset
|
|
270
|
+
else
|
|
271
|
+
previous_offset = first_offset
|
|
272
|
+
previous_at = nil
|
|
273
|
+
|
|
274
|
+
transitions.map do |t|
|
|
275
|
+
offset = offsets[t[:offset]]
|
|
276
|
+
at = t[:at]
|
|
277
|
+
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
|
|
278
|
+
tt = TimezoneTransition.new(offset, previous_offset, at)
|
|
279
|
+
previous_offset = offset
|
|
280
|
+
previous_at = at
|
|
281
|
+
tt
|
|
282
|
+
end
|
|
283
|
+
end
|
|
284
|
+
end
|
|
285
|
+
end
|
|
286
|
+
private_constant :ZoneinfoReader
|
|
287
|
+
end
|
|
288
|
+
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
|