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.
Files changed (145) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +0 -0
  4. data/.yardopts +3 -0
  5. data/CHANGES.md +489 -382
  6. data/LICENSE +12 -12
  7. data/README.md +368 -114
  8. data/lib/tzinfo.rb +59 -29
  9. data/lib/tzinfo/country.rb +141 -129
  10. data/lib/tzinfo/country_timezone.rb +70 -112
  11. data/lib/tzinfo/data_source.rb +389 -144
  12. data/lib/tzinfo/data_sources.rb +8 -0
  13. data/lib/tzinfo/data_sources/constant_offset_data_timezone_info.rb +56 -0
  14. data/lib/tzinfo/data_sources/country_info.rb +42 -0
  15. data/lib/tzinfo/data_sources/data_timezone_info.rb +91 -0
  16. data/lib/tzinfo/data_sources/linked_timezone_info.rb +33 -0
  17. data/lib/tzinfo/data_sources/ruby_data_source.rb +145 -0
  18. data/lib/tzinfo/data_sources/timezone_info.rb +47 -0
  19. data/lib/tzinfo/data_sources/transitions_data_timezone_info.rb +214 -0
  20. data/lib/tzinfo/data_sources/zoneinfo_data_source.rb +577 -0
  21. data/lib/tzinfo/data_sources/zoneinfo_reader.rb +288 -0
  22. data/lib/tzinfo/data_timezone.rb +33 -47
  23. data/lib/tzinfo/datetime_with_offset.rb +153 -0
  24. data/lib/tzinfo/format1.rb +10 -0
  25. data/lib/tzinfo/format1/country_definer.rb +17 -0
  26. data/lib/tzinfo/format1/country_index_definition.rb +64 -0
  27. data/lib/tzinfo/format1/timezone_definer.rb +64 -0
  28. data/lib/tzinfo/format1/timezone_definition.rb +39 -0
  29. data/lib/tzinfo/format1/timezone_index_definition.rb +77 -0
  30. data/lib/tzinfo/format2.rb +10 -0
  31. data/lib/tzinfo/format2/country_definer.rb +68 -0
  32. data/lib/tzinfo/format2/country_index_definer.rb +68 -0
  33. data/lib/tzinfo/format2/country_index_definition.rb +46 -0
  34. data/lib/tzinfo/format2/timezone_definer.rb +94 -0
  35. data/lib/tzinfo/format2/timezone_definition.rb +73 -0
  36. data/lib/tzinfo/format2/timezone_index_definer.rb +45 -0
  37. data/lib/tzinfo/format2/timezone_index_definition.rb +55 -0
  38. data/lib/tzinfo/info_timezone.rb +26 -21
  39. data/lib/tzinfo/linked_timezone.rb +33 -52
  40. data/lib/tzinfo/offset_timezone_period.rb +42 -0
  41. data/lib/tzinfo/string_deduper.rb +118 -0
  42. data/lib/tzinfo/time_with_offset.rb +128 -0
  43. data/lib/tzinfo/timestamp.rb +548 -0
  44. data/lib/tzinfo/timestamp_with_offset.rb +85 -0
  45. data/lib/tzinfo/timezone.rb +989 -502
  46. data/lib/tzinfo/timezone_offset.rb +84 -74
  47. data/lib/tzinfo/timezone_period.rb +151 -217
  48. data/lib/tzinfo/timezone_proxy.rb +70 -79
  49. data/lib/tzinfo/timezone_transition.rb +77 -109
  50. data/lib/tzinfo/transitions_timezone_period.rb +63 -0
  51. data/lib/tzinfo/untaint_ext.rb +18 -0
  52. data/lib/tzinfo/version.rb +7 -0
  53. data/lib/tzinfo/with_offset.rb +61 -0
  54. metadata +42 -98
  55. metadata.gz.sig +0 -0
  56. data/Rakefile +0 -107
  57. data/lib/tzinfo/country_index_definition.rb +0 -31
  58. data/lib/tzinfo/country_info.rb +0 -42
  59. data/lib/tzinfo/data_timezone_info.rb +0 -55
  60. data/lib/tzinfo/linked_timezone_info.rb +0 -26
  61. data/lib/tzinfo/offset_rationals.rb +0 -77
  62. data/lib/tzinfo/ruby_core_support.rb +0 -169
  63. data/lib/tzinfo/ruby_country_info.rb +0 -74
  64. data/lib/tzinfo/ruby_data_source.rb +0 -140
  65. data/lib/tzinfo/time_or_datetime.rb +0 -340
  66. data/lib/tzinfo/timezone_definition.rb +0 -36
  67. data/lib/tzinfo/timezone_index_definition.rb +0 -54
  68. data/lib/tzinfo/timezone_info.rb +0 -30
  69. data/lib/tzinfo/timezone_transition_definition.rb +0 -104
  70. data/lib/tzinfo/transition_data_timezone_info.rb +0 -274
  71. data/lib/tzinfo/zoneinfo_country_info.rb +0 -37
  72. data/lib/tzinfo/zoneinfo_data_source.rb +0 -496
  73. data/lib/tzinfo/zoneinfo_timezone_info.rb +0 -300
  74. data/test/tc_country.rb +0 -238
  75. data/test/tc_country_index_definition.rb +0 -69
  76. data/test/tc_country_info.rb +0 -16
  77. data/test/tc_country_timezone.rb +0 -173
  78. data/test/tc_data_source.rb +0 -218
  79. data/test/tc_data_timezone.rb +0 -99
  80. data/test/tc_data_timezone_info.rb +0 -18
  81. data/test/tc_info_timezone.rb +0 -34
  82. data/test/tc_linked_timezone.rb +0 -155
  83. data/test/tc_linked_timezone_info.rb +0 -23
  84. data/test/tc_offset_rationals.rb +0 -23
  85. data/test/tc_ruby_core_support.rb +0 -168
  86. data/test/tc_ruby_country_info.rb +0 -110
  87. data/test/tc_ruby_data_source.rb +0 -167
  88. data/test/tc_time_or_datetime.rb +0 -660
  89. data/test/tc_timezone.rb +0 -1361
  90. data/test/tc_timezone_definition.rb +0 -113
  91. data/test/tc_timezone_index_definition.rb +0 -73
  92. data/test/tc_timezone_info.rb +0 -11
  93. data/test/tc_timezone_london.rb +0 -143
  94. data/test/tc_timezone_melbourne.rb +0 -142
  95. data/test/tc_timezone_new_york.rb +0 -142
  96. data/test/tc_timezone_offset.rb +0 -126
  97. data/test/tc_timezone_period.rb +0 -555
  98. data/test/tc_timezone_proxy.rb +0 -136
  99. data/test/tc_timezone_transition.rb +0 -366
  100. data/test/tc_timezone_transition_definition.rb +0 -295
  101. data/test/tc_timezone_utc.rb +0 -27
  102. data/test/tc_transition_data_timezone_info.rb +0 -433
  103. data/test/tc_zoneinfo_country_info.rb +0 -78
  104. data/test/tc_zoneinfo_data_source.rb +0 -1204
  105. data/test/tc_zoneinfo_timezone_info.rb +0 -1236
  106. data/test/test_utils.rb +0 -192
  107. data/test/ts_all.rb +0 -7
  108. data/test/ts_all_ruby.rb +0 -5
  109. data/test/ts_all_zoneinfo.rb +0 -9
  110. data/test/tzinfo-data/tzinfo/data.rb +0 -8
  111. data/test/tzinfo-data/tzinfo/data/definitions/America/Argentina/Buenos_Aires.rb +0 -89
  112. data/test/tzinfo-data/tzinfo/data/definitions/America/New_York.rb +0 -315
  113. data/test/tzinfo-data/tzinfo/data/definitions/Australia/Melbourne.rb +0 -218
  114. data/test/tzinfo-data/tzinfo/data/definitions/EST.rb +0 -19
  115. data/test/tzinfo-data/tzinfo/data/definitions/Etc/GMT__m__1.rb +0 -21
  116. data/test/tzinfo-data/tzinfo/data/definitions/Etc/GMT__p__1.rb +0 -21
  117. data/test/tzinfo-data/tzinfo/data/definitions/Etc/UTC.rb +0 -21
  118. data/test/tzinfo-data/tzinfo/data/definitions/Europe/Amsterdam.rb +0 -261
  119. data/test/tzinfo-data/tzinfo/data/definitions/Europe/Andorra.rb +0 -186
  120. data/test/tzinfo-data/tzinfo/data/definitions/Europe/London.rb +0 -321
  121. data/test/tzinfo-data/tzinfo/data/definitions/Europe/Paris.rb +0 -265
  122. data/test/tzinfo-data/tzinfo/data/definitions/Europe/Prague.rb +0 -220
  123. data/test/tzinfo-data/tzinfo/data/definitions/UTC.rb +0 -16
  124. data/test/tzinfo-data/tzinfo/data/indexes/countries.rb +0 -927
  125. data/test/tzinfo-data/tzinfo/data/indexes/timezones.rb +0 -596
  126. data/test/tzinfo-data/tzinfo/data/version.rb +0 -14
  127. data/test/zoneinfo/America/Argentina/Buenos_Aires +0 -0
  128. data/test/zoneinfo/America/New_York +0 -0
  129. data/test/zoneinfo/Australia/Melbourne +0 -0
  130. data/test/zoneinfo/EST +0 -0
  131. data/test/zoneinfo/Etc/UTC +0 -0
  132. data/test/zoneinfo/Europe/Amsterdam +0 -0
  133. data/test/zoneinfo/Europe/Andorra +0 -0
  134. data/test/zoneinfo/Europe/London +0 -0
  135. data/test/zoneinfo/Europe/Paris +0 -0
  136. data/test/zoneinfo/Europe/Prague +0 -0
  137. data/test/zoneinfo/Factory +0 -0
  138. data/test/zoneinfo/iso3166.tab +0 -275
  139. data/test/zoneinfo/leapseconds +0 -61
  140. data/test/zoneinfo/posix/Europe/London +0 -0
  141. data/test/zoneinfo/posixrules +0 -0
  142. data/test/zoneinfo/right/Europe/London +0 -0
  143. data/test/zoneinfo/zone.tab +0 -439
  144. data/test/zoneinfo/zone1970.tab +0 -369
  145. data/tzinfo.gemspec +0 -21
@@ -1,104 +0,0 @@
1
- module TZInfo
2
- # A TimezoneTransition defined by as integer timestamp, as a rational to
3
- # create a DateTime or as both.
4
- #
5
- # @private
6
- class TimezoneTransitionDefinition < TimezoneTransition #:nodoc:
7
- # The numerator of the DateTime if the transition time is defined as a
8
- # DateTime, otherwise the transition time as a timestamp.
9
- attr_reader :numerator_or_time
10
- protected :numerator_or_time
11
-
12
- # Either the denominator of the DateTime if the transition time is defined
13
- # as a DateTime, otherwise nil.
14
- attr_reader :denominator
15
- protected :denominator
16
-
17
- # Creates a new TimezoneTransitionDefinition with the given offset,
18
- # previous_offset (both TimezoneOffset instances) and UTC time.
19
- #
20
- # The time can be specified as a timestamp, as a rational to create a
21
- # DateTime, or as both.
22
- #
23
- # If both a timestamp and rational are given, then the rational will only
24
- # be used if the timestamp falls outside of the range of Time on the
25
- # platform being used at runtime.
26
- #
27
- # DateTimes are created from the rational as follows:
28
- #
29
- # RubyCoreSupport.datetime_new!(RubyCoreSupport.rational_new!(numerator, denominator), 0, Date::ITALY)
30
- #
31
- # For performance reasons, the numerator and denominator must be specified
32
- # in their lowest form.
33
- def initialize(offset, previous_offset, numerator_or_timestamp, denominator_or_numerator = nil, denominator = nil)
34
- super(offset, previous_offset)
35
-
36
- if denominator
37
- numerator = denominator_or_numerator
38
- timestamp = numerator_or_timestamp
39
- elsif denominator_or_numerator
40
- numerator = numerator_or_timestamp
41
- denominator = denominator_or_numerator
42
- timestamp = nil
43
- else
44
- numerator = nil
45
- denominator = nil
46
- timestamp = numerator_or_timestamp
47
- end
48
-
49
- # Determine whether to use the timestamp or the numerator and denominator.
50
- if numerator && (
51
- !timestamp ||
52
- (timestamp < 0 && !RubyCoreSupport.time_supports_negative) ||
53
- ((timestamp < -2147483648 || timestamp > 2147483647) && !RubyCoreSupport.time_supports_64bit)
54
- )
55
-
56
- @numerator_or_time = numerator
57
- @denominator = denominator
58
- else
59
- @numerator_or_time = timestamp
60
- @denominator = nil
61
- end
62
-
63
- @at = nil
64
- end
65
-
66
- # A TimeOrDateTime instance representing the UTC time when this transition
67
- # occurs.
68
- def at
69
- # Thread-safety: It is possible that the value of @at may be calculated
70
- # multiple times in concurrently executing threads. It is not worth the
71
- # overhead of locking to ensure that @at is only calculated once.
72
-
73
- unless @at
74
- result = unless @denominator
75
- TimeOrDateTime.new(@numerator_or_time)
76
- else
77
- r = RubyCoreSupport.rational_new!(@numerator_or_time, @denominator)
78
- dt = RubyCoreSupport.datetime_new!(r, 0, Date::ITALY)
79
- TimeOrDateTime.new(dt)
80
- end
81
-
82
- return result if frozen?
83
- @at = result
84
- end
85
-
86
- @at
87
- end
88
-
89
- # Returns true if this TimezoneTransitionDefinition is equal to the given
90
- # TimezoneTransitionDefinition. Two TimezoneTransitionDefinition instances
91
- # are considered to be equal by eql? if offset, previous_offset,
92
- # numerator_or_time and denominator are all equal.
93
- def eql?(tti)
94
- tti.kind_of?(TimezoneTransitionDefinition) &&
95
- offset == tti.offset && previous_offset == tti.previous_offset &&
96
- numerator_or_time == tti.numerator_or_time && denominator == tti.denominator
97
- end
98
-
99
- # Returns a hash of this TimezoneTransitionDefinition instance.
100
- def hash
101
- @offset.hash ^ @previous_offset.hash ^ @numerator_or_time.hash ^ @denominator.hash
102
- end
103
- end
104
- end
@@ -1,274 +0,0 @@
1
- module TZInfo
2
- # Raised if no offsets have been defined when calling period_for_utc or
3
- # periods_for_local. Indicates an error in the timezone data.
4
- class NoOffsetsDefined < StandardError
5
- end
6
-
7
- # Represents a data timezone defined by a set of offsets and a set
8
- # of transitions.
9
- #
10
- # @private
11
- class TransitionDataTimezoneInfo < DataTimezoneInfo #:nodoc:
12
-
13
- # Constructs a new TransitionDataTimezoneInfo with its identifier.
14
- def initialize(identifier)
15
- super(identifier)
16
- @offsets = {}
17
- @transitions = []
18
- @previous_offset = nil
19
- @transitions_index = nil
20
- end
21
-
22
- # Defines a offset. The id uniquely identifies this offset within the
23
- # timezone. utc_offset and std_offset define the offset in seconds of
24
- # standard time from UTC and daylight savings from standard time
25
- # respectively. abbreviation describes the timezone offset (e.g. GMT, BST,
26
- # EST or EDT).
27
- #
28
- # The first offset to be defined is treated as the offset that applies
29
- # until the first transition. This will usually be in Local Mean Time (LMT).
30
- #
31
- # ArgumentError will be raised if the id is already defined.
32
- def offset(id, utc_offset, std_offset, abbreviation)
33
- raise ArgumentError, 'Offset already defined' if @offsets.has_key?(id)
34
-
35
- offset = TimezoneOffset.new(utc_offset, std_offset, abbreviation)
36
- @offsets[id] = offset
37
- @previous_offset = offset unless @previous_offset
38
- end
39
-
40
- # Defines a transition. Transitions must be defined in chronological order.
41
- # ArgumentError will be raised if a transition is added out of order.
42
- # offset_id refers to an id defined with offset. ArgumentError will be
43
- # raised if the offset_id cannot be found. numerator_or_time and
44
- # denomiator specify the time the transition occurs as. See
45
- # TimezoneTransition for more detail about specifying times.
46
- def transition(year, month, offset_id, numerator_or_timestamp, denominator_or_numerator = nil, denominator = nil)
47
- offset = @offsets[offset_id]
48
- raise ArgumentError, 'Offset not found' unless offset
49
-
50
- if @transitions_index
51
- if year < @last_year || (year == @last_year && month < @last_month)
52
- raise ArgumentError, 'Transitions must be increasing date order'
53
- end
54
-
55
- # Record the position of the first transition with this index.
56
- index = transition_index(year, month)
57
- @transitions_index[index] ||= @transitions.length
58
-
59
- # Fill in any gaps
60
- (index - 1).downto(0) do |i|
61
- break if @transitions_index[i]
62
- @transitions_index[i] = @transitions.length
63
- end
64
- else
65
- @transitions_index = [@transitions.length]
66
- @start_year = year
67
- @start_month = month
68
- end
69
-
70
- @transitions << TimezoneTransitionDefinition.new(offset, @previous_offset,
71
- numerator_or_timestamp, denominator_or_numerator, denominator)
72
- @last_year = year
73
- @last_month = month
74
- @previous_offset = offset
75
- end
76
-
77
- # Returns the TimezonePeriod for the given UTC time.
78
- # Raises NoOffsetsDefined if no offsets have been defined.
79
- def period_for_utc(utc)
80
- unless @transitions.empty?
81
- utc = TimeOrDateTime.wrap(utc)
82
- index = transition_index(utc.year, utc.mon)
83
-
84
- start_transition = nil
85
- start = transition_before_end(index)
86
- if start
87
- start.downto(0) do |i|
88
- if @transitions[i].at <= utc
89
- start_transition = @transitions[i]
90
- break
91
- end
92
- end
93
- end
94
-
95
- end_transition = nil
96
- start = transition_after_start(index)
97
- if start
98
- start.upto(@transitions.length - 1) do |i|
99
- if @transitions[i].at > utc
100
- end_transition = @transitions[i]
101
- break
102
- end
103
- end
104
- end
105
-
106
- if start_transition || end_transition
107
- TimezonePeriod.new(start_transition, end_transition)
108
- else
109
- # Won't happen since there are transitions. Must always find one
110
- # transition that is either >= or < the specified time.
111
- raise 'No transitions found in search'
112
- end
113
- else
114
- raise NoOffsetsDefined, 'No offsets have been defined' unless @previous_offset
115
- TimezonePeriod.new(nil, nil, @previous_offset)
116
- end
117
- end
118
-
119
- # Returns the set of TimezonePeriods for the given local time as an array.
120
- # Results returned are ordered by increasing UTC start date.
121
- # Returns an empty array if no periods are found for the given time.
122
- # Raises NoOffsetsDefined if no offsets have been defined.
123
- def periods_for_local(local)
124
- unless @transitions.empty?
125
- local = TimeOrDateTime.wrap(local)
126
- index = transition_index(local.year, local.mon)
127
-
128
- result = []
129
-
130
- start_index = transition_after_start(index - 1)
131
- if start_index && @transitions[start_index].local_end_at > local
132
- if start_index > 0
133
- if @transitions[start_index - 1].local_start_at <= local
134
- result << TimezonePeriod.new(@transitions[start_index - 1], @transitions[start_index])
135
- end
136
- else
137
- result << TimezonePeriod.new(nil, @transitions[start_index])
138
- end
139
- end
140
-
141
- end_index = transition_before_end(index + 1)
142
-
143
- if end_index
144
- start_index = end_index unless start_index
145
-
146
- start_index.upto(transition_before_end(index + 1)) do |i|
147
- if @transitions[i].local_start_at <= local
148
- if i + 1 < @transitions.length
149
- if @transitions[i + 1].local_end_at > local
150
- result << TimezonePeriod.new(@transitions[i], @transitions[i + 1])
151
- end
152
- else
153
- result << TimezonePeriod.new(@transitions[i], nil)
154
- end
155
- end
156
- end
157
- end
158
-
159
- result
160
- else
161
- raise NoOffsetsDefined, 'No offsets have been defined' unless @previous_offset
162
- [TimezonePeriod.new(nil, nil, @previous_offset)]
163
- end
164
- end
165
-
166
- # Returns an Array of TimezoneTransition instances representing the times
167
- # where the UTC offset of the timezone changes.
168
- #
169
- # Transitions are returned up to a given date and time up to a given date
170
- # and time, specified in UTC (utc_to).
171
- #
172
- # A from date and time may also be supplied using the utc_from parameter
173
- # (also specified in UTC). If utc_from is not nil, only transitions from
174
- # that date and time onwards will be returned.
175
- #
176
- # Comparisons with utc_to are exclusive. Comparisons with utc_from are
177
- # inclusive. If a transition falls precisely on utc_to, it will be excluded.
178
- # If a transition falls on utc_from, it will be included.
179
- #
180
- # Transitions returned are ordered by when they occur, from earliest to
181
- # latest.
182
- #
183
- # utc_to and utc_from can be specified using either DateTime, Time or
184
- # integer timestamps (Time.to_i).
185
- #
186
- # If utc_from is specified and utc_to is not greater than utc_from, then
187
- # transitions_up_to raises an ArgumentError exception.
188
- def transitions_up_to(utc_to, utc_from = nil)
189
- utc_to = TimeOrDateTime.wrap(utc_to)
190
- utc_from = utc_from ? TimeOrDateTime.wrap(utc_from) : nil
191
-
192
- if utc_from && utc_to <= utc_from
193
- raise ArgumentError, 'utc_to must be greater than utc_from'
194
- end
195
-
196
- unless @transitions.empty?
197
- if utc_from
198
- from = transition_after_start(transition_index(utc_from.year, utc_from.mon))
199
-
200
- if from
201
- while from < @transitions.length && @transitions[from].at < utc_from
202
- from += 1
203
- end
204
-
205
- if from >= @transitions.length
206
- return []
207
- end
208
- else
209
- # utc_from is later than last transition.
210
- return []
211
- end
212
- else
213
- from = 0
214
- end
215
-
216
- to = transition_before_end(transition_index(utc_to.year, utc_to.mon))
217
-
218
- if to
219
- while to >= 0 && @transitions[to].at >= utc_to
220
- to -= 1
221
- end
222
-
223
- if to < 0
224
- return []
225
- end
226
- else
227
- # utc_to is earlier than first transition.
228
- return []
229
- end
230
-
231
- @transitions[from..to]
232
- else
233
- []
234
- end
235
- end
236
-
237
- private
238
- # Returns the index into the @transitions_index array for a given year
239
- # and month.
240
- def transition_index(year, month)
241
- index = (year - @start_year) * 2
242
- index += 1 if month > 6
243
- index -= 1 if @start_month > 6
244
- index
245
- end
246
-
247
- # Returns the index into @transitions of the first transition that occurs
248
- # on or after the start of the given index into @transitions_index.
249
- # Returns nil if there are no such transitions.
250
- def transition_after_start(index)
251
- if index >= @transitions_index.length
252
- nil
253
- else
254
- index = 0 if index < 0
255
- @transitions_index[index]
256
- end
257
- end
258
-
259
- # Returns the index into @transitions of the first transition that occurs
260
- # before the end of the given index into @transitions_index.
261
- # Returns nil if there are no such transitions.
262
- def transition_before_end(index)
263
- index = index + 1
264
-
265
- if index <= 0
266
- nil
267
- elsif index >= @transitions_index.length
268
- @transitions.length - 1
269
- else
270
- @transitions_index[index] - 1
271
- end
272
- end
273
- end
274
- end
@@ -1,37 +0,0 @@
1
- module TZInfo
2
- # Represents information about a country returned by ZoneinfoDataSource.
3
- #
4
- # @private
5
- class ZoneinfoCountryInfo < CountryInfo #:nodoc:
6
- # Constructs a new CountryInfo with an ISO 3166 country code, name and
7
- # an array of CountryTimezones.
8
- def initialize(code, name, zones)
9
- super(code, name)
10
- @zones = zones.dup.freeze
11
- @zone_identifiers = nil
12
- end
13
-
14
- # Returns a frozen array of all the zone identifiers for the country ordered
15
- # geographically, most populous first.
16
- def zone_identifiers
17
- # Thread-safety: It is possible that the value of @zone_identifiers may be
18
- # calculated multiple times in concurrently executing threads. It is not
19
- # worth the overhead of locking to ensure that @zone_identifiers is only
20
- # calculated once.
21
-
22
- unless @zone_identifiers
23
- result = zones.collect {|zone| zone.identifier}.freeze
24
- return result if frozen?
25
- @zone_identifiers = result
26
- end
27
-
28
- @zone_identifiers
29
- end
30
-
31
- # Returns a frozen array of all the timezones for the for the country
32
- # ordered geographically, most populous first.
33
- def zones
34
- @zones
35
- end
36
- end
37
- end
@@ -1,496 +0,0 @@
1
- module TZInfo
2
- # Use send as a workaround for an issue on JRuby 9.2.9.0 where using the
3
- # refinement causes calls to RubyCoreSupport.file_open to fail to pass the
4
- # block parameter.
5
- #
6
- # https://travis-ci.org/tzinfo/tzinfo/jobs/628812051#L1931
7
- # https://github.com/jruby/jruby/issues/6009
8
- send(:using, TZInfo::RubyCoreSupport::UntaintExt) if TZInfo::RubyCoreSupport.const_defined?(:UntaintExt)
9
-
10
- # An InvalidZoneinfoDirectory exception is raised if the DataSource is
11
- # set to a specific zoneinfo path, which is not a valid zoneinfo directory
12
- # (i.e. a directory containing index files named iso3166.tab and zone.tab
13
- # as well as other timezone files).
14
- class InvalidZoneinfoDirectory < StandardError
15
- end
16
-
17
- # A ZoneinfoDirectoryNotFound exception is raised if no valid zoneinfo
18
- # directory could be found when checking the paths listed in
19
- # ZoneinfoDataSource.search_path. A valid zoneinfo directory is one that
20
- # contains timezone files, a country code index file named iso3166.tab and a
21
- # timezone index file named zone1970.tab or zone.tab.
22
- class ZoneinfoDirectoryNotFound < StandardError
23
- end
24
-
25
- # A DataSource that loads data from a 'zoneinfo' directory containing
26
- # compiled "TZif" version 3 (or earlier) files in addition to iso3166.tab and
27
- # zone1970.tab or zone.tab index files.
28
- #
29
- # To have TZInfo load the system zoneinfo files, call TZInfo::DataSource.set
30
- # as follows:
31
- #
32
- # TZInfo::DataSource.set(:zoneinfo)
33
- #
34
- # To load zoneinfo files from a particular directory, pass the directory to
35
- # TZInfo::DataSource.set:
36
- #
37
- # TZInfo::DataSource.set(:zoneinfo, directory)
38
- #
39
- # Note that the platform used at runtime may limit the range of available
40
- # transition data that can be loaded from zoneinfo files. There are two
41
- # factors to consider:
42
- #
43
- # First of all, the zoneinfo support in TZInfo makes use of Ruby's Time class.
44
- # On 32-bit builds of Ruby 1.8, the Time class only supports 32-bit
45
- # timestamps. This means that only Times between 1901-12-13 20:45:52 and
46
- # 2038-01-19 03:14:07 can be represented. Furthermore, certain platforms only
47
- # allow for positive 32-bit timestamps (notably Windows), making the earliest
48
- # representable time 1970-01-01 00:00:00.
49
- #
50
- # 64-bit builds of Ruby 1.8 and all builds of Ruby 1.9 support 64-bit
51
- # timestamps. This means that there is no practical restriction on the range
52
- # of the Time class on these platforms.
53
- #
54
- # TZInfo will only load transitions that fall within the supported range of
55
- # the Time class. Any queries performed on times outside of this range may
56
- # give inaccurate results.
57
- #
58
- # The second factor concerns the zoneinfo files. Versions of the 'zic' tool
59
- # (used to build zoneinfo files) that were released prior to February 2006
60
- # created zoneinfo files that used 32-bit integers for transition timestamps.
61
- # Later versions of zic produce zoneinfo files that use 64-bit integers. If
62
- # you have 32-bit zoneinfo files on your system, then any queries falling
63
- # outside of the range 1901-12-13 20:45:52 to 2038-01-19 03:14:07 may be
64
- # inaccurate.
65
- #
66
- # Most modern platforms include 64-bit zoneinfo files. However, Mac OS X (up
67
- # to at least 10.8.4) still uses 32-bit zoneinfo files.
68
- #
69
- # To check whether your zoneinfo files contain 32-bit or 64-bit transition
70
- # data, you can run the following code (substituting the identifier of the
71
- # zone you want to test for zone_identifier):
72
- #
73
- # TZInfo::DataSource.set(:zoneinfo)
74
- # dir = TZInfo::DataSource.get.zoneinfo_dir
75
- # File.open(File.join(dir, zone_identifier), 'r') {|f| f.read(5) }
76
- #
77
- # If the last line returns "TZif\\x00", then you have a 32-bit zoneinfo file.
78
- # If it returns "TZif2" or "TZif3" then you have a 64-bit zoneinfo file.
79
- #
80
- # If you require support for 64-bit transitions, but are restricted to 32-bit
81
- # zoneinfo support, then you may want to consider using TZInfo::RubyDataSource
82
- # instead.
83
- class ZoneinfoDataSource < DataSource
84
- # The default value of ZoneinfoDataSource.search_path.
85
- DEFAULT_SEARCH_PATH = ['/usr/share/zoneinfo', '/usr/share/lib/zoneinfo', '/etc/zoneinfo'].freeze
86
-
87
- # The default value of ZoneinfoDataSource.alternate_iso3166_tab_search_path.
88
- DEFAULT_ALTERNATE_ISO3166_TAB_SEARCH_PATH = ['/usr/share/misc/iso3166.tab', '/usr/share/misc/iso3166'].freeze
89
-
90
- # Paths to be checked to find the system zoneinfo directory.
91
- @@search_path = DEFAULT_SEARCH_PATH.dup
92
-
93
- # Paths to possible alternate iso3166.tab files (used to locate the
94
- # system-wide iso3166.tab files on FreeBSD and OpenBSD).
95
- @@alternate_iso3166_tab_search_path = DEFAULT_ALTERNATE_ISO3166_TAB_SEARCH_PATH.dup
96
-
97
- # An Array of directories that will be checked to find the system zoneinfo
98
- # directory.
99
- #
100
- # Directories are checked in the order they appear in the Array.
101
- #
102
- # The default value is ['/usr/share/zoneinfo', '/usr/share/lib/zoneinfo', '/etc/zoneinfo'].
103
- def self.search_path
104
- @@search_path
105
- end
106
-
107
- # Sets the directories to be checked when locating the system zoneinfo
108
- # directory.
109
- #
110
- # Can be set to an Array of directories or a String containing directories
111
- # separated with File::PATH_SEPARATOR.
112
- #
113
- # Directories are checked in the order they appear in the Array or String.
114
- #
115
- # Set to nil to revert to the default paths.
116
- def self.search_path=(search_path)
117
- @@search_path = process_search_path(search_path, DEFAULT_SEARCH_PATH)
118
- end
119
-
120
- # An Array of paths that will be checked to find an alternate iso3166.tab
121
- # file if one was not included in the zoneinfo directory (for example, on
122
- # FreeBSD and OpenBSD systems).
123
- #
124
- # Paths are checked in the order they appear in the array.
125
- #
126
- # The default value is ['/usr/share/misc/iso3166.tab', '/usr/share/misc/iso3166'].
127
- def self.alternate_iso3166_tab_search_path
128
- @@alternate_iso3166_tab_search_path
129
- end
130
-
131
- # Sets the paths to check to locate an alternate iso3166.tab file if one was
132
- # not included in the zoneinfo directory.
133
- #
134
- # Can be set to an Array of directories or a String containing directories
135
- # separated with File::PATH_SEPARATOR.
136
- #
137
- # Paths are checked in the order they appear in the array.
138
- #
139
- # Set to nil to revert to the default paths.
140
- def self.alternate_iso3166_tab_search_path=(alternate_iso3166_tab_search_path)
141
- @@alternate_iso3166_tab_search_path = process_search_path(alternate_iso3166_tab_search_path, DEFAULT_ALTERNATE_ISO3166_TAB_SEARCH_PATH)
142
- end
143
-
144
- # The zoneinfo directory being used.
145
- attr_reader :zoneinfo_dir
146
-
147
- # Creates a new ZoneinfoDataSource.
148
- #
149
- # If zoneinfo_dir is specified, it will be checked and used as the source
150
- # of zoneinfo files.
151
- #
152
- # The directory must contain a file named iso3166.tab and a file named
153
- # either zone1970.tab or zone.tab. These may either be included in the root
154
- # of the directory or in a 'tab' sub-directory and named 'country.tab' and
155
- # 'zone_sun.tab' respectively (as is the case on Solaris.
156
- #
157
- # Additionally, the path to iso3166.tab can be overridden using the
158
- # alternate_iso3166_tab_path parameter.
159
- #
160
- # InvalidZoneinfoDirectory will be raised if the iso3166.tab and
161
- # zone1970.tab or zone.tab files cannot be found using the zoneinfo_dir and
162
- # alternate_iso3166_tab_path parameters.
163
- #
164
- # If zoneinfo_dir is not specified or nil, the paths referenced in
165
- # search_path are searched in order to find a valid zoneinfo directory
166
- # (one that contains zone1970.tab or zone.tab and iso3166.tab files as
167
- # above).
168
- #
169
- # The paths referenced in alternate_iso3166_tab_search_path are also
170
- # searched to find an iso3166.tab file if one of the searched zoneinfo
171
- # directories doesn't contain an iso3166.tab file.
172
- #
173
- # If no valid directory can be found by searching, ZoneinfoDirectoryNotFound
174
- # will be raised.
175
- def initialize(zoneinfo_dir = nil, alternate_iso3166_tab_path = nil)
176
- if zoneinfo_dir
177
- iso3166_tab_path, zone_tab_path = validate_zoneinfo_dir(zoneinfo_dir, alternate_iso3166_tab_path)
178
-
179
- unless iso3166_tab_path && zone_tab_path
180
- raise InvalidZoneinfoDirectory, "#{zoneinfo_dir} is not a directory or doesn't contain a iso3166.tab file and a zone1970.tab or zone.tab file."
181
- end
182
-
183
- @zoneinfo_dir = zoneinfo_dir
184
- else
185
- @zoneinfo_dir, iso3166_tab_path, zone_tab_path = find_zoneinfo_dir
186
-
187
- unless @zoneinfo_dir && iso3166_tab_path && zone_tab_path
188
- raise ZoneinfoDirectoryNotFound, "None of the paths included in TZInfo::ZoneinfoDataSource.search_path are valid zoneinfo directories."
189
- end
190
- end
191
-
192
- @zoneinfo_dir = File.expand_path(@zoneinfo_dir).freeze
193
- @timezone_index = load_timezone_index.freeze
194
- @country_index = load_country_index(iso3166_tab_path, zone_tab_path).freeze
195
- end
196
-
197
- # Returns a TimezoneInfo instance for a given identifier.
198
- # Raises InvalidTimezoneIdentifier if the timezone is not found or the
199
- # identifier is invalid.
200
- def load_timezone_info(identifier)
201
- begin
202
- if @timezone_index.include?(identifier)
203
- path = File.join(@zoneinfo_dir, identifier)
204
-
205
- # Untaint path rather than identifier. We don't want to modify
206
- # identifier. identifier may also be frozen and therefore cannot be
207
- # untainted.
208
- path.untaint
209
-
210
- begin
211
- ZoneinfoTimezoneInfo.new(identifier, path)
212
- rescue InvalidZoneinfoFile => e
213
- raise InvalidTimezoneIdentifier, e.message
214
- end
215
- else
216
- raise InvalidTimezoneIdentifier, 'Invalid identifier'
217
- end
218
- rescue Errno::ENOENT, Errno::ENAMETOOLONG, Errno::ENOTDIR
219
- raise InvalidTimezoneIdentifier, 'Invalid identifier'
220
- rescue Errno::EACCES => e
221
- raise InvalidTimezoneIdentifier, e.message
222
- end
223
- end
224
-
225
- # Returns an array of all the available timezone identifiers.
226
- def timezone_identifiers
227
- @timezone_index
228
- end
229
-
230
- # Returns an array of all the available timezone identifiers for
231
- # data timezones (i.e. those that actually contain definitions).
232
- #
233
- # For ZoneinfoDataSource, this will always be identical to
234
- # timezone_identifers.
235
- def data_timezone_identifiers
236
- @timezone_index
237
- end
238
-
239
- # Returns an array of all the available timezone identifiers that
240
- # are links to other timezones.
241
- #
242
- # For ZoneinfoDataSource, this will always be an empty array.
243
- def linked_timezone_identifiers
244
- [].freeze
245
- end
246
-
247
- # Returns a CountryInfo instance for the given ISO 3166-1 alpha-2
248
- # country code. Raises InvalidCountryCode if the country could not be found
249
- # or the code is invalid.
250
- def load_country_info(code)
251
- info = @country_index[code]
252
- raise InvalidCountryCode, 'Invalid country code' unless info
253
- info
254
- end
255
-
256
- # Returns an array of all the available ISO 3166-1 alpha-2
257
- # country codes.
258
- def country_codes
259
- @country_index.keys.freeze
260
- end
261
-
262
- # Returns the name and information about this DataSource.
263
- def to_s
264
- "Zoneinfo DataSource: #{@zoneinfo_dir}"
265
- end
266
-
267
- # Returns internal object state as a programmer-readable string.
268
- def inspect
269
- "#<#{self.class}: #{@zoneinfo_dir}>"
270
- end
271
-
272
- private
273
-
274
- # Processes a path for use as the search_path or
275
- # alternate_iso3166_tab_search_path.
276
- def self.process_search_path(path, default)
277
- if path
278
- if path.kind_of?(String)
279
- path.split(File::PATH_SEPARATOR)
280
- else
281
- path.collect {|p| p.to_s}
282
- end
283
- else
284
- default.dup
285
- end
286
- end
287
-
288
- # Validates a zoneinfo directory and returns the paths to the iso3166.tab
289
- # and zone1970.tab or zone.tab files if valid. If the directory is not
290
- # valid, returns nil.
291
- #
292
- # The path to the iso3166.tab file may be overriden by passing in a path.
293
- # This is treated as either absolute or relative to the current working
294
- # directory.
295
- def validate_zoneinfo_dir(path, iso3166_tab_path = nil)
296
- if File.directory?(path)
297
- if iso3166_tab_path
298
- return nil unless File.file?(iso3166_tab_path)
299
- else
300
- iso3166_tab_path = resolve_tab_path(path, ['iso3166.tab'], 'country.tab')
301
- return nil unless iso3166_tab_path
302
- end
303
-
304
- zone_tab_path = resolve_tab_path(path, ['zone1970.tab', 'zone.tab'], 'zone_sun.tab')
305
- return nil unless zone_tab_path
306
-
307
- [iso3166_tab_path, zone_tab_path]
308
- else
309
- nil
310
- end
311
- end
312
-
313
- # Attempts to resolve the path to a tab file given its standard names and
314
- # tab sub-directory name (as used on Solaris).
315
- def resolve_tab_path(zoneinfo_path, standard_names, tab_name)
316
- standard_names.each do |standard_name|
317
- path = File.join(zoneinfo_path, standard_name)
318
- return path if File.file?(path)
319
- end
320
-
321
- path = File.join(zoneinfo_path, 'tab', tab_name)
322
- return path if File.file?(path)
323
-
324
- nil
325
- end
326
-
327
- # Finds a zoneinfo directory using search_path and
328
- # alternate_iso3166_tab_search_path. Returns the paths to the directory,
329
- # the iso3166.tab file and the zone.tab file or nil if not found.
330
- def find_zoneinfo_dir
331
- alternate_iso3166_tab_path = self.class.alternate_iso3166_tab_search_path.detect do |path|
332
- File.file?(path)
333
- end
334
-
335
- self.class.search_path.each do |path|
336
- # Try without the alternate_iso3166_tab_path first.
337
- iso3166_tab_path, zone_tab_path = validate_zoneinfo_dir(path)
338
- return path, iso3166_tab_path, zone_tab_path if iso3166_tab_path && zone_tab_path
339
-
340
- if alternate_iso3166_tab_path
341
- iso3166_tab_path, zone_tab_path = validate_zoneinfo_dir(path, alternate_iso3166_tab_path)
342
- return path, iso3166_tab_path, zone_tab_path if iso3166_tab_path && zone_tab_path
343
- end
344
- end
345
-
346
- # Not found.
347
- nil
348
- end
349
-
350
- # Scans @zoneinfo_dir and returns an Array of available timezone
351
- # identifiers.
352
- def load_timezone_index
353
- index = []
354
-
355
- # Ignoring particular files:
356
- # +VERSION is included on Mac OS X.
357
- # leapseconds is a list of leap seconds.
358
- # localtime is the current local timezone (may be a link).
359
- # posix, posixrules and right are directories containing other versions of the zoneinfo files.
360
- # src is a directory containing the tzdata source included on Solaris.
361
- # timeconfig is a symlink included on Slackware.
362
-
363
- enum_timezones(nil, ['+VERSION', 'leapseconds', 'localtime', 'posix', 'posixrules', 'right', 'src', 'timeconfig']) do |identifier|
364
- index << identifier
365
- end
366
-
367
- index.sort
368
- end
369
-
370
- # Recursively scans a directory of timezones, calling the passed in block
371
- # for each identifier found.
372
- def enum_timezones(dir, exclude = [], &block)
373
- Dir.foreach(dir ? File.join(@zoneinfo_dir, dir) : @zoneinfo_dir) do |entry|
374
- unless entry =~ /\./ || exclude.include?(entry)
375
- entry.untaint
376
- path = dir ? File.join(dir, entry) : entry
377
- full_path = File.join(@zoneinfo_dir, path)
378
-
379
- if File.directory?(full_path)
380
- enum_timezones(path, [], &block)
381
- elsif File.file?(full_path)
382
- yield path
383
- end
384
- end
385
- end
386
- end
387
-
388
- # Uses the iso3166.tab and zone1970.tab or zone.tab files to build an index
389
- # of the available countries and their timezones.
390
- def load_country_index(iso3166_tab_path, zone_tab_path)
391
-
392
- # Handle standard 3 to 4 column zone.tab files as well as the 4 to 5
393
- # column format used by Solaris.
394
- #
395
- # On Solaris, an extra column before the comment gives an optional
396
- # linked/alternate timezone identifier (or '-' if not set).
397
- #
398
- # Additionally, there is a section at the end of the file for timezones
399
- # covering regions. These are given lower-case "country" codes. The timezone
400
- # identifier column refers to a continent instead of an identifier. These
401
- # lines will be ignored by TZInfo.
402
- #
403
- # Since the last column is optional in both formats, testing for the
404
- # Solaris format is done in two passes. The first pass identifies if there
405
- # are any lines using 5 columns.
406
-
407
-
408
- # The first column is allowed to be a comma separated list of country
409
- # codes, as used in zone1970.tab (introduced in tzdata 2014f).
410
- #
411
- # The first country code in the comma-separated list is the country that
412
- # contains the city the zone identifer is based on. The first country
413
- # code on each line is considered to be primary with the others
414
- # secondary.
415
- #
416
- # The zones for each country are ordered primary first, then secondary.
417
- # Within the primary and secondary groups, the zones are ordered by their
418
- # order in the file.
419
-
420
- file_is_5_column = false
421
- zone_tab = []
422
-
423
- RubyCoreSupport.open_file(zone_tab_path, 'r', :external_encoding => 'UTF-8', :internal_encoding => 'UTF-8') do |file|
424
- file.each_line do |line|
425
- line.chomp!
426
-
427
- if line =~ /\A([A-Z]{2}(?:,[A-Z]{2})*)\t(?:([+\-])(\d{2})(\d{2})([+\-])(\d{3})(\d{2})|([+\-])(\d{2})(\d{2})(\d{2})([+\-])(\d{3})(\d{2})(\d{2}))\t([^\t]+)(?:\t([^\t]+))?(?:\t([^\t]+))?\z/
428
- codes = $1
429
-
430
- if $2
431
- latitude = dms_to_rational($2, $3, $4)
432
- longitude = dms_to_rational($5, $6, $7)
433
- else
434
- latitude = dms_to_rational($8, $9, $10, $11)
435
- longitude = dms_to_rational($12, $13, $14, $15)
436
- end
437
-
438
- zone_identifier = $16
439
- column4 = $17
440
- column5 = $18
441
-
442
- file_is_5_column = true if column5
443
-
444
- zone_tab << [codes.split(','.freeze), zone_identifier, latitude, longitude, column4, column5]
445
- end
446
- end
447
- end
448
-
449
- primary_zones = {}
450
- secondary_zones = {}
451
-
452
- zone_tab.each do |codes, zone_identifier, latitude, longitude, column4, column5|
453
- description = file_is_5_column ? column5 : column4
454
- country_timezone = CountryTimezone.new(zone_identifier, latitude, longitude, description)
455
-
456
- # codes will always have at least one element
457
-
458
- (primary_zones[codes.first] ||= []) << country_timezone
459
-
460
- codes[1..-1].each do |code|
461
- (secondary_zones[code] ||= []) << country_timezone
462
- end
463
- end
464
-
465
- countries = {}
466
-
467
- RubyCoreSupport.open_file(iso3166_tab_path, 'r', :external_encoding => 'UTF-8', :internal_encoding => 'UTF-8') do |file|
468
- file.each_line do |line|
469
- line.chomp!
470
-
471
- # Handle both the two column alpha-2 and name format used in the tz
472
- # database as well as the 4 column alpha-2, alpha-3, numeric-3 and
473
- # name format used by FreeBSD and OpenBSD.
474
-
475
- if line =~ /\A([A-Z]{2})(?:\t[A-Z]{3}\t[0-9]{3})?\t(.+)\z/
476
- code = $1
477
- name = $2
478
- zones = (primary_zones[code] || []) + (secondary_zones[code] || [])
479
-
480
- countries[code] = ZoneinfoCountryInfo.new(code, name, zones)
481
- end
482
- end
483
- end
484
-
485
- countries
486
- end
487
-
488
- # Converts degrees, minutes and seconds to a Rational.
489
- def dms_to_rational(sign, degrees, minutes, seconds = nil)
490
- result = degrees.to_i + Rational(minutes.to_i, 60)
491
- result += Rational(seconds.to_i, 3600) if seconds
492
- result = -result if sign == '-'.freeze
493
- result
494
- end
495
- end
496
- end