time_crisis 0.1.8 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (145) hide show
  1. data/VERSION.yml +2 -2
  2. data/lib/time_crisis.rb +5 -12
  3. data/lib/time_crisis/ext.rb +60 -0
  4. data/lib/time_crisis/support/conversions.rb +4 -0
  5. data/lib/time_crisis/tzinfo.rb +3 -0
  6. data/lib/time_crisis/tzinfo/LICENSE +21 -0
  7. data/lib/time_crisis/tzinfo/data_timezone.rb +27 -0
  8. data/lib/time_crisis/tzinfo/data_timezone_info.rb +208 -0
  9. data/lib/time_crisis/tzinfo/definitions/Africa/Algiers.rb +55 -0
  10. data/lib/time_crisis/tzinfo/definitions/Africa/Cairo.rb +219 -0
  11. data/lib/time_crisis/tzinfo/definitions/Africa/Casablanca.rb +42 -0
  12. data/lib/time_crisis/tzinfo/definitions/Africa/Harare.rb +18 -0
  13. data/lib/time_crisis/tzinfo/definitions/Africa/Johannesburg.rb +25 -0
  14. data/lib/time_crisis/tzinfo/definitions/Africa/Monrovia.rb +22 -0
  15. data/lib/time_crisis/tzinfo/definitions/Africa/Nairobi.rb +23 -0
  16. data/lib/time_crisis/tzinfo/definitions/America/Argentina/Buenos_Aires.rb +84 -0
  17. data/lib/time_crisis/tzinfo/definitions/America/Argentina/San_Juan.rb +86 -0
  18. data/lib/time_crisis/tzinfo/definitions/America/Bogota.rb +23 -0
  19. data/lib/time_crisis/tzinfo/definitions/America/Caracas.rb +23 -0
  20. data/lib/time_crisis/tzinfo/definitions/America/Chicago.rb +283 -0
  21. data/lib/time_crisis/tzinfo/definitions/America/Chihuahua.rb +136 -0
  22. data/lib/time_crisis/tzinfo/definitions/America/Denver.rb +204 -0
  23. data/lib/time_crisis/tzinfo/definitions/America/Godthab.rb +161 -0
  24. data/lib/time_crisis/tzinfo/definitions/America/Guatemala.rb +27 -0
  25. data/lib/time_crisis/tzinfo/definitions/America/Halifax.rb +274 -0
  26. data/lib/time_crisis/tzinfo/definitions/America/Indiana/Indianapolis.rb +149 -0
  27. data/lib/time_crisis/tzinfo/definitions/America/Juneau.rb +194 -0
  28. data/lib/time_crisis/tzinfo/definitions/America/La_Paz.rb +22 -0
  29. data/lib/time_crisis/tzinfo/definitions/America/Lima.rb +35 -0
  30. data/lib/time_crisis/tzinfo/definitions/America/Los_Angeles.rb +232 -0
  31. data/lib/time_crisis/tzinfo/definitions/America/Mazatlan.rb +139 -0
  32. data/lib/time_crisis/tzinfo/definitions/America/Mexico_City.rb +144 -0
  33. data/lib/time_crisis/tzinfo/definitions/America/Monterrey.rb +131 -0
  34. data/lib/time_crisis/tzinfo/definitions/America/New_York.rb +282 -0
  35. data/lib/time_crisis/tzinfo/definitions/America/Phoenix.rb +30 -0
  36. data/lib/time_crisis/tzinfo/definitions/America/Regina.rb +74 -0
  37. data/lib/time_crisis/tzinfo/definitions/America/Santiago.rb +205 -0
  38. data/lib/time_crisis/tzinfo/definitions/America/Sao_Paulo.rb +171 -0
  39. data/lib/time_crisis/tzinfo/definitions/America/St_Johns.rb +288 -0
  40. data/lib/time_crisis/tzinfo/definitions/America/Tijuana.rb +196 -0
  41. data/lib/time_crisis/tzinfo/definitions/Asia/Almaty.rb +67 -0
  42. data/lib/time_crisis/tzinfo/definitions/Asia/Baghdad.rb +73 -0
  43. data/lib/time_crisis/tzinfo/definitions/Asia/Baku.rb +161 -0
  44. data/lib/time_crisis/tzinfo/definitions/Asia/Bangkok.rb +20 -0
  45. data/lib/time_crisis/tzinfo/definitions/Asia/Chongqing.rb +33 -0
  46. data/lib/time_crisis/tzinfo/definitions/Asia/Colombo.rb +30 -0
  47. data/lib/time_crisis/tzinfo/definitions/Asia/Dhaka.rb +29 -0
  48. data/lib/time_crisis/tzinfo/definitions/Asia/Hong_Kong.rb +87 -0
  49. data/lib/time_crisis/tzinfo/definitions/Asia/Irkutsk.rb +165 -0
  50. data/lib/time_crisis/tzinfo/definitions/Asia/Jakarta.rb +30 -0
  51. data/lib/time_crisis/tzinfo/definitions/Asia/Jerusalem.rb +163 -0
  52. data/lib/time_crisis/tzinfo/definitions/Asia/Kabul.rb +20 -0
  53. data/lib/time_crisis/tzinfo/definitions/Asia/Kamchatka.rb +163 -0
  54. data/lib/time_crisis/tzinfo/definitions/Asia/Karachi.rb +114 -0
  55. data/lib/time_crisis/tzinfo/definitions/Asia/Kathmandu.rb +20 -0
  56. data/lib/time_crisis/tzinfo/definitions/Asia/Kolkata.rb +25 -0
  57. data/lib/time_crisis/tzinfo/definitions/Asia/Krasnoyarsk.rb +163 -0
  58. data/lib/time_crisis/tzinfo/definitions/Asia/Kuala_Lumpur.rb +31 -0
  59. data/lib/time_crisis/tzinfo/definitions/Asia/Kuwait.rb +18 -0
  60. data/lib/time_crisis/tzinfo/definitions/Asia/Magadan.rb +163 -0
  61. data/lib/time_crisis/tzinfo/definitions/Asia/Muscat.rb +18 -0
  62. data/lib/time_crisis/tzinfo/definitions/Asia/Novosibirsk.rb +164 -0
  63. data/lib/time_crisis/tzinfo/definitions/Asia/Rangoon.rb +24 -0
  64. data/lib/time_crisis/tzinfo/definitions/Asia/Riyadh.rb +18 -0
  65. data/lib/time_crisis/tzinfo/definitions/Asia/Seoul.rb +34 -0
  66. data/lib/time_crisis/tzinfo/definitions/Asia/Shanghai.rb +35 -0
  67. data/lib/time_crisis/tzinfo/definitions/Asia/Singapore.rb +33 -0
  68. data/lib/time_crisis/tzinfo/definitions/Asia/Taipei.rb +59 -0
  69. data/lib/time_crisis/tzinfo/definitions/Asia/Tashkent.rb +47 -0
  70. data/lib/time_crisis/tzinfo/definitions/Asia/Tbilisi.rb +78 -0
  71. data/lib/time_crisis/tzinfo/definitions/Asia/Tehran.rb +121 -0
  72. data/lib/time_crisis/tzinfo/definitions/Asia/Tokyo.rb +30 -0
  73. data/lib/time_crisis/tzinfo/definitions/Asia/Ulaanbaatar.rb +65 -0
  74. data/lib/time_crisis/tzinfo/definitions/Asia/Urumqi.rb +33 -0
  75. data/lib/time_crisis/tzinfo/definitions/Asia/Vladivostok.rb +164 -0
  76. data/lib/time_crisis/tzinfo/definitions/Asia/Yakutsk.rb +163 -0
  77. data/lib/time_crisis/tzinfo/definitions/Asia/Yekaterinburg.rb +165 -0
  78. data/lib/time_crisis/tzinfo/definitions/Asia/Yerevan.rb +165 -0
  79. data/lib/time_crisis/tzinfo/definitions/Atlantic/Azores.rb +270 -0
  80. data/lib/time_crisis/tzinfo/definitions/Atlantic/Cape_Verde.rb +23 -0
  81. data/lib/time_crisis/tzinfo/definitions/Atlantic/South_Georgia.rb +18 -0
  82. data/lib/time_crisis/tzinfo/definitions/Australia/Adelaide.rb +187 -0
  83. data/lib/time_crisis/tzinfo/definitions/Australia/Brisbane.rb +35 -0
  84. data/lib/time_crisis/tzinfo/definitions/Australia/Darwin.rb +29 -0
  85. data/lib/time_crisis/tzinfo/definitions/Australia/Hobart.rb +193 -0
  86. data/lib/time_crisis/tzinfo/definitions/Australia/Melbourne.rb +185 -0
  87. data/lib/time_crisis/tzinfo/definitions/Australia/Perth.rb +37 -0
  88. data/lib/time_crisis/tzinfo/definitions/Australia/Sydney.rb +185 -0
  89. data/lib/time_crisis/tzinfo/definitions/Etc/UTC.rb +16 -0
  90. data/lib/time_crisis/tzinfo/definitions/Europe/Amsterdam.rb +228 -0
  91. data/lib/time_crisis/tzinfo/definitions/Europe/Athens.rb +185 -0
  92. data/lib/time_crisis/tzinfo/definitions/Europe/Belgrade.rb +163 -0
  93. data/lib/time_crisis/tzinfo/definitions/Europe/Berlin.rb +188 -0
  94. data/lib/time_crisis/tzinfo/definitions/Europe/Bratislava.rb +13 -0
  95. data/lib/time_crisis/tzinfo/definitions/Europe/Brussels.rb +232 -0
  96. data/lib/time_crisis/tzinfo/definitions/Europe/Bucharest.rb +181 -0
  97. data/lib/time_crisis/tzinfo/definitions/Europe/Budapest.rb +197 -0
  98. data/lib/time_crisis/tzinfo/definitions/Europe/Copenhagen.rb +179 -0
  99. data/lib/time_crisis/tzinfo/definitions/Europe/Dublin.rb +276 -0
  100. data/lib/time_crisis/tzinfo/definitions/Europe/Helsinki.rb +163 -0
  101. data/lib/time_crisis/tzinfo/definitions/Europe/Istanbul.rb +218 -0
  102. data/lib/time_crisis/tzinfo/definitions/Europe/Kiev.rb +168 -0
  103. data/lib/time_crisis/tzinfo/definitions/Europe/Lisbon.rb +268 -0
  104. data/lib/time_crisis/tzinfo/definitions/Europe/Ljubljana.rb +13 -0
  105. data/lib/time_crisis/tzinfo/definitions/Europe/London.rb +288 -0
  106. data/lib/time_crisis/tzinfo/definitions/Europe/Madrid.rb +211 -0
  107. data/lib/time_crisis/tzinfo/definitions/Europe/Minsk.rb +170 -0
  108. data/lib/time_crisis/tzinfo/definitions/Europe/Moscow.rb +181 -0
  109. data/lib/time_crisis/tzinfo/definitions/Europe/Paris.rb +232 -0
  110. data/lib/time_crisis/tzinfo/definitions/Europe/Prague.rb +187 -0
  111. data/lib/time_crisis/tzinfo/definitions/Europe/Riga.rb +176 -0
  112. data/lib/time_crisis/tzinfo/definitions/Europe/Rome.rb +215 -0
  113. data/lib/time_crisis/tzinfo/definitions/Europe/Sarajevo.rb +13 -0
  114. data/lib/time_crisis/tzinfo/definitions/Europe/Skopje.rb +13 -0
  115. data/lib/time_crisis/tzinfo/definitions/Europe/Sofia.rb +173 -0
  116. data/lib/time_crisis/tzinfo/definitions/Europe/Stockholm.rb +165 -0
  117. data/lib/time_crisis/tzinfo/definitions/Europe/Tallinn.rb +172 -0
  118. data/lib/time_crisis/tzinfo/definitions/Europe/Vienna.rb +183 -0
  119. data/lib/time_crisis/tzinfo/definitions/Europe/Vilnius.rb +170 -0
  120. data/lib/time_crisis/tzinfo/definitions/Europe/Warsaw.rb +212 -0
  121. data/lib/time_crisis/tzinfo/definitions/Europe/Zagreb.rb +13 -0
  122. data/lib/time_crisis/tzinfo/definitions/Pacific/Auckland.rb +202 -0
  123. data/lib/time_crisis/tzinfo/definitions/Pacific/Fiji.rb +23 -0
  124. data/lib/time_crisis/tzinfo/definitions/Pacific/Guam.rb +22 -0
  125. data/lib/time_crisis/tzinfo/definitions/Pacific/Honolulu.rb +28 -0
  126. data/lib/time_crisis/tzinfo/definitions/Pacific/Majuro.rb +20 -0
  127. data/lib/time_crisis/tzinfo/definitions/Pacific/Midway.rb +25 -0
  128. data/lib/time_crisis/tzinfo/definitions/Pacific/Noumea.rb +25 -0
  129. data/lib/time_crisis/tzinfo/definitions/Pacific/Pago_Pago.rb +26 -0
  130. data/lib/time_crisis/tzinfo/definitions/Pacific/Port_Moresby.rb +20 -0
  131. data/lib/time_crisis/tzinfo/definitions/Pacific/Tongatapu.rb +27 -0
  132. data/lib/time_crisis/tzinfo/info_timezone.rb +32 -0
  133. data/lib/time_crisis/tzinfo/linked_timezone.rb +32 -0
  134. data/lib/time_crisis/tzinfo/linked_timezone_info.rb +24 -0
  135. data/lib/time_crisis/tzinfo/offset_rationals.rb +79 -0
  136. data/lib/time_crisis/tzinfo/ruby_core_support.rb +33 -0
  137. data/lib/time_crisis/tzinfo/time_or_datetime.rb +309 -0
  138. data/lib/time_crisis/tzinfo/timezone.rb +486 -0
  139. data/lib/time_crisis/tzinfo/timezone_definition.rb +36 -0
  140. data/lib/time_crisis/tzinfo/timezone_info.rb +20 -0
  141. data/lib/time_crisis/tzinfo/timezone_offset_info.rb +75 -0
  142. data/lib/time_crisis/tzinfo/timezone_period.rb +179 -0
  143. data/lib/time_crisis/tzinfo/timezone_transition_info.rb +109 -0
  144. data/time_crisis.gemspec +142 -2
  145. metadata +142 -2
@@ -0,0 +1,486 @@
1
+ require 'time_crisis/tzinfo/time_or_datetime'
2
+ require 'time_crisis/tzinfo/timezone_period'
3
+
4
+ module TimeCrisis
5
+ module TZInfo
6
+ # Indicate a specified time in a local timezone has more than one
7
+ # possible time in UTC. This happens when switching from daylight savings time
8
+ # to normal time where the clocks are rolled back. Thrown by period_for_local
9
+ # and local_to_utc when using an ambiguous time and not specifying any
10
+ # means to resolve the ambiguity.
11
+ class AmbiguousTime < StandardError
12
+ end
13
+
14
+ # Thrown to indicate that no TimezonePeriod matching a given time could be found.
15
+ class PeriodNotFound < StandardError
16
+ end
17
+
18
+ # Thrown by Timezone#get if the identifier given is not valid.
19
+ class InvalidTimezoneIdentifier < StandardError
20
+ end
21
+
22
+ # Thrown if an attempt is made to use a timezone created with Timezone.new(nil).
23
+ class UnknownTimezone < StandardError
24
+ end
25
+
26
+ # Timezone is the base class of all timezones. It provides a factory method
27
+ # get to access timezones by identifier. Once a specific Timezone has been
28
+ # retrieved, DateTimes, Times and timestamps can be converted between the UTC
29
+ # and the local time for the zone. For example:
30
+ #
31
+ # tz = TZInfo::Timezone.get('America/New_York')
32
+ # puts tz.utc_to_local(DateTime.new(2005,8,29,15,35,0)).to_s
33
+ # puts tz.local_to_utc(Time.utc(2005,8,29,11,35,0)).to_s
34
+ # puts tz.utc_to_local(1125315300).to_s
35
+ #
36
+ # Each time conversion method returns an object of the same type it was
37
+ # passed.
38
+ #
39
+ # The timezone information all comes from the tz database
40
+ # (see http://www.twinsun.com/tz/tz-link.htm)
41
+ class Timezone
42
+ include Comparable
43
+
44
+ # Cache of loaded zones by identifier to avoid using require if a zone
45
+ # has already been loaded.
46
+ @@loaded_zones = {}
47
+
48
+ # Whether the timezones index has been loaded yet.
49
+ @@index_loaded = false
50
+
51
+ # Returns a timezone by its identifier (e.g. "Europe/London",
52
+ # "America/Chicago" or "UTC").
53
+ #
54
+ # Raises InvalidTimezoneIdentifier if the timezone couldn't be found.
55
+ def self.get(identifier)
56
+ instance = @@loaded_zones[identifier]
57
+ unless instance
58
+ raise InvalidTimezoneIdentifier, 'Invalid identifier' if identifier !~ /^[A-z0-9\+\-_]+(\/[A-z0-9\+\-_]+)*$/
59
+ identifier = identifier.gsub(/-/, '__m__').gsub(/\+/, '__p__')
60
+ begin
61
+ # Use a temporary variable to avoid an rdoc warning
62
+ file = "time_crisis/tzinfo/definitions/#{identifier}".untaint
63
+ require file
64
+
65
+ m = Definitions
66
+ identifier.split(/\//).each {|part|
67
+ m = m.const_get(part)
68
+ }
69
+
70
+ info = m.get
71
+
72
+ # Could make Timezone subclasses register an interest in an info
73
+ # type. Since there are currently only two however, there isn't
74
+ # much point.
75
+ if info.kind_of?(DataTimezoneInfo)
76
+ instance = DataTimezone.new(info)
77
+ elsif info.kind_of?(LinkedTimezoneInfo)
78
+ instance = LinkedTimezone.new(info)
79
+ else
80
+ raise InvalidTimezoneIdentifier, "No handler for info type #{info.class}"
81
+ end
82
+
83
+ @@loaded_zones[instance.identifier] = instance
84
+ rescue LoadError, NameError => e
85
+ raise InvalidTimezoneIdentifier, e.message
86
+ end
87
+ end
88
+
89
+ instance
90
+ end
91
+
92
+ # Returns a proxy for the Timezone with the given identifier. The proxy
93
+ # will cause the real timezone to be loaded when an attempt is made to
94
+ # find a period or convert a time. get_proxy will not validate the
95
+ # identifier. If an invalid identifier is specified, no exception will be
96
+ # raised until the proxy is used.
97
+ def self.get_proxy(identifier)
98
+ TimezoneProxy.new(identifier)
99
+ end
100
+
101
+ # If identifier is nil calls super(), otherwise calls get. An identfier
102
+ # should always be passed in when called externally.
103
+ def self.new(identifier = nil)
104
+ if identifier
105
+ get(identifier)
106
+ else
107
+ super()
108
+ end
109
+ end
110
+
111
+ # Returns an array containing all the available Timezones.
112
+ #
113
+ # Returns TimezoneProxy objects to avoid the overhead of loading Timezone
114
+ # definitions until a conversion is actually required.
115
+ def self.all
116
+ get_proxies(all_identifiers)
117
+ end
118
+
119
+ # Returns an array containing the identifiers of all the available
120
+ # Timezones.
121
+ def self.all_identifiers
122
+ load_index
123
+ Indexes::Timezones.timezones
124
+ end
125
+
126
+ # Returns an array containing all the available Timezones that are based
127
+ # on data (are not links to other Timezones).
128
+ #
129
+ # Returns TimezoneProxy objects to avoid the overhead of loading Timezone
130
+ # definitions until a conversion is actually required.
131
+ def self.all_data_zones
132
+ get_proxies(all_data_zone_identifiers)
133
+ end
134
+
135
+ # Returns an array containing the identifiers of all the available
136
+ # Timezones that are based on data (are not links to other Timezones)..
137
+ def self.all_data_zone_identifiers
138
+ load_index
139
+ Indexes::Timezones.data_timezones
140
+ end
141
+
142
+ # Returns an array containing all the available Timezones that are links
143
+ # to other Timezones.
144
+ #
145
+ # Returns TimezoneProxy objects to avoid the overhead of loading Timezone
146
+ # definitions until a conversion is actually required.
147
+ def self.all_linked_zones
148
+ get_proxies(all_linked_zone_identifiers)
149
+ end
150
+
151
+ # Returns an array containing the identifiers of all the available
152
+ # Timezones that are links to other Timezones.
153
+ def self.all_linked_zone_identifiers
154
+ load_index
155
+ Indexes::Timezones.linked_timezones
156
+ end
157
+
158
+ # Returns all the Timezones defined for all Countries. This is not the
159
+ # complete set of Timezones as some are not country specific (e.g.
160
+ # 'Etc/GMT').
161
+ #
162
+ # Returns TimezoneProxy objects to avoid the overhead of loading Timezone
163
+ # definitions until a conversion is actually required.
164
+ def self.all_country_zones
165
+ Country.all_codes.inject([]) {|zones, country|
166
+ zones += Country.get(country).zones
167
+ }
168
+ end
169
+
170
+ # Returns all the zone identifiers defined for all Countries. This is not the
171
+ # complete set of zone identifiers as some are not country specific (e.g.
172
+ # 'Etc/GMT'). You can obtain a Timezone instance for a given identifier
173
+ # with the get method.
174
+ def self.all_country_zone_identifiers
175
+ Country.all_codes.inject([]) {|zones, country|
176
+ zones += Country.get(country).zone_identifiers
177
+ }
178
+ end
179
+
180
+ # Returns all US Timezone instances. A shortcut for
181
+ # TZInfo::Country.get('US').zones.
182
+ #
183
+ # Returns TimezoneProxy objects to avoid the overhead of loading Timezone
184
+ # definitions until a conversion is actually required.
185
+ def self.us_zones
186
+ Country.get('US').zones
187
+ end
188
+
189
+ # Returns all US zone identifiers. A shortcut for
190
+ # TZInfo::Country.get('US').zone_identifiers.
191
+ def self.us_zone_identifiers
192
+ Country.get('US').zone_identifiers
193
+ end
194
+
195
+ # The identifier of the timezone, e.g. "Europe/Paris".
196
+ def identifier
197
+ raise UnknownTimezone, 'TZInfo::Timezone constructed directly'
198
+ end
199
+
200
+ # An alias for identifier.
201
+ def name
202
+ # Don't use alias, as identifier gets overridden.
203
+ identifier
204
+ end
205
+
206
+ # Returns a friendlier version of the identifier.
207
+ def to_s
208
+ friendly_identifier
209
+ end
210
+
211
+ # Returns internal object state as a programmer-readable string.
212
+ def inspect
213
+ "#<#{self.class}: #{identifier}>"
214
+ end
215
+
216
+ # Returns a friendlier version of the identifier. Set skip_first_part to
217
+ # omit the first part of the identifier (typically a region name) where
218
+ # there is more than one part.
219
+ #
220
+ # For example:
221
+ #
222
+ # Timezone.get('Europe/Paris').friendly_identifier(false) #=> "Europe - Paris"
223
+ # Timezone.get('Europe/Paris').friendly_identifier(true) #=> "Paris"
224
+ # Timezone.get('America/Indiana/Knox').friendly_identifier(false) #=> "America - Knox, Indiana"
225
+ # Timezone.get('America/Indiana/Knox').friendly_identifier(true) #=> "Knox, Indiana"
226
+ def friendly_identifier(skip_first_part = false)
227
+ parts = identifier.split('/')
228
+ if parts.empty?
229
+ # shouldn't happen
230
+ identifier
231
+ elsif parts.length == 1
232
+ parts[0]
233
+ else
234
+ if skip_first_part
235
+ result = ''
236
+ else
237
+ result = parts[0] + ' - '
238
+ end
239
+
240
+ parts[1, parts.length - 1].reverse_each {|part|
241
+ part.gsub!(/_/, ' ')
242
+
243
+ if part.index(/[a-z]/)
244
+ # Missing a space if a lower case followed by an upper case and the
245
+ # name isn't McXxxx.
246
+ part.gsub!(/([^M][a-z])([A-Z])/, '\1 \2')
247
+ part.gsub!(/([M][a-bd-z])([A-Z])/, '\1 \2')
248
+
249
+ # Missing an apostrophe if two consecutive upper case characters.
250
+ part.gsub!(/([A-Z])([A-Z])/, '\1\'\2')
251
+ end
252
+
253
+ result << part
254
+ result << ', '
255
+ }
256
+
257
+ result.slice!(result.length - 2, 2)
258
+ result
259
+ end
260
+ end
261
+
262
+ # Returns the TimezonePeriod for the given UTC time. utc can either be
263
+ # a DateTime, Time or integer timestamp (Time.to_i). Any timezone
264
+ # information in utc is ignored (it is treated as a UTC time).
265
+ def period_for_utc(utc)
266
+ raise UnknownTimezone, 'TZInfo::Timezone constructed directly'
267
+ end
268
+
269
+ # Returns the set of TimezonePeriod instances that are valid for the given
270
+ # local time as an array. If you just want a single period, use
271
+ # period_for_local instead and specify how ambiguities should be resolved.
272
+ # Returns an empty array if no periods are found for the given time.
273
+ def periods_for_local(local)
274
+ raise UnknownTimezone, 'TZInfo::Timezone constructed directly'
275
+ end
276
+
277
+ # Returns the TimezonePeriod for the given local time. local can either be
278
+ # a DateTime, Time or integer timestamp (Time.to_i). Any timezone
279
+ # information in local is ignored (it is treated as a time in the current
280
+ # timezone).
281
+ #
282
+ # Warning: There are local times that have no equivalent UTC times (e.g.
283
+ # in the transition from standard time to daylight savings time). There are
284
+ # also local times that have more than one UTC equivalent (e.g. in the
285
+ # transition from daylight savings time to standard time).
286
+ #
287
+ # In the first case (no equivalent UTC time), a PeriodNotFound exception
288
+ # will be raised.
289
+ #
290
+ # In the second case (more than one equivalent UTC time), an AmbiguousTime
291
+ # exception will be raised unless the optional dst parameter or block
292
+ # handles the ambiguity.
293
+ #
294
+ # If the ambiguity is due to a transition from daylight savings time to
295
+ # standard time, the dst parameter can be used to select whether the
296
+ # daylight savings time or local time is used. For example,
297
+ #
298
+ # Timezone.get('America/New_York').period_for_local(DateTime.new(2004,10,31,1,30,0))
299
+ #
300
+ # would raise an AmbiguousTime exception.
301
+ #
302
+ # Specifying dst=true would the daylight savings period from April to
303
+ # October 2004. Specifying dst=false would return the standard period
304
+ # from October 2004 to April 2005.
305
+ #
306
+ # If the dst parameter does not resolve the ambiguity, and a block is
307
+ # specified, it is called. The block must take a single parameter - an
308
+ # array of the periods that need to be resolved. The block can select and
309
+ # return a single period or return nil or an empty array
310
+ # to cause an AmbiguousTime exception to be raised.
311
+ def period_for_local(local, dst = nil)
312
+ results = periods_for_local(local)
313
+
314
+ if results.empty?
315
+ raise PeriodNotFound
316
+ elsif results.size < 2
317
+ results.first
318
+ else
319
+ # ambiguous result try to resolve
320
+
321
+ if !dst.nil?
322
+ matches = results.find_all {|period| period.dst? == dst}
323
+ results = matches if !matches.empty?
324
+ end
325
+
326
+ if results.size < 2
327
+ results.first
328
+ else
329
+ # still ambiguous, try the block
330
+
331
+ if block_given?
332
+ results = yield results
333
+ end
334
+
335
+ if results.is_a?(TimezonePeriod)
336
+ results
337
+ elsif results && results.size == 1
338
+ results.first
339
+ else
340
+ raise AmbiguousTime, "#{local} is an ambiguous local time."
341
+ end
342
+ end
343
+ end
344
+ end
345
+
346
+ # Converts a time in UTC to the local timezone. utc can either be
347
+ # a DateTime, Time or timestamp (Time.to_i). The returned time has the same
348
+ # type as utc. Any timezone information in utc is ignored (it is treated as
349
+ # a UTC time).
350
+ def utc_to_local(utc)
351
+ TimeOrDateTime.wrap(utc) {|wrapped|
352
+ period_for_utc(wrapped).to_local(wrapped)
353
+ }
354
+ end
355
+
356
+ # Converts a time in the local timezone to UTC. local can either be
357
+ # a DateTime, Time or timestamp (Time.to_i). The returned time has the same
358
+ # type as local. Any timezone information in local is ignored (it is treated
359
+ # as a local time).
360
+ #
361
+ # Warning: There are local times that have no equivalent UTC times (e.g.
362
+ # in the transition from standard time to daylight savings time). There are
363
+ # also local times that have more than one UTC equivalent (e.g. in the
364
+ # transition from daylight savings time to standard time).
365
+ #
366
+ # In the first case (no equivalent UTC time), a PeriodNotFound exception
367
+ # will be raised.
368
+ #
369
+ # In the second case (more than one equivalent UTC time), an AmbiguousTime
370
+ # exception will be raised unless the optional dst parameter or block
371
+ # handles the ambiguity.
372
+ #
373
+ # If the ambiguity is due to a transition from daylight savings time to
374
+ # standard time, the dst parameter can be used to select whether the
375
+ # daylight savings time or local time is used. For example,
376
+ #
377
+ # Timezone.get('America/New_York').local_to_utc(DateTime.new(2004,10,31,1,30,0))
378
+ #
379
+ # would raise an AmbiguousTime exception.
380
+ #
381
+ # Specifying dst=true would return 2004-10-31 5:30:00. Specifying dst=false
382
+ # would return 2004-10-31 6:30:00.
383
+ #
384
+ # If the dst parameter does not resolve the ambiguity, and a block is
385
+ # specified, it is called. The block must take a single parameter - an
386
+ # array of the periods that need to be resolved. The block can return a
387
+ # single period to use to convert the time or return nil or an empty array
388
+ # to cause an AmbiguousTime exception to be raised.
389
+ def local_to_utc(local, dst = nil)
390
+ TimeOrDateTime.wrap(local) {|wrapped|
391
+ if block_given?
392
+ period = period_for_local(wrapped, dst) {|periods| yield periods }
393
+ else
394
+ period = period_for_local(wrapped, dst)
395
+ end
396
+
397
+ period.to_utc(wrapped)
398
+ }
399
+ end
400
+
401
+ # Returns the current time in the timezone as a Time.
402
+ def now
403
+ utc_to_local(Time.now.utc)
404
+ end
405
+
406
+ # Returns the TimezonePeriod for the current time.
407
+ def current_period
408
+ period_for_utc(Time.now.utc)
409
+ end
410
+
411
+ # Returns the current Time and TimezonePeriod as an array. The first element
412
+ # is the time, the second element is the period.
413
+ def current_period_and_time
414
+ utc = Time.now.utc
415
+ period = period_for_utc(utc)
416
+ [period.to_local(utc), period]
417
+ end
418
+
419
+ alias :current_time_and_period :current_period_and_time
420
+
421
+ # Converts a time in UTC to local time and returns it as a string
422
+ # according to the given format. The formatting is identical to
423
+ # Time.strftime and DateTime.strftime, except %Z is replaced with the
424
+ # timezone abbreviation for the specified time (for example, EST or EDT).
425
+ def strftime(format, utc = Time.now.utc)
426
+ period = period_for_utc(utc)
427
+ local = period.to_local(utc)
428
+ local = Time.at(local).utc unless local.kind_of?(Time) || local.kind_of?(DateTime)
429
+ abbreviation = period.abbreviation.to_s.gsub(/%/, '%%')
430
+
431
+ format = format.gsub(/(.?)%Z/) do
432
+ if $1 == '%'
433
+ # return %%Z so the real strftime treats it as a literal %Z too
434
+ '%%Z'
435
+ else
436
+ "#$1#{abbreviation}"
437
+ end
438
+ end
439
+
440
+ local.strftime(format)
441
+ end
442
+
443
+ # Compares two Timezones based on their identifier. Returns -1 if tz is less
444
+ # than self, 0 if tz is equal to self and +1 if tz is greater than self.
445
+ def <=>(tz)
446
+ identifier <=> tz.identifier
447
+ end
448
+
449
+ # Returns true if and only if the identifier of tz is equal to the
450
+ # identifier of this Timezone.
451
+ def eql?(tz)
452
+ self == tz
453
+ end
454
+
455
+ # Returns a hash of this Timezone.
456
+ def hash
457
+ identifier.hash
458
+ end
459
+
460
+ # Dumps this Timezone for marshalling.
461
+ def _dump(limit)
462
+ identifier
463
+ end
464
+
465
+ # Loads a marshalled Timezone.
466
+ def self._load(data)
467
+ Timezone.get(data)
468
+ end
469
+
470
+ private
471
+ # Loads in the index of timezones if it hasn't already been loaded.
472
+ def self.load_index
473
+ unless @@index_loaded
474
+ require 'time_crisis/tzinfo/indexes/timezones'
475
+ @@index_loaded = true
476
+ end
477
+ end
478
+
479
+ # Returns an array of proxies corresponding to the given array of
480
+ # identifiers.
481
+ def self.get_proxies(identifiers)
482
+ identifiers.collect {|identifier| get_proxy(identifier)}
483
+ end
484
+ end
485
+ end
486
+ end