tzinfo 1.2.5

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of tzinfo might be problematic. Click here for more details.

Files changed (111) hide show
  1. checksums.yaml +7 -0
  2. checksums.yaml.gz.sig +3 -0
  3. data.tar.gz.sig +0 -0
  4. data/.yardopts +6 -0
  5. data/CHANGES.md +786 -0
  6. data/LICENSE +19 -0
  7. data/README.md +151 -0
  8. data/Rakefile +107 -0
  9. data/lib/tzinfo.rb +40 -0
  10. data/lib/tzinfo/country.rb +196 -0
  11. data/lib/tzinfo/country_index_definition.rb +31 -0
  12. data/lib/tzinfo/country_info.rb +42 -0
  13. data/lib/tzinfo/country_timezone.rb +135 -0
  14. data/lib/tzinfo/data_source.rb +190 -0
  15. data/lib/tzinfo/data_timezone.rb +58 -0
  16. data/lib/tzinfo/data_timezone_info.rb +55 -0
  17. data/lib/tzinfo/info_timezone.rb +30 -0
  18. data/lib/tzinfo/linked_timezone.rb +63 -0
  19. data/lib/tzinfo/linked_timezone_info.rb +26 -0
  20. data/lib/tzinfo/offset_rationals.rb +77 -0
  21. data/lib/tzinfo/ruby_core_support.rb +146 -0
  22. data/lib/tzinfo/ruby_country_info.rb +74 -0
  23. data/lib/tzinfo/ruby_data_source.rb +136 -0
  24. data/lib/tzinfo/time_or_datetime.rb +340 -0
  25. data/lib/tzinfo/timezone.rb +669 -0
  26. data/lib/tzinfo/timezone_definition.rb +36 -0
  27. data/lib/tzinfo/timezone_index_definition.rb +54 -0
  28. data/lib/tzinfo/timezone_info.rb +30 -0
  29. data/lib/tzinfo/timezone_offset.rb +101 -0
  30. data/lib/tzinfo/timezone_period.rb +245 -0
  31. data/lib/tzinfo/timezone_proxy.rb +105 -0
  32. data/lib/tzinfo/timezone_transition.rb +130 -0
  33. data/lib/tzinfo/timezone_transition_definition.rb +104 -0
  34. data/lib/tzinfo/transition_data_timezone_info.rb +274 -0
  35. data/lib/tzinfo/zoneinfo_country_info.rb +37 -0
  36. data/lib/tzinfo/zoneinfo_data_source.rb +488 -0
  37. data/lib/tzinfo/zoneinfo_timezone_info.rb +296 -0
  38. data/test/tc_country.rb +234 -0
  39. data/test/tc_country_index_definition.rb +69 -0
  40. data/test/tc_country_info.rb +16 -0
  41. data/test/tc_country_timezone.rb +173 -0
  42. data/test/tc_data_source.rb +218 -0
  43. data/test/tc_data_timezone.rb +99 -0
  44. data/test/tc_data_timezone_info.rb +18 -0
  45. data/test/tc_info_timezone.rb +34 -0
  46. data/test/tc_linked_timezone.rb +155 -0
  47. data/test/tc_linked_timezone_info.rb +23 -0
  48. data/test/tc_offset_rationals.rb +23 -0
  49. data/test/tc_ruby_core_support.rb +168 -0
  50. data/test/tc_ruby_country_info.rb +110 -0
  51. data/test/tc_ruby_data_source.rb +143 -0
  52. data/test/tc_time_or_datetime.rb +654 -0
  53. data/test/tc_timezone.rb +1350 -0
  54. data/test/tc_timezone_definition.rb +113 -0
  55. data/test/tc_timezone_index_definition.rb +73 -0
  56. data/test/tc_timezone_info.rb +11 -0
  57. data/test/tc_timezone_london.rb +143 -0
  58. data/test/tc_timezone_melbourne.rb +142 -0
  59. data/test/tc_timezone_new_york.rb +142 -0
  60. data/test/tc_timezone_offset.rb +126 -0
  61. data/test/tc_timezone_period.rb +555 -0
  62. data/test/tc_timezone_proxy.rb +136 -0
  63. data/test/tc_timezone_transition.rb +366 -0
  64. data/test/tc_timezone_transition_definition.rb +295 -0
  65. data/test/tc_timezone_utc.rb +27 -0
  66. data/test/tc_transition_data_timezone_info.rb +423 -0
  67. data/test/tc_zoneinfo_country_info.rb +78 -0
  68. data/test/tc_zoneinfo_data_source.rb +1195 -0
  69. data/test/tc_zoneinfo_timezone_info.rb +1232 -0
  70. data/test/test_utils.rb +163 -0
  71. data/test/ts_all.rb +7 -0
  72. data/test/ts_all_ruby.rb +5 -0
  73. data/test/ts_all_zoneinfo.rb +7 -0
  74. data/test/tzinfo-data/tzinfo/data.rb +8 -0
  75. data/test/tzinfo-data/tzinfo/data/definitions/America/Argentina/Buenos_Aires.rb +89 -0
  76. data/test/tzinfo-data/tzinfo/data/definitions/America/New_York.rb +315 -0
  77. data/test/tzinfo-data/tzinfo/data/definitions/Australia/Melbourne.rb +218 -0
  78. data/test/tzinfo-data/tzinfo/data/definitions/EST.rb +19 -0
  79. data/test/tzinfo-data/tzinfo/data/definitions/Etc/GMT__m__1.rb +21 -0
  80. data/test/tzinfo-data/tzinfo/data/definitions/Etc/GMT__p__1.rb +21 -0
  81. data/test/tzinfo-data/tzinfo/data/definitions/Etc/UTC.rb +21 -0
  82. data/test/tzinfo-data/tzinfo/data/definitions/Europe/Amsterdam.rb +261 -0
  83. data/test/tzinfo-data/tzinfo/data/definitions/Europe/Andorra.rb +186 -0
  84. data/test/tzinfo-data/tzinfo/data/definitions/Europe/London.rb +321 -0
  85. data/test/tzinfo-data/tzinfo/data/definitions/Europe/Paris.rb +265 -0
  86. data/test/tzinfo-data/tzinfo/data/definitions/Europe/Prague.rb +220 -0
  87. data/test/tzinfo-data/tzinfo/data/definitions/UTC.rb +16 -0
  88. data/test/tzinfo-data/tzinfo/data/indexes/countries.rb +927 -0
  89. data/test/tzinfo-data/tzinfo/data/indexes/timezones.rb +596 -0
  90. data/test/tzinfo-data/tzinfo/data/version.rb +14 -0
  91. data/test/zoneinfo/America/Argentina/Buenos_Aires +0 -0
  92. data/test/zoneinfo/America/New_York +0 -0
  93. data/test/zoneinfo/Australia/Melbourne +0 -0
  94. data/test/zoneinfo/EST +0 -0
  95. data/test/zoneinfo/Etc/UTC +0 -0
  96. data/test/zoneinfo/Europe/Amsterdam +0 -0
  97. data/test/zoneinfo/Europe/Andorra +0 -0
  98. data/test/zoneinfo/Europe/London +0 -0
  99. data/test/zoneinfo/Europe/Paris +0 -0
  100. data/test/zoneinfo/Europe/Prague +0 -0
  101. data/test/zoneinfo/Factory +0 -0
  102. data/test/zoneinfo/iso3166.tab +275 -0
  103. data/test/zoneinfo/leapseconds +61 -0
  104. data/test/zoneinfo/posix/Europe/London +0 -0
  105. data/test/zoneinfo/posixrules +0 -0
  106. data/test/zoneinfo/right/Europe/London +0 -0
  107. data/test/zoneinfo/zone.tab +439 -0
  108. data/test/zoneinfo/zone1970.tab +369 -0
  109. data/tzinfo.gemspec +21 -0
  110. metadata +193 -0
  111. metadata.gz.sig +2 -0
@@ -0,0 +1,669 @@
1
+ require 'date'
2
+ require 'set'
3
+ require 'thread_safe'
4
+
5
+ module TZInfo
6
+ # AmbiguousTime is raised to indicates that a specified time in a local
7
+ # timezone has more than one possible equivalent UTC time. This happens when
8
+ # transitioning from daylight savings time to standard time where the clocks
9
+ # are rolled back.
10
+ #
11
+ # AmbiguousTime is raised by period_for_local and local_to_utc when using an
12
+ # ambiguous time and not specifying any means to resolve the ambiguity.
13
+ class AmbiguousTime < StandardError
14
+ end
15
+
16
+ # PeriodNotFound is raised to indicate that no TimezonePeriod matching a given
17
+ # time could be found.
18
+ class PeriodNotFound < StandardError
19
+ end
20
+
21
+ # Raised by Timezone#get if the identifier given is not valid.
22
+ class InvalidTimezoneIdentifier < StandardError
23
+ end
24
+
25
+ # Raised if an attempt is made to use a timezone created with
26
+ # Timezone.new(nil).
27
+ class UnknownTimezone < StandardError
28
+ end
29
+
30
+ # Timezone is the base class of all timezones. It provides a factory method,
31
+ # 'get', to access timezones by identifier. Once a specific Timezone has been
32
+ # retrieved, DateTimes, Times and timestamps can be converted between the UTC
33
+ # and the local time for the zone. For example:
34
+ #
35
+ # tz = TZInfo::Timezone.get('America/New_York')
36
+ # puts tz.utc_to_local(DateTime.new(2005,8,29,15,35,0)).to_s
37
+ # puts tz.local_to_utc(Time.utc(2005,8,29,11,35,0)).to_s
38
+ # puts tz.utc_to_local(1125315300).to_s
39
+ #
40
+ # Each time conversion method returns an object of the same type it was
41
+ # passed.
42
+ #
43
+ # The Timezone class is thread-safe. It is safe to use class and instance
44
+ # methods of Timezone in concurrently executing threads. Instances of Timezone
45
+ # can be shared across thread boundaries.
46
+ class Timezone
47
+ include Comparable
48
+
49
+ # Cache of loaded zones by identifier to avoid using require if a zone
50
+ # has already been loaded.
51
+ #
52
+ # @!visibility private
53
+ @@loaded_zones = nil
54
+
55
+ # Default value of the dst parameter of the local_to_utc and
56
+ # period_for_local methods.
57
+ #
58
+ # @!visibility private
59
+ @@default_dst = nil
60
+
61
+ # Sets the default value of the optional dst parameter of the
62
+ # local_to_utc and period_for_local methods. Can be set to nil, true or
63
+ # false.
64
+ #
65
+ # The value of default_dst defaults to nil if unset.
66
+ def self.default_dst=(value)
67
+ @@default_dst = value.nil? ? nil : !!value
68
+ end
69
+
70
+ # Gets the default value of the optional dst parameter of the
71
+ # local_to_utc and period_for_local methods. Can be set to nil, true or
72
+ # false.
73
+ def self.default_dst
74
+ @@default_dst
75
+ end
76
+
77
+ # Returns a timezone by its identifier (e.g. "Europe/London",
78
+ # "America/Chicago" or "UTC").
79
+ #
80
+ # Raises InvalidTimezoneIdentifier if the timezone couldn't be found.
81
+ def self.get(identifier)
82
+ instance = @@loaded_zones[identifier]
83
+
84
+ unless instance
85
+ # Thread-safety: It is possible that multiple equivalent Timezone
86
+ # instances could be created here in concurrently executing threads.
87
+ # The consequences of this are that the data may be loaded more than
88
+ # once (depending on the data source) and memoized calculations could
89
+ # be discarded. The performance benefit of ensuring that only a single
90
+ # instance is created is unlikely to be worth the overhead of only
91
+ # allowing one Timezone to be loaded at a time.
92
+ info = data_source.load_timezone_info(identifier)
93
+ instance = info.create_timezone
94
+ @@loaded_zones[instance.identifier] = instance
95
+ end
96
+
97
+ instance
98
+ end
99
+
100
+ # Returns a proxy for the Timezone with the given identifier. The proxy
101
+ # will cause the real timezone to be loaded when an attempt is made to
102
+ # find a period or convert a time. get_proxy will not validate the
103
+ # identifier. If an invalid identifier is specified, no exception will be
104
+ # raised until the proxy is used.
105
+ def self.get_proxy(identifier)
106
+ TimezoneProxy.new(identifier)
107
+ end
108
+
109
+ # If identifier is nil calls super(), otherwise calls get. An identfier
110
+ # should always be passed in when called externally.
111
+ def self.new(identifier = nil)
112
+ if identifier
113
+ get(identifier)
114
+ else
115
+ super()
116
+ end
117
+ end
118
+
119
+ # Returns an array containing all the available Timezones.
120
+ #
121
+ # Returns TimezoneProxy objects to avoid the overhead of loading Timezone
122
+ # definitions until a conversion is actually required.
123
+ def self.all
124
+ get_proxies(all_identifiers)
125
+ end
126
+
127
+ # Returns an array containing the identifiers of all the available
128
+ # Timezones.
129
+ def self.all_identifiers
130
+ data_source.timezone_identifiers
131
+ end
132
+
133
+ # Returns an array containing all the available Timezones that are based
134
+ # on data (are not links to other Timezones).
135
+ #
136
+ # Returns TimezoneProxy objects to avoid the overhead of loading Timezone
137
+ # definitions until a conversion is actually required.
138
+ def self.all_data_zones
139
+ get_proxies(all_data_zone_identifiers)
140
+ end
141
+
142
+ # Returns an array containing the identifiers of all the available
143
+ # Timezones that are based on data (are not links to other Timezones)..
144
+ def self.all_data_zone_identifiers
145
+ data_source.data_timezone_identifiers
146
+ end
147
+
148
+ # Returns an array containing all the available Timezones that are links
149
+ # to other Timezones.
150
+ #
151
+ # Returns TimezoneProxy objects to avoid the overhead of loading Timezone
152
+ # definitions until a conversion is actually required.
153
+ def self.all_linked_zones
154
+ get_proxies(all_linked_zone_identifiers)
155
+ end
156
+
157
+ # Returns an array containing the identifiers of all the available
158
+ # Timezones that are links to other Timezones.
159
+ def self.all_linked_zone_identifiers
160
+ data_source.linked_timezone_identifiers
161
+ end
162
+
163
+ # Returns all the Timezones defined for all Countries. This is not the
164
+ # complete set of Timezones as some are not country specific (e.g.
165
+ # 'Etc/GMT').
166
+ #
167
+ # Returns TimezoneProxy objects to avoid the overhead of loading Timezone
168
+ # definitions until a conversion is actually required.
169
+ def self.all_country_zones
170
+ Country.all_codes.inject([]) do |zones,country|
171
+ zones += Country.get(country).zones
172
+ end.uniq
173
+ end
174
+
175
+ # Returns all the zone identifiers defined for all Countries. This is not the
176
+ # complete set of zone identifiers as some are not country specific (e.g.
177
+ # 'Etc/GMT'). You can obtain a Timezone instance for a given identifier
178
+ # with the get method.
179
+ def self.all_country_zone_identifiers
180
+ Country.all_codes.inject([]) do |zones,country|
181
+ zones += Country.get(country).zone_identifiers
182
+ end.uniq
183
+ end
184
+
185
+ # Returns all US Timezone instances. A shortcut for
186
+ # TZInfo::Country.get('US').zones.
187
+ #
188
+ # Returns TimezoneProxy objects to avoid the overhead of loading Timezone
189
+ # definitions until a conversion is actually required.
190
+ def self.us_zones
191
+ Country.get('US').zones
192
+ end
193
+
194
+ # Returns all US zone identifiers. A shortcut for
195
+ # TZInfo::Country.get('US').zone_identifiers.
196
+ def self.us_zone_identifiers
197
+ Country.get('US').zone_identifiers
198
+ end
199
+
200
+ # The identifier of the timezone, e.g. "Europe/Paris".
201
+ def identifier
202
+ raise_unknown_timezone
203
+ end
204
+
205
+ # An alias for identifier.
206
+ def name
207
+ # Don't use alias, as identifier gets overridden.
208
+ identifier
209
+ end
210
+
211
+ # Returns a friendlier version of the identifier.
212
+ def to_s
213
+ friendly_identifier
214
+ end
215
+
216
+ # Returns internal object state as a programmer-readable string.
217
+ def inspect
218
+ "#<#{self.class}: #{identifier}>"
219
+ end
220
+
221
+ # Returns a friendlier version of the identifier. Set skip_first_part to
222
+ # omit the first part of the identifier (typically a region name) where
223
+ # there is more than one part.
224
+ #
225
+ # For example:
226
+ #
227
+ # Timezone.get('Europe/Paris').friendly_identifier(false) #=> "Europe - Paris"
228
+ # Timezone.get('Europe/Paris').friendly_identifier(true) #=> "Paris"
229
+ # Timezone.get('America/Indiana/Knox').friendly_identifier(false) #=> "America - Knox, Indiana"
230
+ # Timezone.get('America/Indiana/Knox').friendly_identifier(true) #=> "Knox, Indiana"
231
+ def friendly_identifier(skip_first_part = false)
232
+ parts = identifier.split('/')
233
+ if parts.empty?
234
+ # shouldn't happen
235
+ identifier
236
+ elsif parts.length == 1
237
+ parts[0]
238
+ else
239
+ prefix = skip_first_part ? nil : "#{parts[0]} - "
240
+
241
+ parts = parts.drop(1).map do |part|
242
+ part.gsub!(/_/, ' ')
243
+
244
+ if part.index(/[a-z]/)
245
+ # Missing a space if a lower case followed by an upper case and the
246
+ # name isn't McXxxx.
247
+ part.gsub!(/([^M][a-z])([A-Z])/, '\1 \2')
248
+ part.gsub!(/([M][a-bd-z])([A-Z])/, '\1 \2')
249
+
250
+ # Missing an apostrophe if two consecutive upper case characters.
251
+ part.gsub!(/([A-Z])([A-Z])/, '\1\'\2')
252
+ end
253
+
254
+ part
255
+ end
256
+
257
+ "#{prefix}#{parts.reverse.join(', ')}"
258
+ end
259
+ end
260
+
261
+ # Returns the TimezonePeriod for the given UTC time. utc can either be
262
+ # a DateTime, Time or integer timestamp (Time.to_i). Any timezone
263
+ # information in utc is ignored (it is treated as a UTC time).
264
+ def period_for_utc(utc)
265
+ raise_unknown_timezone
266
+ end
267
+
268
+ # Returns the set of TimezonePeriod instances that are valid for the given
269
+ # local time as an array. If you just want a single period, use
270
+ # period_for_local instead and specify how ambiguities should be resolved.
271
+ # Returns an empty array if no periods are found for the given time.
272
+ def periods_for_local(local)
273
+ raise_unknown_timezone
274
+ end
275
+
276
+ # Returns an Array of TimezoneTransition instances representing the times
277
+ # where the UTC offset of the timezone changes.
278
+ #
279
+ # Transitions are returned up to a given date and time up to a given date
280
+ # and time, specified in UTC (utc_to).
281
+ #
282
+ # A from date and time may also be supplied using the utc_from parameter
283
+ # (also specified in UTC). If utc_from is not nil, only transitions from
284
+ # that date and time onwards will be returned.
285
+ #
286
+ # Comparisons with utc_to are exclusive. Comparisons with utc_from are
287
+ # inclusive. If a transition falls precisely on utc_to, it will be excluded.
288
+ # If a transition falls on utc_from, it will be included.
289
+ #
290
+ # Transitions returned are ordered by when they occur, from earliest to
291
+ # latest.
292
+ #
293
+ # utc_to and utc_from can be specified using either DateTime, Time or
294
+ # integer timestamps (Time.to_i).
295
+ #
296
+ # If utc_from is specified and utc_to is not greater than utc_from, then
297
+ # transitions_up_to raises an ArgumentError exception.
298
+ def transitions_up_to(utc_to, utc_from = nil)
299
+ raise_unknown_timezone
300
+ end
301
+
302
+ # Returns the canonical Timezone instance for this Timezone.
303
+ #
304
+ # The IANA Time Zone database contains two types of definition: Zones and
305
+ # Links. Zones are defined by rules that set out when transitions occur.
306
+ # Links are just references to fully defined Zone, creating an alias for
307
+ # that Zone.
308
+ #
309
+ # Links are commonly used where a time zone has been renamed in a
310
+ # release of the Time Zone database. For example, the Zone US/Eastern was
311
+ # renamed as America/New_York. A US/Eastern Link was added in its place,
312
+ # linking to (and creating an alias for) for America/New_York.
313
+ #
314
+ # Links are also used for time zones that are currently identical to a full
315
+ # Zone, but that are administered seperately. For example, Europe/Vatican is
316
+ # a Link to (and alias for) Europe/Rome.
317
+ #
318
+ # For a full Zone, canonical_zone returns self.
319
+ #
320
+ # For a Link, canonical_zone returns a Timezone instance representing the
321
+ # full Zone that the link targets.
322
+ #
323
+ # TZInfo can be used with different data sources (see the documentation for
324
+ # TZInfo::DataSource). Please note that some DataSource implementations may
325
+ # not support distinguishing between full Zones and Links and will treat all
326
+ # time zones as full Zones. In this case, the canonical_zone will always
327
+ # return self.
328
+ #
329
+ # There are two built-in DataSource implementations. RubyDataSource (which
330
+ # will be used if the tzinfo-data gem is available) supports Link zones.
331
+ # ZoneinfoDataSource returns Link zones as if they were full Zones. If the
332
+ # canonical_zone or canonical_identifier methods are required, the
333
+ # tzinfo-data gem should be installed.
334
+ #
335
+ # The TZInfo::DataSource.get method can be used to check which DataSource
336
+ # implementation is being used.
337
+ def canonical_zone
338
+ raise_unknown_timezone
339
+ end
340
+
341
+ # Returns the TimezonePeriod for the given local time. local can either be
342
+ # a DateTime, Time or integer timestamp (Time.to_i). Any timezone
343
+ # information in local is ignored (it is treated as a time in the current
344
+ # timezone).
345
+ #
346
+ # Warning: There are local times that have no equivalent UTC times (e.g.
347
+ # in the transition from standard time to daylight savings time). There are
348
+ # also local times that have more than one UTC equivalent (e.g. in the
349
+ # transition from daylight savings time to standard time).
350
+ #
351
+ # In the first case (no equivalent UTC time), a PeriodNotFound exception
352
+ # will be raised.
353
+ #
354
+ # In the second case (more than one equivalent UTC time), an AmbiguousTime
355
+ # exception will be raised unless the optional dst parameter or block
356
+ # handles the ambiguity.
357
+ #
358
+ # If the ambiguity is due to a transition from daylight savings time to
359
+ # standard time, the dst parameter can be used to select whether the
360
+ # daylight savings time or local time is used. For example,
361
+ #
362
+ # Timezone.get('America/New_York').period_for_local(DateTime.new(2004,10,31,1,30,0))
363
+ #
364
+ # would raise an AmbiguousTime exception.
365
+ #
366
+ # Specifying dst=true would the daylight savings period from April to
367
+ # October 2004. Specifying dst=false would return the standard period
368
+ # from October 2004 to April 2005.
369
+ #
370
+ # If the dst parameter does not resolve the ambiguity, and a block is
371
+ # specified, it is called. The block must take a single parameter - an
372
+ # array of the periods that need to be resolved. The block can select and
373
+ # return a single period or return nil or an empty array
374
+ # to cause an AmbiguousTime exception to be raised.
375
+ #
376
+ # The default value of the dst parameter can be specified by setting
377
+ # Timezone.default_dst. If default_dst is not set, or is set to nil, then
378
+ # an AmbiguousTime exception will be raised in ambiguous situations unless
379
+ # a block is given to resolve the ambiguity.
380
+ def period_for_local(local, dst = Timezone.default_dst)
381
+ results = periods_for_local(local)
382
+
383
+ if results.empty?
384
+ raise PeriodNotFound
385
+ elsif results.size < 2
386
+ results.first
387
+ else
388
+ # ambiguous result try to resolve
389
+
390
+ if !dst.nil?
391
+ matches = results.find_all {|period| period.dst? == dst}
392
+ results = matches if !matches.empty?
393
+ end
394
+
395
+ if results.size < 2
396
+ results.first
397
+ else
398
+ # still ambiguous, try the block
399
+
400
+ if block_given?
401
+ results = yield results
402
+ end
403
+
404
+ if results.is_a?(TimezonePeriod)
405
+ results
406
+ elsif results && results.size == 1
407
+ results.first
408
+ else
409
+ raise AmbiguousTime, "#{local} is an ambiguous local time."
410
+ end
411
+ end
412
+ end
413
+ end
414
+
415
+ # Converts a time in UTC to the local timezone. utc can either be
416
+ # a DateTime, Time or timestamp (Time.to_i). The returned time has the same
417
+ # type as utc. Any timezone information in utc is ignored (it is treated as
418
+ # a UTC time).
419
+ def utc_to_local(utc)
420
+ TimeOrDateTime.wrap(utc) {|wrapped|
421
+ period_for_utc(wrapped).to_local(wrapped)
422
+ }
423
+ end
424
+
425
+ # Converts a time in the local timezone to UTC. local can either be
426
+ # a DateTime, Time or timestamp (Time.to_i). The returned time has the same
427
+ # type as local. Any timezone information in local is ignored (it is treated
428
+ # as a local time).
429
+ #
430
+ # Warning: There are local times that have no equivalent UTC times (e.g.
431
+ # in the transition from standard time to daylight savings time). There are
432
+ # also local times that have more than one UTC equivalent (e.g. in the
433
+ # transition from daylight savings time to standard time).
434
+ #
435
+ # In the first case (no equivalent UTC time), a PeriodNotFound exception
436
+ # will be raised.
437
+ #
438
+ # In the second case (more than one equivalent UTC time), an AmbiguousTime
439
+ # exception will be raised unless the optional dst parameter or block
440
+ # handles the ambiguity.
441
+ #
442
+ # If the ambiguity is due to a transition from daylight savings time to
443
+ # standard time, the dst parameter can be used to select whether the
444
+ # daylight savings time or local time is used. For example,
445
+ #
446
+ # Timezone.get('America/New_York').local_to_utc(DateTime.new(2004,10,31,1,30,0))
447
+ #
448
+ # would raise an AmbiguousTime exception.
449
+ #
450
+ # Specifying dst=true would return 2004-10-31 5:30:00. Specifying dst=false
451
+ # would return 2004-10-31 6:30:00.
452
+ #
453
+ # If the dst parameter does not resolve the ambiguity, and a block is
454
+ # specified, it is called. The block must take a single parameter - an
455
+ # array of the periods that need to be resolved. The block can return a
456
+ # single period to use to convert the time or return nil or an empty array
457
+ # to cause an AmbiguousTime exception to be raised.
458
+ #
459
+ # The default value of the dst parameter can be specified by setting
460
+ # Timezone.default_dst. If default_dst is not set, or is set to nil, then
461
+ # an AmbiguousTime exception will be raised in ambiguous situations unless
462
+ # a block is given to resolve the ambiguity.
463
+ def local_to_utc(local, dst = Timezone.default_dst)
464
+ TimeOrDateTime.wrap(local) {|wrapped|
465
+ if block_given?
466
+ period = period_for_local(wrapped, dst) {|periods| yield periods }
467
+ else
468
+ period = period_for_local(wrapped, dst)
469
+ end
470
+
471
+ period.to_utc(wrapped)
472
+ }
473
+ end
474
+
475
+ # Returns information about offsets used by the Timezone up to a given
476
+ # date and time, specified using UTC (utc_to). The information is returned
477
+ # as an Array of TimezoneOffset instances.
478
+ #
479
+ # A from date and time may also be supplied using the utc_from parameter
480
+ # (also specified in UTC). If utc_from is not nil, only offsets used from
481
+ # that date and time forward will be returned.
482
+ #
483
+ # Comparisons with utc_to are exclusive. Comparisons with utc_from are
484
+ # inclusive.
485
+ #
486
+ # Offsets may be returned in any order.
487
+ #
488
+ # utc_to and utc_from can be specified using either DateTime, Time or
489
+ # integer timestamps (Time.to_i).
490
+ #
491
+ # If utc_from is specified and utc_to is not greater than utc_from, then
492
+ # offsets_up_to raises an ArgumentError exception.
493
+ def offsets_up_to(utc_to, utc_from = nil)
494
+ utc_to = TimeOrDateTime.wrap(utc_to)
495
+ transitions = transitions_up_to(utc_to, utc_from)
496
+
497
+ if transitions.empty?
498
+ # No transitions in the range, find the period that covers it.
499
+
500
+ if utc_from
501
+ # Use the from date as it is inclusive.
502
+ period = period_for_utc(utc_from)
503
+ else
504
+ # utc_to is exclusive, so this can't be used with period_for_utc.
505
+ # However, any time earlier than utc_to can be used.
506
+
507
+ # Subtract 1 hour (since this is one of the cached OffsetRationals).
508
+ # Use add_with_convert so that conversion to DateTime is performed if
509
+ # required.
510
+ period = period_for_utc(utc_to.add_with_convert(-3600))
511
+ end
512
+
513
+ [period.offset]
514
+ else
515
+ result = Set.new
516
+
517
+ first = transitions.first
518
+ result << first.previous_offset unless utc_from && first.at == utc_from
519
+
520
+ transitions.each do |t|
521
+ result << t.offset
522
+ end
523
+
524
+ result.to_a
525
+ end
526
+ end
527
+
528
+ # Returns the canonical identifier for this Timezone.
529
+ #
530
+ # This is a shortcut for calling canonical_zone.identifier. Please refer
531
+ # to the canonical_zone documentation for further information.
532
+ def canonical_identifier
533
+ canonical_zone.identifier
534
+ end
535
+
536
+ # Returns the current time in the timezone as a Time.
537
+ def now
538
+ utc_to_local(Time.now.utc)
539
+ end
540
+
541
+ # Returns the TimezonePeriod for the current time.
542
+ def current_period
543
+ period_for_utc(Time.now.utc)
544
+ end
545
+
546
+ # Returns the current Time and TimezonePeriod as an array. The first element
547
+ # is the time, the second element is the period.
548
+ def current_period_and_time
549
+ utc = Time.now.utc
550
+ period = period_for_utc(utc)
551
+ [period.to_local(utc), period]
552
+ end
553
+
554
+ alias :current_time_and_period :current_period_and_time
555
+
556
+ # Converts a time in UTC to local time and returns it as a string according
557
+ # to the given format.
558
+ #
559
+ # The formatting is identical to Time.strftime and DateTime.strftime, except
560
+ # %Z and %z are replaced with the timezone abbreviation (for example, EST or
561
+ # EDT) and offset for the specified Timezone and time.
562
+ #
563
+ # The offset can be formatted as follows:
564
+ #
565
+ # - %z - hour and minute (e.g. +0500)
566
+ # - %:z - hour and minute separated with a colon (e.g. +05:00)
567
+ # - %::z - hour minute and second separated with colons (e.g. +05:00:00)
568
+ # - %:::z - hour only (e.g. +05)
569
+ #
570
+ # Timezone#strftime currently handles the replacement of %z. From TZInfo
571
+ # version 2.0.0, %z will be passed to Time#strftime and DateTime#strftime
572
+ # instead. Some of the formatting options may cease to be available
573
+ # depending on the version of Ruby in use (for example, %:::z is only
574
+ # supported by Time#strftime from MRI version 2.0.0 onwards.)
575
+ def strftime(format, utc = Time.now.utc)
576
+ period = period_for_utc(utc)
577
+ local = period.to_local(utc)
578
+ local = Time.at(local).utc unless local.kind_of?(Time) || local.kind_of?(DateTime)
579
+ abbreviation = period.abbreviation.to_s.gsub(/%/, '%%')
580
+
581
+ format = format.gsub(/%(%*)(Z|:*z)/) do
582
+ if $1.length.odd?
583
+ # Escaped literal percent or series of percents. Pass on to strftime.
584
+ "#$1%#$2"
585
+ elsif $2 == "Z"
586
+ "#$1#{abbreviation}"
587
+ else
588
+ m, s = period.utc_total_offset.divmod(60)
589
+ h, m = m.divmod(60)
590
+ case $2.length
591
+ when 1
592
+ "#$1#{'%+03d%02d' % [h,m]}"
593
+ when 2
594
+ "#$1#{'%+03d:%02d' % [h,m]}"
595
+ when 3
596
+ "#$1#{'%+03d:%02d:%02d' % [h,m,s]}"
597
+ when 4
598
+ "#$1#{'%+03d' % [h]}"
599
+ else # more than 3 colons - not a valid option
600
+ # Passing the invalid format string through to Time#strftime or
601
+ # DateTime#strtime would normally result in it being returned in the
602
+ # result. However, with Ruby 1.8.7 on Windows (as tested with Ruby
603
+ # 1.8.7-p374 from http://rubyinstaller.org/downloads/archives), this
604
+ # causes Time#strftime to always return an empty string (e.g.
605
+ # Time.now.strftime('a %::::z b') returns '').
606
+ #
607
+ # Escape the percent to force it to be evaluated as a literal.
608
+ "#$1%%#$2"
609
+ end
610
+ end
611
+ end
612
+
613
+ local.strftime(format)
614
+ end
615
+
616
+ # Compares two Timezones based on their identifier. Returns -1 if tz is less
617
+ # than self, 0 if tz is equal to self and +1 if tz is greater than self.
618
+ #
619
+ # Returns nil if tz is not comparable with Timezone instances.
620
+ def <=>(tz)
621
+ return nil unless tz.is_a?(Timezone)
622
+ identifier <=> tz.identifier
623
+ end
624
+
625
+ # Returns true if and only if the identifier of tz is equal to the
626
+ # identifier of this Timezone.
627
+ def eql?(tz)
628
+ self == tz
629
+ end
630
+
631
+ # Returns a hash of this Timezone.
632
+ def hash
633
+ identifier.hash
634
+ end
635
+
636
+ # Dumps this Timezone for marshalling.
637
+ def _dump(limit)
638
+ identifier
639
+ end
640
+
641
+ # Loads a marshalled Timezone.
642
+ def self._load(data)
643
+ Timezone.get(data)
644
+ end
645
+
646
+ private
647
+ # Initializes @@loaded_zones.
648
+ def self.init_loaded_zones
649
+ @@loaded_zones = ThreadSafe::Cache.new
650
+ end
651
+ init_loaded_zones
652
+
653
+ # Returns an array of proxies corresponding to the given array of
654
+ # identifiers.
655
+ def self.get_proxies(identifiers)
656
+ identifiers.collect {|identifier| get_proxy(identifier)}
657
+ end
658
+
659
+ # Returns the current DataSource.
660
+ def self.data_source
661
+ DataSource.get
662
+ end
663
+
664
+ # Raises an UnknownTimezone exception.
665
+ def raise_unknown_timezone
666
+ raise UnknownTimezone, 'TZInfo::Timezone constructed directly'
667
+ end
668
+ end
669
+ end