tzinfo 1.2.5 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +1 -3
- data.tar.gz.sig +0 -0
- data/.yardopts +3 -0
- data/CHANGES.md +469 -377
- data/LICENSE +12 -12
- data/README.md +368 -113
- data/lib/tzinfo.rb +60 -37
- 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.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 +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_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 +979 -498
- 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
- metadata +62 -121
- metadata.gz.sig +2 -2
- 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 -146
- 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 -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 -488
- data/lib/tzinfo/zoneinfo_timezone_info.rb +0 -296
- data/test/tc_country.rb +0 -234
- 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 -143
- data/test/tc_time_or_datetime.rb +0 -654
- data/test/tc_timezone.rb +0 -1350
- 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 -423
- data/test/tc_zoneinfo_country_info.rb +0 -78
- data/test/tc_zoneinfo_data_source.rb +0 -1195
- data/test/tc_zoneinfo_timezone_info.rb +0 -1232
- data/test/test_utils.rb +0 -163
- data/test/ts_all.rb +0 -7
- data/test/ts_all_ruby.rb +0 -5
- data/test/ts_all_zoneinfo.rb +0 -7
- 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,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
|