tzinfo 1.2.11 → 2.0.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 (151) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/.yardopts +3 -0
  4. data/CHANGES.md +469 -431
  5. data/LICENSE +13 -13
  6. data/README.md +368 -114
  7. data/lib/tzinfo/country.rb +131 -129
  8. data/lib/tzinfo/country_timezone.rb +70 -112
  9. data/lib/tzinfo/data_source.rb +389 -144
  10. data/lib/tzinfo/data_sources/constant_offset_data_timezone_info.rb +56 -0
  11. data/lib/tzinfo/data_sources/country_info.rb +42 -0
  12. data/lib/tzinfo/data_sources/data_timezone_info.rb +91 -0
  13. data/lib/tzinfo/data_sources/linked_timezone_info.rb +33 -0
  14. data/lib/tzinfo/data_sources/ruby_data_source.rb +141 -0
  15. data/lib/tzinfo/data_sources/timezone_info.rb +47 -0
  16. data/lib/tzinfo/data_sources/transitions_data_timezone_info.rb +214 -0
  17. data/lib/tzinfo/data_sources/zoneinfo_data_source.rb +573 -0
  18. data/lib/tzinfo/data_sources/zoneinfo_reader.rb +284 -0
  19. data/lib/tzinfo/data_sources.rb +8 -0
  20. data/lib/tzinfo/data_timezone.rb +33 -47
  21. data/lib/tzinfo/datetime_with_offset.rb +153 -0
  22. data/lib/tzinfo/format1/country_definer.rb +17 -0
  23. data/lib/tzinfo/format1/country_index_definition.rb +64 -0
  24. data/lib/tzinfo/format1/timezone_definer.rb +64 -0
  25. data/lib/tzinfo/format1/timezone_definition.rb +39 -0
  26. data/lib/tzinfo/format1/timezone_index_definition.rb +77 -0
  27. data/lib/tzinfo/format1.rb +10 -0
  28. data/lib/tzinfo/format2/country_definer.rb +68 -0
  29. data/lib/tzinfo/format2/country_index_definer.rb +68 -0
  30. data/lib/tzinfo/format2/country_index_definition.rb +46 -0
  31. data/lib/tzinfo/format2/timezone_definer.rb +94 -0
  32. data/lib/tzinfo/format2/timezone_definition.rb +73 -0
  33. data/lib/tzinfo/format2/timezone_index_definer.rb +45 -0
  34. data/lib/tzinfo/format2/timezone_index_definition.rb +55 -0
  35. data/lib/tzinfo/format2.rb +10 -0
  36. data/lib/tzinfo/info_timezone.rb +26 -21
  37. data/lib/tzinfo/linked_timezone.rb +33 -52
  38. data/lib/tzinfo/offset_timezone_period.rb +42 -0
  39. data/lib/tzinfo/string_deduper.rb +118 -0
  40. data/lib/tzinfo/time_with_offset.rb +128 -0
  41. data/lib/tzinfo/timestamp.rb +548 -0
  42. data/lib/tzinfo/timestamp_with_offset.rb +85 -0
  43. data/lib/tzinfo/timezone.rb +979 -502
  44. data/lib/tzinfo/timezone_offset.rb +84 -74
  45. data/lib/tzinfo/timezone_period.rb +151 -217
  46. data/lib/tzinfo/timezone_proxy.rb +70 -79
  47. data/lib/tzinfo/timezone_transition.rb +77 -109
  48. data/lib/tzinfo/transitions_timezone_period.rb +63 -0
  49. data/lib/tzinfo/version.rb +7 -0
  50. data/lib/tzinfo/with_offset.rb +61 -0
  51. data/lib/tzinfo.rb +60 -40
  52. data.tar.gz.sig +0 -0
  53. metadata +51 -115
  54. metadata.gz.sig +2 -3
  55. data/Rakefile +0 -107
  56. data/lib/tzinfo/annual_rules.rb +0 -51
  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/posix_time_zone_parser.rb +0 -136
  63. data/lib/tzinfo/ruby_core_support.rb +0 -176
  64. data/lib/tzinfo/ruby_country_info.rb +0 -74
  65. data/lib/tzinfo/ruby_data_source.rb +0 -136
  66. data/lib/tzinfo/time_or_datetime.rb +0 -351
  67. data/lib/tzinfo/timezone_definition.rb +0 -36
  68. data/lib/tzinfo/timezone_index_definition.rb +0 -54
  69. data/lib/tzinfo/timezone_info.rb +0 -30
  70. data/lib/tzinfo/timezone_transition_definition.rb +0 -104
  71. data/lib/tzinfo/transition_data_timezone_info.rb +0 -274
  72. data/lib/tzinfo/transition_rule.rb +0 -325
  73. data/lib/tzinfo/zoneinfo_country_info.rb +0 -37
  74. data/lib/tzinfo/zoneinfo_data_source.rb +0 -504
  75. data/lib/tzinfo/zoneinfo_timezone_info.rb +0 -516
  76. data/test/assets/payload.rb +0 -1
  77. data/test/tc_annual_rules.rb +0 -95
  78. data/test/tc_country.rb +0 -240
  79. data/test/tc_country_index_definition.rb +0 -69
  80. data/test/tc_country_info.rb +0 -16
  81. data/test/tc_country_timezone.rb +0 -173
  82. data/test/tc_data_source.rb +0 -218
  83. data/test/tc_data_timezone.rb +0 -99
  84. data/test/tc_data_timezone_info.rb +0 -18
  85. data/test/tc_info_timezone.rb +0 -34
  86. data/test/tc_linked_timezone.rb +0 -155
  87. data/test/tc_linked_timezone_info.rb +0 -23
  88. data/test/tc_offset_rationals.rb +0 -23
  89. data/test/tc_posix_time_zone_parser.rb +0 -261
  90. data/test/tc_ruby_core_support.rb +0 -168
  91. data/test/tc_ruby_country_info.rb +0 -110
  92. data/test/tc_ruby_data_source.rb +0 -175
  93. data/test/tc_time_or_datetime.rb +0 -674
  94. data/test/tc_timezone.rb +0 -1361
  95. data/test/tc_timezone_definition.rb +0 -113
  96. data/test/tc_timezone_index_definition.rb +0 -73
  97. data/test/tc_timezone_info.rb +0 -11
  98. data/test/tc_timezone_london.rb +0 -143
  99. data/test/tc_timezone_melbourne.rb +0 -142
  100. data/test/tc_timezone_new_york.rb +0 -142
  101. data/test/tc_timezone_offset.rb +0 -126
  102. data/test/tc_timezone_period.rb +0 -555
  103. data/test/tc_timezone_proxy.rb +0 -136
  104. data/test/tc_timezone_transition.rb +0 -366
  105. data/test/tc_timezone_transition_definition.rb +0 -295
  106. data/test/tc_timezone_utc.rb +0 -27
  107. data/test/tc_transition_data_timezone_info.rb +0 -433
  108. data/test/tc_transition_rule.rb +0 -663
  109. data/test/tc_zoneinfo_country_info.rb +0 -78
  110. data/test/tc_zoneinfo_data_source.rb +0 -1226
  111. data/test/tc_zoneinfo_timezone_info.rb +0 -2149
  112. data/test/test_utils.rb +0 -214
  113. data/test/ts_all.rb +0 -7
  114. data/test/ts_all_ruby.rb +0 -5
  115. data/test/ts_all_zoneinfo.rb +0 -9
  116. data/test/tzinfo-data/tzinfo/data/definitions/America/Argentina/Buenos_Aires.rb +0 -89
  117. data/test/tzinfo-data/tzinfo/data/definitions/America/New_York.rb +0 -327
  118. data/test/tzinfo-data/tzinfo/data/definitions/Australia/Melbourne.rb +0 -230
  119. data/test/tzinfo-data/tzinfo/data/definitions/EST.rb +0 -19
  120. data/test/tzinfo-data/tzinfo/data/definitions/Etc/GMT__m__1.rb +0 -21
  121. data/test/tzinfo-data/tzinfo/data/definitions/Etc/GMT__p__1.rb +0 -21
  122. data/test/tzinfo-data/tzinfo/data/definitions/Etc/UTC.rb +0 -21
  123. data/test/tzinfo-data/tzinfo/data/definitions/Europe/Amsterdam.rb +0 -273
  124. data/test/tzinfo-data/tzinfo/data/definitions/Europe/Andorra.rb +0 -198
  125. data/test/tzinfo-data/tzinfo/data/definitions/Europe/London.rb +0 -333
  126. data/test/tzinfo-data/tzinfo/data/definitions/Europe/Paris.rb +0 -277
  127. data/test/tzinfo-data/tzinfo/data/definitions/Europe/Prague.rb +0 -235
  128. data/test/tzinfo-data/tzinfo/data/definitions/UTC.rb +0 -16
  129. data/test/tzinfo-data/tzinfo/data/indexes/countries.rb +0 -940
  130. data/test/tzinfo-data/tzinfo/data/indexes/timezones.rb +0 -609
  131. data/test/tzinfo-data/tzinfo/data/version.rb +0 -20
  132. data/test/tzinfo-data/tzinfo/data.rb +0 -8
  133. data/test/zoneinfo/America/Argentina/Buenos_Aires +0 -0
  134. data/test/zoneinfo/America/New_York +0 -0
  135. data/test/zoneinfo/Australia/Melbourne +0 -0
  136. data/test/zoneinfo/EST +0 -0
  137. data/test/zoneinfo/Etc/UTC +0 -0
  138. data/test/zoneinfo/Europe/Amsterdam +0 -0
  139. data/test/zoneinfo/Europe/Andorra +0 -0
  140. data/test/zoneinfo/Europe/London +0 -0
  141. data/test/zoneinfo/Europe/Paris +0 -0
  142. data/test/zoneinfo/Europe/Prague +0 -0
  143. data/test/zoneinfo/Factory +0 -0
  144. data/test/zoneinfo/iso3166.tab +0 -274
  145. data/test/zoneinfo/leapseconds +0 -78
  146. data/test/zoneinfo/posix/Europe/London +0 -0
  147. data/test/zoneinfo/posixrules +0 -0
  148. data/test/zoneinfo/right/Europe/London +0 -0
  149. data/test/zoneinfo/zone.tab +0 -452
  150. data/test/zoneinfo/zone1970.tab +0 -384
  151. data/tzinfo.gemspec +0 -21
@@ -1,252 +1,300 @@
1
- require 'date'
1
+ # encoding: UTF-8
2
+ # frozen_string_literal: true
3
+
2
4
  require 'set'
3
- require 'thread_safe'
4
5
 
5
6
  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.
7
+ # {AmbiguousTime} is raised to indicate that a specified local time has more
8
+ # than one possible equivalent UTC time. Such ambiguities arise when the
9
+ # clocks are set back in a time zone, most commonly during the repeated hour
10
+ # when transitioning from daylight savings time to standard time.
10
11
  #
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.
12
+ # {AmbiguousTime} is raised by {Timezone#local_datetime},
13
+ # {Timezone#local_time}, {Timezone#local_timestamp}, {Timezone#local_to_utc}
14
+ # and {Timezone#period_for_local} when using an ambiguous time and not
15
+ # specifying how to resolve the ambiguity.
13
16
  class AmbiguousTime < StandardError
14
17
  end
15
-
16
- # PeriodNotFound is raised to indicate that no TimezonePeriod matching a given
17
- # time could be found.
18
+
19
+ # {PeriodNotFound} is raised to indicate that no {TimezonePeriod} matching a
20
+ # given time could be found.
18
21
  class PeriodNotFound < StandardError
19
22
  end
20
-
21
- # Raised by Timezone#get if the identifier given is not valid.
23
+
24
+ # {InvalidTimezoneIdentifier} is raised by {Timezone.get} if the identifier
25
+ # given is not valid.
22
26
  class InvalidTimezoneIdentifier < StandardError
23
27
  end
24
-
25
- # Raised if an attempt is made to use a timezone created with
26
- # Timezone.new(nil).
28
+
29
+ # {UnknownTimezone} is raised when calling methods on an instance of
30
+ # {Timezone} that was created directly. To obtain {Timezone} instances the
31
+ # {Timezone.get} method should be used instead.
27
32
  class UnknownTimezone < StandardError
28
33
  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
+ # The {Timezone} class represents a time zone. It provides a factory method,
36
+ # {get}, to retrieve {Timezone} instances by their identifier.
37
+ #
38
+ # The {Timezone#to_local} method can be used to convert `Time` and `DateTime`
39
+ # instances to the local time for the zone. For example:
40
+ #
41
+ # tz = TZInfo::Timezone.get('America/New_York')
42
+ # local_time = tz.to_local(Time.utc(2005,8,29,15,35,0))
43
+ # local_datetime = tz.to_local(DateTime.new(2005,8,29,15,35,0))
34
44
  #
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
45
+ # Local `Time` and `DateTime` instances returned by `Timezone` have the
46
+ # correct local offset.
39
47
  #
40
- # Each time conversion method returns an object of the same type it was
41
- # passed.
48
+ # The {Timezone#local_to_utc} method can by used to convert local `Time` and
49
+ # `DateTime` instances to UTC. {Timezone#local_to_utc} ignores the UTC offset
50
+ # of the supplied value and treats if it is a local time for the zone. For
51
+ # example:
42
52
  #
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.
53
+ # tz = TZInfo::Timezone.get('America/New_York')
54
+ # utc_time = tz.local_to_utc(Time.new(2005,8,29,11,35,0))
55
+ # utc_datetime = tz.local_to_utc(DateTime.new(2005,8,29,11,35,0))
56
+ #
57
+ # Each time zone is treated as sequence of periods of time ({TimezonePeriod})
58
+ # that observe the same offset ({TimezoneOffset}). Transitions
59
+ # ({TimezoneTransition}) denote the end of one period and the start of the
60
+ # next. The {Timezone} class has methods that allow the periods, offsets and
61
+ # transitions of a time zone to be interrogated.
62
+ #
63
+ # All methods that take `Time` objects as parameters can be used with
64
+ # arbitrary `Time`-like objects that respond to both `to_i` and `subsec` and
65
+ # optionally `utc_offset`.
66
+ #
67
+ # The {Timezone} class is thread-safe. It is safe to use class and instance
68
+ # methods of {Timezone} in concurrently executing threads. Instances of
69
+ # {Timezone} can be shared across thread boundaries.
70
+ #
71
+ # The IANA Time Zone Database maintainers recommend that time zone identifiers
72
+ # are not made visible to end-users (see [Names of
73
+ # timezones](https://data.iana.org/time-zones/theory.html#naming)). The
74
+ # {Country} class can be used to obtain lists of time zones by country,
75
+ # including user-friendly descriptions and approximate locations.
76
+ #
77
+ # @abstract The {get} method returns an instance of either {DataTimezone} or
78
+ # {LinkedTimezone}. The {get_proxy} method and other methods returning
79
+ # collections of time zones return instances of {TimezoneProxy}.
46
80
  class Timezone
47
81
  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.
82
+
83
+ # The default value of the dst parameter of the {local_datetime},
84
+ # {local_time}, {local_timestamp}, {local_to_utc} and {period_for_local}
85
+ # methods.
57
86
  #
58
87
  # @!visibility private
59
88
  @@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
89
+
90
+ class << self
91
+ # Sets the default value of the optional `dst` parameter of the
92
+ # {local_datetime}, {local_time}, {local_timestamp}, {local_to_utc} and
93
+ # {period_for_local} methods. Can be set to `nil`, `true` or `false`.
94
+ #
95
+ # @param value [Boolean] `nil`, `true` or `false`.
96
+ def default_dst=(value)
97
+ @@default_dst = value.nil? ? nil : !!value
95
98
  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()
99
+
100
+ # Returns the default value of the optional `dst` parameter of the
101
+ # {local_time}, {local_datetime} and {local_timestamp}, {local_to_utc}
102
+ # and {period_for_local} methods (`nil`, `true` or `false`).
103
+ #
104
+ # {default_dst} defaults to `nil` unless changed with {default_dst=}.
105
+ #
106
+ # @return [Boolean] the default value of the optional `dst` parameter of
107
+ # the {local_time}, {local_datetime} and {local_timestamp},
108
+ # {local_to_utc} and {period_for_local} methods (`nil`, `true` or
109
+ # `false`).
110
+ def default_dst
111
+ @@default_dst
112
+ end
113
+
114
+ # Returns a time zone by its IANA Time Zone Database identifier (e.g.
115
+ # `"Europe/London"` or `"America/Chicago"`). Call {all_identifiers} for a
116
+ # list of all the valid identifiers.
117
+ #
118
+ # The {get} method will return a subclass of {Timezone}, either a
119
+ # {DataTimezone} (for a time zone defined by rules that set out when
120
+ # transitions occur) or a {LinkedTimezone} (for a time zone that is just a
121
+ # link to or alias for a another time zone).
122
+ #
123
+ # @param identifier [String] an IANA Time Zone Database time zone
124
+ # identifier.
125
+ # @return [Timezone] the {Timezone} with the given `identifier`.
126
+ # @raise [InvalidTimezoneIdentifier] if the `identifier` is not valid.
127
+ def get(identifier)
128
+ data_source.get_timezone_info(identifier).create_timezone
129
+ end
130
+
131
+ # Returns a proxy for the time zone with the given identifier. This allows
132
+ # loading of the time zone data to be deferred until it is first needed.
133
+ #
134
+ # The identifier will not be validated. If an invalid identifier is
135
+ # specified, no exception will be raised until the proxy is used.
136
+ #
137
+ # @param identifier [String] an IANA Time Zone Database time zone
138
+ # identifier.
139
+ # @return [TimezoneProxy] a proxy for the time zone with the given
140
+ # `identifier`.
141
+ def get_proxy(identifier)
142
+ TimezoneProxy.new(identifier)
143
+ end
144
+
145
+ # Returns an `Array` of all the available time zones.
146
+ #
147
+ # {TimezoneProxy} instances are returned to avoid the overhead of loading
148
+ # time zone data until it is first needed.
149
+ #
150
+ # @return [Array<Timezone>] all available time zones.
151
+ def all
152
+ get_proxies(all_identifiers)
153
+ end
154
+
155
+ # @return [Array<String>] an `Array` containing the identifiers of all the
156
+ # available time zones.
157
+ def all_identifiers
158
+ data_source.timezone_identifiers
159
+ end
160
+
161
+ # Returns an `Array` of all the available time zones that are
162
+ # defined by offsets and transitions.
163
+ #
164
+ # {TimezoneProxy} instances are returned to avoid the overhead of loading
165
+ # time zone data until it is first needed.
166
+ #
167
+ # @return [Array<Timezone>] an `Array` of all the available time zones
168
+ # that are defined by offsets and transitions.
169
+ def all_data_zones
170
+ get_proxies(all_data_zone_identifiers)
171
+ end
172
+
173
+ # @return [Array<String>] an `Array` of the identifiers of all available
174
+ # time zones that are defined by offsets and transitions.
175
+ def all_data_zone_identifiers
176
+ data_source.data_timezone_identifiers
177
+ end
178
+
179
+ # Returns an `Array` of all the available time zones that are
180
+ # defined as links to / aliases for other time zones.
181
+ #
182
+ # {TimezoneProxy} instances are returned to avoid the overhead of loading
183
+ # time zone data until it is first needed.
184
+ #
185
+ # @return [Array<Timezone>] an `Array` of all the available time zones
186
+ # that are defined as links to / aliases for other time zones.
187
+ def all_linked_zones
188
+ get_proxies(all_linked_zone_identifiers)
189
+ end
190
+
191
+ # @return [Array<String>] an `Array` of the identifiers of all available
192
+ # time zones that are defined as links to / aliases for other time zones.
193
+ def all_linked_zone_identifiers
194
+ data_source.linked_timezone_identifiers
195
+ end
196
+
197
+ # Returns an `Array` of all the time zones that are observed by at least
198
+ # one {Country}. This is not the complete set of time zones as some are
199
+ # not country specific (e.g. `'Etc/GMT'`).
200
+ #
201
+ # {TimezoneProxy} instances are returned to avoid the overhead of loading
202
+ # time zone data until it is first needed.
203
+ #
204
+ # @return [Array<Timezone>] an `Array` of all the time zones that are
205
+ # observed by at least one {Country}.
206
+ def all_country_zones
207
+ Country.all.map(&:zones).flatten.uniq
208
+ end
209
+
210
+ # Returns an `Array` of the identifiers of all the time zones that are
211
+ # observed by at least one {Country}. This is not the complete set of time
212
+ # zone identifiers as some are not country specific (e.g. `'Etc/GMT'`).
213
+ #
214
+ # {TimezoneProxy} instances are returned to avoid the overhead of loading
215
+ # time zone data until it is first needed.
216
+ #
217
+ # @return [Array<String>] an `Array` of the identifiers of all the time
218
+ # zones that are observed by at least one {Country}.
219
+ def all_country_zone_identifiers
220
+ Country.all.map(&:zone_identifiers).flatten.uniq
221
+ end
222
+
223
+ private
224
+
225
+ # @param [Enumerable<String>] identifiers an `Enumerable` of time zone
226
+ # identifiers.
227
+ # @return [Array<TimezoneProxy>] an `Array` of {TimezoneProxy}
228
+ # instances corresponding to the given identifiers.
229
+ def get_proxies(identifiers)
230
+ identifiers.collect {|identifier| get_proxy(identifier)}
231
+ end
232
+
233
+ # @return [DataSource] the current DataSource.
234
+ def data_source
235
+ DataSource.get
116
236
  end
117
237
  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".
238
+
239
+ # @return [String] the identifier of the time zone, for example,
240
+ # `"Europe/Paris"`.
201
241
  def identifier
202
242
  raise_unknown_timezone
203
243
  end
204
-
205
- # An alias for identifier.
244
+
245
+ # @return [String] the identifier of the time zone, for example,
246
+ # `"Europe/Paris"`.
206
247
  def name
207
248
  # Don't use alias, as identifier gets overridden.
208
249
  identifier
209
250
  end
210
-
211
- # Returns a friendlier version of the identifier.
251
+
252
+ # @return [String] {identifier}, modified to make it more readable.
212
253
  def to_s
213
254
  friendly_identifier
214
255
  end
215
-
216
- # Returns internal object state as a programmer-readable string.
256
+
257
+ # @return [String] the internal object state as a programmer-readable
258
+ # `String`.
217
259
  def inspect
218
260
  "#<#{self.class}: #{identifier}>"
219
261
  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.
262
+
263
+ # Returns {identifier}, modified to make it more readable. Set
264
+ # `skip_first_part` to omit the first part of the identifier (typically a
265
+ # region name) where there is more than one part.
224
266
  #
225
267
  # For example:
226
268
  #
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"
269
+ # TZInfo::Timezone.get('Europe/Paris').friendly_identifier(false) #=> "Europe - Paris"
270
+ # TZInfo::Timezone.get('Europe/Paris').friendly_identifier(true) #=> "Paris"
271
+ # TZInfo::Timezone.get('America/Indiana/Knox').friendly_identifier(false) #=> "America - Knox, Indiana"
272
+ # TZInfo::Timezone.get('America/Indiana/Knox').friendly_identifier(true) #=> "Knox, Indiana"
273
+ #
274
+ # @param skip_first_part [Boolean] whether the first part of the identifier
275
+ # (typically a region name) should be omitted.
276
+ # @return [String] the modified identifier.
231
277
  def friendly_identifier(skip_first_part = false)
232
- parts = identifier.split('/')
278
+ id = identifier
279
+ id = id.encode(Encoding::UTF_8) unless id.encoding.ascii_compatible?
280
+ parts = id.split('/')
233
281
  if parts.empty?
234
282
  # shouldn't happen
235
283
  identifier
236
- elsif parts.length == 1
284
+ elsif parts.length == 1
237
285
  parts[0]
238
286
  else
239
287
  prefix = skip_first_part ? nil : "#{parts[0]} - "
240
288
 
241
289
  parts = parts.drop(1).map do |part|
242
290
  part.gsub!(/_/, ' ')
243
-
291
+
244
292
  if part.index(/[a-z]/)
245
293
  # Missing a space if a lower case followed by an upper case and the
246
294
  # name isn't McXxxx.
247
295
  part.gsub!(/([^M][a-z])([A-Z])/, '\1 \2')
248
296
  part.gsub!(/([M][a-bd-z])([A-Z])/, '\1 \2')
249
-
297
+
250
298
  # Missing an apostrophe if two consecutive upper case characters.
251
299
  part.gsub!(/([A-Z])([A-Z])/, '\1\'\2')
252
300
  end
@@ -257,417 +305,846 @@ module TZInfo
257
305
  "#{prefix}#{parts.reverse.join(', ')}"
258
306
  end
259
307
  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)
308
+
309
+ # Returns the {TimezonePeriod} that is valid at a given time.
310
+ #
311
+ # Unlike {period_for_local} and {period_for_utc}, the UTC offset of the
312
+ # `time` parameter is taken into consideration.
313
+ #
314
+ # @param time [Object] a `Time`, `DateTime` or {Timestamp}.
315
+ # @return [TimezonePeriod] the {TimezonePeriod} that is valid at `time`.
316
+ # @raise [ArgumentError] if `time` is `nil`.
317
+ # @raise [ArgumentError] if `time` is a {Timestamp} with an unspecified
318
+ # offset.
319
+ def period_for(time)
273
320
  raise_unknown_timezone
274
321
  end
275
-
276
- # Returns an Array of TimezoneTransition instances representing the times
277
- # where the UTC offset of the timezone changes.
322
+
323
+ # Returns the set of {TimezonePeriod}s that are valid for the given
324
+ # local time as an `Array`.
278
325
  #
279
- # Transitions are returned up to a given date and time up to a given date
280
- # and time, specified in UTC (utc_to).
326
+ # The UTC offset of the `local_time` parameter is ignored (it is treated as
327
+ # a time in the time zone represented by `self`).
281
328
  #
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.
329
+ # This will typically return an `Array` containing a single
330
+ # {TimezonePeriod}. More than one {TimezonePeriod} will be returned when the
331
+ # local time is ambiguous (for example, when daylight savings time ends). An
332
+ # empty `Array` will be returned when the local time is not valid (for
333
+ # example, when daylight savings time begins).
285
334
  #
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.
335
+ # To obtain just a single {TimezonePeriod} in all cases, use
336
+ # {period_for_local} instead and specify how ambiguities should be resolved.
289
337
  #
290
- # Transitions returned are ordered by when they occur, from earliest to
291
- # latest.
338
+ # @param local_time [Object] a `Time`, `DateTime` or {Timestamp}.
339
+ # @return [Array<TimezonePeriod>] the set of {TimezonePeriod}s that are
340
+ # valid at `local_time`.
341
+ # @raise [ArgumentError] if `local_time` is `nil`.
342
+ def periods_for_local(local_time)
343
+ raise_unknown_timezone
344
+ end
345
+
346
+ # Returns an `Array` of {TimezoneTransition} instances representing the
347
+ # times where the UTC offset of the timezone changes.
292
348
  #
293
- # utc_to and utc_from can be specified using either DateTime, Time or
294
- # integer timestamps (Time.to_i).
349
+ # Transitions are returned up to a given time (`to`).
295
350
  #
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)
351
+ # A from time may also be supplied using the `from` parameter. If from is
352
+ # not `nil`, only transitions from that time onwards will be returned.
353
+ #
354
+ # Comparisons with `to` are exclusive. Comparisons with `from` are
355
+ # inclusive. If a transition falls precisely on `to`, it will be excluded.
356
+ # If a transition falls on `from`, it will be included.
357
+ #
358
+ # @param to [Object] a `Time`, `DateTime` or {Timestamp} specifying the
359
+ # latest (exclusive) transition to return.
360
+ # @param from [Object] an optional `Time`, `DateTime` or {Timestamp}
361
+ # specifying the earliest (inclusive) transition to return.
362
+ # @return [Array<TimezoneTransition>] the transitions that are earlier than
363
+ # `to` and, if specified, at or later than `from`. Transitions are ordered
364
+ # by when they occur, from earliest to latest.
365
+ # @raise [ArgumentError] if `from` is specified and `to` is not greater than
366
+ # `from`.
367
+ # @raise [ArgumentError] is raised if `to` is `nil`.
368
+ # @raise [ArgumentError] if either `to` or `from` is a {Timestamp} with an
369
+ # unspecified offset.
370
+ def transitions_up_to(to, from = nil)
299
371
  raise_unknown_timezone
300
372
  end
301
-
302
- # Returns the canonical Timezone instance for this Timezone.
373
+
374
+ # Returns the canonical {Timezone} instance for this {Timezone}.
303
375
  #
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
376
+ # The IANA Time Zone database contains two types of definition: Zones and
377
+ # Links. Zones are defined by rules that set out when transitions occur.
378
+ # Links are just references to fully defined Zone, creating an alias for
307
379
  # that Zone.
308
380
  #
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.
381
+ # Links are commonly used where a time zone has been renamed in a release of
382
+ # the Time Zone database. For example, the US/Eastern Zone was renamed as
383
+ # America/New_York. A US/Eastern Link was added in its place, linking to
384
+ # (and creating an alias for) America/New_York.
313
385
  #
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
386
+ # Links are also used for time zones that are currently identical to a full
387
+ # Zone, but that are administered separately. For example, Europe/Vatican is
316
388
  # a Link to (and alias for) Europe/Rome.
317
389
  #
318
- # For a full Zone, canonical_zone returns self.
390
+ # For a full Zone (implemented by {DataTimezone}), {canonical_zone} returns
391
+ # self.
319
392
  #
320
- # For a Link, canonical_zone returns a Timezone instance representing the
321
- # full Zone that the link targets.
393
+ # For a Link (implemented by {LinkedTimezone}), {canonical_zone} returns a
394
+ # {Timezone} instance representing the full Zone that the link targets.
322
395
  #
323
396
  # 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
397
+ # {TZInfo::DataSource}). Some DataSource implementations may not support
398
+ # distinguishing between full Zones and Links and will treat all time zones
399
+ # as full Zones. In this case, {canonical_zone} will always return `self`.
400
+ #
401
+ # There are two built-in DataSource implementations.
402
+ # {DataSources::RubyDataSource} (which will be used if the tzinfo-data gem
403
+ # is available) supports Link zones. {DataSources::ZoneinfoDataSource}
404
+ # returns Link zones as if they were full Zones. If the {canonical_zone} or
405
+ # {canonical_identifier} methods are needed, the tzinfo-data gem should be
406
+ # installed.
407
+ #
408
+ # The {TZInfo::DataSource.get} method can be used to check which DataSource
336
409
  # implementation is being used.
410
+ #
411
+ # @return [Timezone] the canonical {Timezone} instance for this {Timezone}.
337
412
  def canonical_zone
338
413
  raise_unknown_timezone
339
414
  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).
415
+
416
+ # Returns the {TimezonePeriod} that is valid at a given time.
345
417
  #
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).
418
+ # The UTC offset of the `utc_time` parameter is ignored (it is treated as a
419
+ # UTC time). Use the {period_for} method instead if the UTC offset of the
420
+ # time needs to be taken into consideration.
421
+ #
422
+ # @param utc_time [Object] a `Time`, `DateTime` or {Timestamp}.
423
+ # @return [TimezonePeriod] the {TimezonePeriod} that is valid at `utc_time`.
424
+ # @raise [ArgumentError] if `utc_time` is `nil`.
425
+ def period_for_utc(utc_time)
426
+ raise ArgumentError, 'utc_time must be specified' unless utc_time
427
+ period_for(Timestamp.for(utc_time, :treat_as_utc))
428
+ end
429
+
430
+ # Returns the {TimezonePeriod} that is valid at the given local time.
350
431
  #
351
- # In the first case (no equivalent UTC time), a PeriodNotFound exception
432
+ # The UTC offset of the `local_time` parameter is ignored (it is treated as
433
+ # a time in the time zone represented by `self`). Use the {period_for}
434
+ # method instead if the the UTC offset of the time needs to be taken into
435
+ # consideration.
436
+ #
437
+ # _Warning:_ There are local times that have no equivalent UTC times (for
438
+ # example, during the transition from standard time to daylight savings
439
+ # time). There are also local times that have more than one UTC equivalent
440
+ # (for example, during the transition from daylight savings time to standard
441
+ # time).
442
+ #
443
+ # In the first case (no equivalent UTC time), a {PeriodNotFound} exception
352
444
  # will be raised.
353
445
  #
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.
446
+ # In the second case (more than one equivalent UTC time), an {AmbiguousTime}
447
+ # exception will be raised unless the optional `dst` parameter or block
448
+ # handles the ambiguity.
357
449
  #
358
450
  # 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
-
451
+ # standard time, the `dst` parameter can be used to select whether the
452
+ # daylight savings time or local time is used. For example, the following
453
+ # code would raise an {AmbiguousTime} exception:
454
+ #
455
+ # tz = TZInfo::Timezone.get('America/New_York')
456
+ # tz.period_for_local(Time.new(2004,10,31,1,30,0))
457
+ #
458
+ # Specifying `dst = true` would select the daylight savings period from
459
+ # April to October 2004. Specifying `dst = false` would return the
460
+ # standard time period from October 2004 to April 2005.
461
+ #
462
+ # The `dst` parameter will not be able to resolve an ambiguity resulting
463
+ # from the clocks being set back without changing from daylight savings time
464
+ # to standard time. In this case, if a block is specified, it will be called
465
+ # to resolve the ambiguity. The block must take a single parameter - an
466
+ # `Array` of {TimezonePeriod}s that need to be resolved. The block can
467
+ # select and return a single {TimezonePeriod} or return `nil` or an empty
468
+ # `Array` to cause an {AmbiguousTime} exception to be raised.
469
+ #
470
+ # The default value of the `dst` parameter can be specified using
471
+ # {Timezone.default_dst=}.
472
+ #
473
+ # @param local_time [Object] a `Time`, `DateTime` or {Timestamp}.
474
+ # @param dst [Boolean] whether to resolve ambiguous local times by always
475
+ # selecting the period observing daylight savings time (`true`), always
476
+ # selecting the period observing standard time (`false`), or leaving the
477
+ # ambiguity unresolved (`nil`).
478
+ # @yield [periods] if the `dst` parameter did not resolve an ambiguity, an
479
+ # optional block is yielded to.
480
+ # @yieldparam periods [Array<TimezonePeriod>] an `Array` containing all
481
+ # the {TimezonePeriod}s that still match `local_time` after applying the
482
+ # `dst` parameter.
483
+ # @yieldreturn [Object] to resolve the ambiguity: a chosen {TimezonePeriod}
484
+ # or an `Array` containing a chosen {TimezonePeriod}; to leave the
485
+ # ambiguity unresolved: an empty `Array`, an `Array` containing more than
486
+ # one {TimezonePeriod}, or `nil`.
487
+ # @return [TimezonePeriod] the {TimezonePeriod} that is valid at
488
+ # `local_time`.
489
+ # @raise [ArgumentError] if `local_time` is `nil`.
490
+ # @raise [PeriodNotFound] if `local_time` is not valid for the time zone
491
+ # (there is no equivalent UTC time).
492
+ # @raise [AmbiguousTime] if `local_time` was ambiguous for the time zone and
493
+ # the `dst` parameter or block did not resolve the ambiguity.
494
+ def period_for_local(local_time, dst = Timezone.default_dst)
495
+ raise ArgumentError, 'local_time must be specified' unless local_time
496
+ local_time = Timestamp.for(local_time, :ignore)
497
+ results = periods_for_local(local_time)
498
+
383
499
  if results.empty?
384
- raise PeriodNotFound
500
+ raise PeriodNotFound, "#{local_time.strftime('%Y-%m-%d %H:%M:%S')} is an invalid local time."
385
501
  elsif results.size < 2
386
502
  results.first
387
503
  else
388
504
  # ambiguous result try to resolve
389
-
505
+
390
506
  if !dst.nil?
391
507
  matches = results.find_all {|period| period.dst? == dst}
392
- results = matches if !matches.empty?
508
+ results = matches if !matches.empty?
393
509
  end
394
-
510
+
395
511
  if results.size < 2
396
512
  results.first
397
513
  else
398
514
  # still ambiguous, try the block
399
-
515
+
400
516
  if block_given?
401
517
  results = yield results
402
518
  end
403
-
519
+
404
520
  if results.is_a?(TimezonePeriod)
405
521
  results
406
522
  elsif results && results.size == 1
407
523
  results.first
408
- else
409
- raise AmbiguousTime, "#{local} is an ambiguous local time."
524
+ else
525
+ raise AmbiguousTime, "#{local_time.strftime('%Y-%m-%d %H:%M:%S')} is an ambiguous local time."
410
526
  end
411
527
  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
528
+ end
529
+ end
530
+
531
+ # Converts a time to the local time for the time zone.
532
+ #
533
+ # The result will be of type {TimeWithOffset} (if passed a `Time`),
534
+ # {DateTimeWithOffset} (if passed a `DateTime`) or {TimestampWithOffset} (if
535
+ # passed a {Timestamp}). {TimeWithOffset}, {DateTimeWithOffset} and
536
+ # {TimestampWithOffset} are subclasses of `Time`, `DateTime` and {Timestamp}
537
+ # that provide additional information about the local result.
538
+ #
539
+ # Unlike {utc_to_local}, {to_local} takes the UTC offset of the given time
540
+ # into consideration.
541
+ #
542
+ # @param time [Object] a `Time`, `DateTime` or {Timestamp}.
543
+ # @return [Object] the local equivalent of `time` as a {TimeWithOffset},
544
+ # {DateTimeWithOffset} or {TimestampWithOffset}.
545
+ # @raise [ArgumentError] if `time` is `nil`.
546
+ # @raise [ArgumentError] if `time` is a {Timestamp} that does not have a
547
+ # specified UTC offset.
548
+ def to_local(time)
549
+ raise ArgumentError, 'time must be specified' unless time
550
+
551
+ Timestamp.for(time) do |ts|
552
+ TimestampWithOffset.set_timezone_offset(ts, period_for(ts).offset)
553
+ end
554
+ end
555
+
556
+ # Converts a time in UTC to the local time for the time zone.
557
+ #
558
+ # The result will be of type {TimeWithOffset} (if passed a `Time`),
559
+ # {DateTimeWithOffset} (if passed a `DateTime`) or {TimestampWithOffset} (if
560
+ # passed a {Timestamp}). {TimeWithOffset}, {DateTimeWithOffset} and
561
+ # {TimestampWithOffset} are subclasses of `Time`, `DateTime` and {Timestamp}
562
+ # that provide additional information about the local result.
563
+ #
564
+ # The UTC offset of the `utc_time` parameter is ignored (it is treated as a
565
+ # UTC time). Use the {to_local} method instead if the the UTC offset of the
566
+ # time needs to be taken into consideration.
567
+ #
568
+ # @param utc_time [Object] a `Time`, `DateTime` or {Timestamp}.
569
+ # @return [Object] the local equivalent of `utc_time` as a {TimeWithOffset},
570
+ # {DateTimeWithOffset} or {TimestampWithOffset}.
571
+ # @raise [ArgumentError] if `utc_time` is `nil`.
572
+ def utc_to_local(utc_time)
573
+ raise ArgumentError, 'utc_time must be specified' unless utc_time
574
+
575
+ Timestamp.for(utc_time, :treat_as_utc) do |ts|
576
+ to_local(ts)
577
+ end
578
+ end
579
+
580
+ # Converts a local time for the time zone to UTC.
581
+ #
582
+ # The result will either be a `Time`, `DateTime` or {Timestamp} according to
583
+ # the type of the `local_time` parameter.
584
+ #
585
+ # The UTC offset of the `local_time` parameter is ignored (it is treated as
586
+ # a time in the time zone represented by `self`).
587
+ #
588
+ # _Warning:_ There are local times that have no equivalent UTC times (for
589
+ # example, during the transition from standard time to daylight savings
590
+ # time). There are also local times that have more than one UTC equivalent
591
+ # (for example, during the transition from daylight savings time to standard
592
+ # time).
593
+ #
594
+ # In the first case (no equivalent UTC time), a {PeriodNotFound} exception
436
595
  # will be raised.
437
596
  #
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.
597
+ # In the second case (more than one equivalent UTC time), an {AmbiguousTime}
598
+ # exception will be raised unless the optional `dst` parameter or block
599
+ # handles the ambiguity.
441
600
  #
442
601
  # 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 }
602
+ # standard time, the `dst` parameter can be used to select whether the
603
+ # daylight savings time or local time is used. For example, the following
604
+ # code would raise an {AmbiguousTime} exception:
605
+ #
606
+ # tz = TZInfo::Timezone.get('America/New_York')
607
+ # tz.period_for_local(Time.new(2004,10,31,1,30,0))
608
+ #
609
+ # Specifying `dst = true` would select the daylight savings period from
610
+ # April to October 2004. Specifying `dst = false` would return the
611
+ # standard time period from October 2004 to April 2005.
612
+ #
613
+ # The `dst` parameter will not be able to resolve an ambiguity resulting
614
+ # from the clocks being set back without changing from daylight savings time
615
+ # to standard time. In this case, if a block is specified, it will be called
616
+ # to resolve the ambiguity. The block must take a single parameter - an
617
+ # `Array` of {TimezonePeriod}s that need to be resolved. The block can
618
+ # select and return a single {TimezonePeriod} or return `nil` or an empty
619
+ # `Array` to cause an {AmbiguousTime} exception to be raised.
620
+ #
621
+ # The default value of the `dst` parameter can be specified using
622
+ # {Timezone.default_dst=}.
623
+ #
624
+ # @param local_time [Object] a `Time`, `DateTime` or {Timestamp}.
625
+ # @param dst [Boolean] whether to resolve ambiguous local times by always
626
+ # selecting the period observing daylight savings time (`true`), always
627
+ # selecting the period observing standard time (`false`), or leaving the
628
+ # ambiguity unresolved (`nil`).
629
+ # @yield [periods] if the `dst` parameter did not resolve an ambiguity, an
630
+ # optional block is yielded to.
631
+ # @yieldparam periods [Array<TimezonePeriod>] an `Array` containing all
632
+ # the {TimezonePeriod}s that still match `local_time` after applying the
633
+ # `dst` parameter.
634
+ # @yieldreturn [Object] to resolve the ambiguity: a chosen {TimezonePeriod}
635
+ # or an `Array` containing a chosen {TimezonePeriod}; to leave the
636
+ # ambiguity unresolved: an empty `Array`, an `Array` containing more than
637
+ # one {TimezonePeriod}, or `nil`.
638
+ # @return [Object] the UTC equivalent of `local_time` as a `Time`,
639
+ # `DateTime` or {Timestamp}.
640
+ # @raise [ArgumentError] if `local_time` is `nil`.
641
+ # @raise [PeriodNotFound] if `local_time` is not valid for the time zone
642
+ # (there is no equivalent UTC time).
643
+ # @raise [AmbiguousTime] if `local_time` was ambiguous for the time zone and
644
+ # the `dst` parameter or block did not resolve the ambiguity.
645
+ def local_to_utc(local_time, dst = Timezone.default_dst)
646
+ raise ArgumentError, 'local_time must be specified' unless local_time
647
+
648
+ Timestamp.for(local_time, :ignore) do |ts|
649
+ period = if block_given?
650
+ period_for_local(ts, dst) {|periods| yield periods }
467
651
  else
468
- period = period_for_local(wrapped, dst)
652
+ period_for_local(ts, dst)
469
653
  end
470
-
471
- period.to_utc(wrapped)
472
- }
654
+
655
+ ts.add_and_set_utc_offset(-period.observed_utc_offset, :utc)
656
+ end
473
657
  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.
658
+
659
+ # Creates a `Time` object based on the given (Gregorian calendar) date and
660
+ # time parameters. The parameters are interpreted as a local time in the
661
+ # time zone. The result has the appropriate `utc_offset`, `zone` and
662
+ # {TimeWithOffset#timezone_offset timezone_offset}.
478
663
  #
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.
664
+ # _Warning:_ There are time values that are not valid as local times in a
665
+ # time zone (for example, during the transition from standard time to
666
+ # daylight savings time). There are also time values that are ambiguous,
667
+ # occurring more than once with different offsets to UTC (for example,
668
+ # during the transition from daylight savings time to standard time).
482
669
  #
483
- # Comparisons with utc_to are exclusive. Comparisons with utc_from are
484
- # inclusive.
670
+ # In the first case (an invalid local time), a {PeriodNotFound} exception
671
+ # will be raised.
485
672
  #
486
- # Offsets may be returned in any order.
673
+ # In the second case (more than one occurrence), an {AmbiguousTime}
674
+ # exception will be raised unless the optional `dst` parameter or block
675
+ # handles the ambiguity.
487
676
  #
488
- # utc_to and utc_from can be specified using either DateTime, Time or
489
- # integer timestamps (Time.to_i).
677
+ # If the ambiguity is due to a transition from daylight savings time to
678
+ # standard time, the `dst` parameter can be used to select whether the
679
+ # daylight savings time or local time is used. For example, the following
680
+ # code would raise an {AmbiguousTime} exception:
681
+ #
682
+ # tz = TZInfo::Timezone.get('America/New_York')
683
+ # tz.local_time(2004,10,31,1,30,0,0)
684
+ #
685
+ # Specifying `dst = true` would return a `Time` with a UTC offset of -4
686
+ # hours and abbreviation EDT (Eastern Daylight Time). Specifying `dst =
687
+ # false` would return a `Time` with a UTC offset of -5 hours and
688
+ # abbreviation EST (Eastern Standard Time).
689
+ #
690
+ # The `dst` parameter will not be able to resolve an ambiguity resulting
691
+ # from the clocks being set back without changing from daylight savings time
692
+ # to standard time. In this case, if a block is specified, it will be called
693
+ # to resolve the ambiguity. The block must take a single parameter - an
694
+ # `Array` of {TimezonePeriod}s that need to be resolved. The block can
695
+ # select and return a single {TimezonePeriod} or return `nil` or an empty
696
+ # `Array` to cause an {AmbiguousTime} exception to be raised.
490
697
  #
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
-
698
+ # The default value of the `dst` parameter can be specified using
699
+ # {Timezone.default_dst=}.
700
+ #
701
+ # @param year [Integer] the year.
702
+ # @param month [Integer] the month (1-12).
703
+ # @param day [Integer] the day of the month (1-31).
704
+ # @param hour [Integer] the hour (0-23).
705
+ # @param minute [Integer] the minute (0-59).
706
+ # @param second [Integer] the second (0-59).
707
+ # @param sub_second [Numeric] the fractional part of the second as either
708
+ # a `Rational` that is greater than or equal to 0 and less than 1, or
709
+ # the `Integer` 0.
710
+ # @param dst [Boolean] whether to resolve ambiguous local times by always
711
+ # selecting the period observing daylight savings time (`true`), always
712
+ # selecting the period observing standard time (`false`), or leaving the
713
+ # ambiguity unresolved (`nil`).
714
+ # @yield [periods] if the `dst` parameter did not resolve an ambiguity, an
715
+ # optional block is yielded to.
716
+ # @yieldparam periods [Array<TimezonePeriod>] an `Array` containing all
717
+ # the {TimezonePeriod}s that still match `local_time` after applying the
718
+ # `dst` parameter.
719
+ # @yieldreturn [Object] to resolve the ambiguity: a chosen {TimezonePeriod}
720
+ # or an `Array` containing a chosen {TimezonePeriod}; to leave the
721
+ # ambiguity unresolved: an empty `Array`, an `Array` containing more than
722
+ # one {TimezonePeriod}, or `nil`.
723
+ # @return [TimeWithOffset] a new `Time` object based on the given values,
724
+ # interpreted as a local time in the time zone.
725
+ # @raise [ArgumentError] if either of `year`, `month`, `day`, `hour`,
726
+ # `minute`, or `second` is not an `Integer`.
727
+ # @raise [ArgumentError] if `sub_second` is not a `Rational`, or the
728
+ # `Integer` 0.
729
+ # @raise [ArgumentError] if `utc_offset` is not `nil`, not an `Integer`
730
+ # and not the `Symbol` `:utc`.
731
+ # @raise [RangeError] if `month` is not between 1 and 12.
732
+ # @raise [RangeError] if `day` is not between 1 and 31.
733
+ # @raise [RangeError] if `hour` is not between 0 and 23.
734
+ # @raise [RangeError] if `minute` is not between 0 and 59.
735
+ # @raise [RangeError] if `second` is not between 0 and 59.
736
+ # @raise [RangeError] if `sub_second` is a `Rational` but that is less
737
+ # than 0 or greater than or equal to 1.
738
+ # @raise [PeriodNotFound] if the date and time parameters do not specify a
739
+ # valid local time in the time zone.
740
+ # @raise [AmbiguousTime] if the date and time parameters are ambiguous for
741
+ # the time zone and the `dst` parameter or block did not resolve the
742
+ # ambiguity.
743
+ def local_time(year, month = 1, day = 1, hour = 0, minute = 0, second = 0, sub_second = 0, dst = Timezone.default_dst, &block)
744
+ local_timestamp(year, month, day, hour, minute, second, sub_second, dst, &block).to_time
745
+ end
746
+
747
+ # Creates a `DateTime` object based on the given (Gregorian calendar) date
748
+ # and time parameters. The parameters are interpreted as a local time in the
749
+ # time zone. The result has the appropriate `offset` and
750
+ # {DateTimeWithOffset#timezone_offset timezone_offset}.
751
+ #
752
+ # _Warning:_ There are time values that are not valid as local times in a
753
+ # time zone (for example, during the transition from standard time to
754
+ # daylight savings time). There are also time values that are ambiguous,
755
+ # occurring more than once with different offsets to UTC (for example,
756
+ # during the transition from daylight savings time to standard time).
757
+ #
758
+ # In the first case (an invalid local time), a {PeriodNotFound} exception
759
+ # will be raised.
760
+ #
761
+ # In the second case (more than one occurrence), an {AmbiguousTime}
762
+ # exception will be raised unless the optional `dst` parameter or block
763
+ # handles the ambiguity.
764
+ #
765
+ # If the ambiguity is due to a transition from daylight savings time to
766
+ # standard time, the `dst` parameter can be used to select whether the
767
+ # daylight savings time or local time is used. For example, the following
768
+ # code would raise an {AmbiguousTime} exception:
769
+ #
770
+ # tz = TZInfo::Timezone.get('America/New_York')
771
+ # tz.local_datetime(2004,10,31,1,30,0,0)
772
+ #
773
+ # Specifying `dst = true` would return a `Time` with a UTC offset of -4
774
+ # hours and abbreviation EDT (Eastern Daylight Time). Specifying `dst =
775
+ # false` would return a `Time` with a UTC offset of -5 hours and
776
+ # abbreviation EST (Eastern Standard Time).
777
+ #
778
+ # The `dst` parameter will not be able to resolve an ambiguity resulting
779
+ # from the clocks being set back without changing from daylight savings time
780
+ # to standard time. In this case, if a block is specified, it will be called
781
+ # to resolve the ambiguity. The block must take a single parameter - an
782
+ # `Array` of {TimezonePeriod}s that need to be resolved. The block can
783
+ # select and return a single {TimezonePeriod} or return `nil` or an empty
784
+ # `Array` to cause an {AmbiguousTime} exception to be raised.
785
+ #
786
+ # The default value of the `dst` parameter can be specified using
787
+ # {Timezone.default_dst=}.
788
+ #
789
+ # @param year [Integer] the year.
790
+ # @param month [Integer] the month (1-12).
791
+ # @param day [Integer] the day of the month (1-31).
792
+ # @param hour [Integer] the hour (0-23).
793
+ # @param minute [Integer] the minute (0-59).
794
+ # @param second [Integer] the second (0-59).
795
+ # @param sub_second [Numeric] the fractional part of the second as either
796
+ # a `Rational` that is greater than or equal to 0 and less than 1, or
797
+ # the `Integer` 0.
798
+ # @param dst [Boolean] whether to resolve ambiguous local times by always
799
+ # selecting the period observing daylight savings time (`true`), always
800
+ # selecting the period observing standard time (`false`), or leaving the
801
+ # ambiguity unresolved (`nil`).
802
+ # @yield [periods] if the `dst` parameter did not resolve an ambiguity, an
803
+ # optional block is yielded to.
804
+ # @yieldparam periods [Array<TimezonePeriod>] an `Array` containing all
805
+ # the {TimezonePeriod}s that still match `local_time` after applying the
806
+ # `dst` parameter.
807
+ # @yieldreturn [Object] to resolve the ambiguity: a chosen {TimezonePeriod}
808
+ # or an `Array` containing a chosen {TimezonePeriod}; to leave the
809
+ # ambiguity unresolved: an empty `Array`, an `Array` containing more than
810
+ # one {TimezonePeriod}, or `nil`.
811
+ # @return [DateTimeWithOffset] a new `DateTime` object based on the given
812
+ # values, interpreted as a local time in the time zone.
813
+ # @raise [ArgumentError] if either of `year`, `month`, `day`, `hour`,
814
+ # `minute`, or `second` is not an `Integer`.
815
+ # @raise [ArgumentError] if `sub_second` is not a `Rational`, or the
816
+ # `Integer` 0.
817
+ # @raise [ArgumentError] if `utc_offset` is not `nil`, not an `Integer`
818
+ # and not the `Symbol` `:utc`.
819
+ # @raise [RangeError] if `month` is not between 1 and 12.
820
+ # @raise [RangeError] if `day` is not between 1 and 31.
821
+ # @raise [RangeError] if `hour` is not between 0 and 23.
822
+ # @raise [RangeError] if `minute` is not between 0 and 59.
823
+ # @raise [RangeError] if `second` is not between 0 and 59.
824
+ # @raise [RangeError] if `sub_second` is a `Rational` but that is less
825
+ # than 0 or greater than or equal to 1.
826
+ # @raise [PeriodNotFound] if the date and time parameters do not specify a
827
+ # valid local time in the time zone.
828
+ # @raise [AmbiguousTime] if the date and time parameters are ambiguous for
829
+ # the time zone and the `dst` parameter or block did not resolve the
830
+ # ambiguity.
831
+ def local_datetime(year, month = 1, day = 1, hour = 0, minute = 0, second = 0, sub_second = 0, dst = Timezone.default_dst, &block)
832
+ local_timestamp(year, month, day, hour, minute, second, sub_second, dst, &block).to_datetime
833
+ end
834
+
835
+ # Creates a {Timestamp} object based on the given (Gregorian calendar) date
836
+ # and time parameters. The parameters are interpreted as a local time in the
837
+ # time zone. The result has the appropriate {Timestamp#utc_offset
838
+ # utc_offset} and {TimestampWithOffset#timezone_offset timezone_offset}.
839
+ #
840
+ # _Warning:_ There are time values that are not valid as local times in a
841
+ # time zone (for example, during the transition from standard time to
842
+ # daylight savings time). There are also time values that are ambiguous,
843
+ # occurring more than once with different offsets to UTC (for example,
844
+ # during the transition from daylight savings time to standard time).
845
+ #
846
+ # In the first case (an invalid local time), a {PeriodNotFound} exception
847
+ # will be raised.
848
+ #
849
+ # In the second case (more than one occurrence), an {AmbiguousTime}
850
+ # exception will be raised unless the optional `dst` parameter or block
851
+ # handles the ambiguity.
852
+ #
853
+ # If the ambiguity is due to a transition from daylight savings time to
854
+ # standard time, the `dst` parameter can be used to select whether the
855
+ # daylight savings time or local time is used. For example, the following
856
+ # code would raise an {AmbiguousTime} exception:
857
+ #
858
+ # tz = TZInfo::Timezone.get('America/New_York')
859
+ # tz.local_timestamp(2004,10,31,1,30,0,0)
860
+ #
861
+ # Specifying `dst = true` would return a `Time` with a UTC offset of -4
862
+ # hours and abbreviation EDT (Eastern Daylight Time). Specifying `dst =
863
+ # false` would return a `Time` with a UTC offset of -5 hours and
864
+ # abbreviation EST (Eastern Standard Time).
865
+ #
866
+ # The `dst` parameter will not be able to resolve an ambiguity resulting
867
+ # from the clocks being set back without changing from daylight savings time
868
+ # to standard time. In this case, if a block is specified, it will be called
869
+ # to resolve the ambiguity. The block must take a single parameter - an
870
+ # `Array` of {TimezonePeriod}s that need to be resolved. The block can
871
+ # select and return a single {TimezonePeriod} or return `nil` or an empty
872
+ # `Array` to cause an {AmbiguousTime} exception to be raised.
873
+ #
874
+ # The default value of the `dst` parameter can be specified using
875
+ # {Timezone.default_dst=}.
876
+ #
877
+ # @param year [Integer] the year.
878
+ # @param month [Integer] the month (1-12).
879
+ # @param day [Integer] the day of the month (1-31).
880
+ # @param hour [Integer] the hour (0-23).
881
+ # @param minute [Integer] the minute (0-59).
882
+ # @param second [Integer] the second (0-59).
883
+ # @param sub_second [Numeric] the fractional part of the second as either
884
+ # a `Rational` that is greater than or equal to 0 and less than 1, or
885
+ # the `Integer` 0.
886
+ # @param dst [Boolean] whether to resolve ambiguous local times by always
887
+ # selecting the period observing daylight savings time (`true`), always
888
+ # selecting the period observing standard time (`false`), or leaving the
889
+ # ambiguity unresolved (`nil`).
890
+ # @yield [periods] if the `dst` parameter did not resolve an ambiguity, an
891
+ # optional block is yielded to.
892
+ # @yieldparam periods [Array<TimezonePeriod>] an `Array` containing all
893
+ # the {TimezonePeriod}s that still match `local_time` after applying the
894
+ # `dst` parameter.
895
+ # @yieldreturn [Object] to resolve the ambiguity: a chosen {TimezonePeriod}
896
+ # or an `Array` containing a chosen {TimezonePeriod}; to leave the
897
+ # ambiguity unresolved: an empty `Array`, an `Array` containing more than
898
+ # one {TimezonePeriod}, or `nil`.
899
+ # @return [TimestampWithOffset] a new {Timestamp} object based on the given
900
+ # values, interpreted as a local time in the time zone.
901
+ # @raise [ArgumentError] if either of `year`, `month`, `day`, `hour`,
902
+ # `minute`, or `second` is not an `Integer`.
903
+ # @raise [ArgumentError] if `sub_second` is not a `Rational`, or the
904
+ # `Integer` 0.
905
+ # @raise [ArgumentError] if `utc_offset` is not `nil`, not an `Integer`
906
+ # and not the `Symbol` `:utc`.
907
+ # @raise [RangeError] if `month` is not between 1 and 12.
908
+ # @raise [RangeError] if `day` is not between 1 and 31.
909
+ # @raise [RangeError] if `hour` is not between 0 and 23.
910
+ # @raise [RangeError] if `minute` is not between 0 and 59.
911
+ # @raise [RangeError] if `second` is not between 0 and 59.
912
+ # @raise [RangeError] if `sub_second` is a `Rational` but that is less
913
+ # than 0 or greater than or equal to 1.
914
+ # @raise [PeriodNotFound] if the date and time parameters do not specify a
915
+ # valid local time in the time zone.
916
+ # @raise [AmbiguousTime] if the date and time parameters are ambiguous for
917
+ # the time zone and the `dst` parameter or block did not resolve the
918
+ # ambiguity.
919
+ def local_timestamp(year, month = 1, day = 1, hour = 0, minute = 0, second = 0, sub_second = 0, dst = Timezone.default_dst, &block)
920
+ ts = Timestamp.create(year, month, day, hour, minute, second, sub_second)
921
+ timezone_offset = period_for_local(ts, dst, &block).offset
922
+ utc_offset = timezone_offset.observed_utc_offset
923
+ TimestampWithOffset.new(ts.value - utc_offset, sub_second, utc_offset).set_timezone_offset(timezone_offset)
924
+ end
925
+
926
+ # Returns the unique offsets used by the time zone up to a given time (`to`)
927
+ # as an `Array` of {TimezoneOffset} instances.
928
+ #
929
+ # A from time may also be supplied using the `from` parameter. If from is
930
+ # not `nil`, only offsets used from that time onwards will be returned.
931
+ #
932
+ # Comparisons with `to` are exclusive. Comparisons with `from` are
933
+ # inclusive.
934
+ #
935
+ # @param to [Object] a `Time`, `DateTime` or {Timestamp} specifying the
936
+ # latest (exclusive) offset to return.
937
+ # @param from [Object] an optional `Time`, `DateTime` or {Timestamp}
938
+ # specifying the earliest (inclusive) offset to return.
939
+ # @return [Array<TimezoneOffsets>] the offsets that are used earlier than
940
+ # `to` and, if specified, at or later than `from`. Offsets may be returned
941
+ # in any order.
942
+ # @raise [ArgumentError] if `from` is specified and `to` is not greater than
943
+ # `from`.
944
+ # @raise [ArgumentError] is raised if `to` is `nil`.
945
+ # @raise [ArgumentError] if either `to` or `from` is a {Timestamp} with an
946
+ # unspecified offset.
947
+ def offsets_up_to(to, from = nil)
948
+ raise ArgumentError, 'to must be specified' unless to
949
+
950
+ to_timestamp = Timestamp.for(to)
951
+ from_timestamp = from && Timestamp.for(from)
952
+ transitions = transitions_up_to(to_timestamp, from_timestamp)
953
+
497
954
  if transitions.empty?
498
955
  # No transitions in the range, find the period that covers it.
499
956
 
500
- if utc_from
957
+ if from_timestamp
501
958
  # Use the from date as it is inclusive.
502
- period = period_for_utc(utc_from)
959
+ period = period_for(from_timestamp)
503
960
  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))
961
+ # to is exclusive, so this can't be used with period_for. However, any
962
+ # time earlier than to can be used. Subtract 1 hour.
963
+ period = period_for(to_timestamp.add_and_set_utc_offset(-3600, :utc))
511
964
  end
512
-
965
+
513
966
  [period.offset]
514
967
  else
515
968
  result = Set.new
516
-
517
- first = transitions.first
518
- result << first.previous_offset unless utc_from && first.at == utc_from
519
-
969
+
970
+ first = transitions.first
971
+ result << first.previous_offset unless from_timestamp && first.at == from_timestamp
972
+
520
973
  transitions.each do |t|
521
974
  result << t.offset
522
975
  end
523
-
976
+
524
977
  result.to_a
525
978
  end
526
979
  end
527
-
528
- # Returns the canonical identifier for this Timezone.
980
+
981
+ # Returns the canonical identifier of this time zone.
982
+ #
983
+ # This is a shortcut for calling `canonical_zone.identifier`. Please refer
984
+ # to the {canonical_zone} documentation for further information.
529
985
  #
530
- # This is a shortcut for calling canonical_zone.identifier. Please refer
531
- # to the canonical_zone documentation for further information.
986
+ # @return [String] the canonical identifier of this time zone.
532
987
  def canonical_identifier
533
988
  canonical_zone.identifier
534
989
  end
535
-
536
- # Returns the current time in the timezone as a Time.
990
+
991
+ # @return [TimeWithOffset] the current local time in the time zone.
537
992
  def now
538
- utc_to_local(Time.now.utc)
993
+ to_local(Time.now)
539
994
  end
540
-
541
- # Returns the TimezonePeriod for the current time.
995
+
996
+ # @return [TimezonePeriod] the current {TimezonePeriod} for the time zone.
542
997
  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
- utc = TimeOrDateTime.wrap(utc)
577
- period = period_for_utc(utc)
578
- local_wrapped = period.to_local(utc)
579
- local = local_wrapped.to_orig
580
- local = local_wrapped.to_time unless local.kind_of?(Time) || local.kind_of?(DateTime)
581
- abbreviation = period.abbreviation.to_s.gsub(/%/, '%%')
582
-
583
- format = format.gsub(/%(%*)([sZ]|:*z)/) do
584
- if $1.length.odd?
585
- # Escaped literal percent or series of percents. Pass on to strftime.
586
- "#$1%#$2"
587
- elsif $2 == "s"
588
- "#$1#{utc.to_i}"
589
- elsif $2 == "Z"
590
- "#$1#{abbreviation}"
591
- else
592
- m, s = period.utc_total_offset.divmod(60)
593
- h, m = m.divmod(60)
594
- case $2.length
595
- when 1
596
- "#$1#{'%+03d%02d' % [h,m]}"
597
- when 2
598
- "#$1#{'%+03d:%02d' % [h,m]}"
599
- when 3
600
- "#$1#{'%+03d:%02d:%02d' % [h,m,s]}"
601
- when 4
602
- "#$1#{'%+03d' % [h]}"
603
- else # more than 3 colons - not a valid option
604
- # Passing the invalid format string through to Time#strftime or
605
- # DateTime#strtime would normally result in it being returned in the
606
- # result. However, with Ruby 1.8.7 on Windows (as tested with Ruby
607
- # 1.8.7-p374 from https://rubyinstaller.org/downloads/archives),
608
- # this causes Time#strftime to always return an empty string (e.g.
609
- # Time.now.strftime('a %::::z b') returns '').
610
- #
611
- # Escape the percent to force it to be evaluated as a literal.
612
- "#$1%%#$2"
613
- end
614
- end
998
+ period_for(Time.now)
999
+ end
1000
+
1001
+ # Returns the current local time and {TimezonePeriod} for the time zone as
1002
+ # an `Array`. The first element is the time as a {TimeWithOffset}. The
1003
+ # second element is the period.
1004
+ #
1005
+ # @return [Array] an `Array` containing the current {TimeWithOffset} for the
1006
+ # time zone as the first element and the current {TimezonePeriod} for the
1007
+ # time zone as the second element.
1008
+ def current_time_and_period
1009
+ period = nil
1010
+
1011
+ local_time = Timestamp.for(Time.now) do |ts|
1012
+ period = period_for(ts)
1013
+ TimestampWithOffset.set_timezone_offset(ts, period.offset)
615
1014
  end
616
-
617
- local.strftime(format)
1015
+
1016
+ [local_time, period]
618
1017
  end
619
-
620
- # Compares two Timezones based on their identifier. Returns -1 if tz is less
621
- # than self, 0 if tz is equal to self and +1 if tz is greater than self.
1018
+ alias current_period_and_time current_time_and_period
1019
+
1020
+ # Converts a time to local time for the time zone and returns a `String`
1021
+ # representation of the local time according to the given format.
1022
+ #
1023
+ # `Timezone#strftime` first expands any occurrences of `%Z` in the format
1024
+ # string to the time zone abbreviation for the local time (for example, EST
1025
+ # or EDT). Depending on the type of `time` parameter, the result of the
1026
+ # expansion is then passed to either `Time#strftime`, `DateTime#strftime` or
1027
+ # `Timestamp#strftime` to handle any other format directives.
1028
+ #
1029
+ # This method is equivalent to the following:
622
1030
  #
623
- # Returns nil if tz is not comparable with Timezone instances.
1031
+ # time_zone.to_local(time).strftime(format)
1032
+ #
1033
+ # @param format [String] the format string.
1034
+ # @param time [Object] a `Time`, `DateTime` or `Timestamp`.
1035
+ # @return [String] the formatted local time.
1036
+ # @raise [ArgumentError] if `format` or `time` is `nil`.
1037
+ # @raise [ArgumentError] if `time` is a {Timestamp} with an unspecified UTC
1038
+ # offset.
1039
+ def strftime(format, time = Time.now)
1040
+ to_local(time).strftime(format)
1041
+ end
1042
+
1043
+ # @param time [Object] a `Time`, `DateTime` or `Timestamp`.
1044
+ # @return [String] the abbreviation of this {Timezone} at the given time.
1045
+ # @raise [ArgumentError] if `time` is `nil`.
1046
+ # @raise [ArgumentError] if `time` is a {Timestamp} with an unspecified UTC
1047
+ # offset.
1048
+ def abbreviation(time = Time.now)
1049
+ period_for(time).abbreviation
1050
+ end
1051
+ alias abbr abbreviation
1052
+
1053
+ # @param time [Object] a `Time`, `DateTime` or `Timestamp`.
1054
+ # @return [Boolean] whether daylight savings time is in effect at the given
1055
+ # time.
1056
+ # @raise [ArgumentError] if `time` is `nil`.
1057
+ # @raise [ArgumentError] if `time` is a {Timestamp} with an unspecified UTC
1058
+ # offset.
1059
+ def dst?(time = Time.now)
1060
+ period_for(time).dst?
1061
+ end
1062
+
1063
+ # Returns the base offset from UTC in seconds at the given time. This does
1064
+ # not include any adjustment made for daylight savings time and will
1065
+ # typically remain constant throughout the year.
1066
+ #
1067
+ # To obtain the observed offset from UTC, including the effect of daylight
1068
+ # savings time, use {observed_utc_offset} instead.
1069
+ #
1070
+ # If you require accurate {base_utc_offset} values, you should install the
1071
+ # tzinfo-data gem and set {DataSources::RubyDataSource} as the {DataSource}.
1072
+ # When using {DataSources::ZoneinfoDataSource}, the value of
1073
+ # {base_utc_offset} has to be derived from changes to the observed UTC
1074
+ # offset and DST status since it is not included in zoneinfo files.
1075
+ #
1076
+ # @param time [Object] a `Time`, `DateTime` or `Timestamp`.
1077
+ # @return [Integer] the base offset from UTC in seconds at the given time.
1078
+ # @raise [ArgumentError] if `time` is `nil`.
1079
+ # @raise [ArgumentError] if `time` is a {Timestamp} with an unspecified UTC
1080
+ # offset.
1081
+ def base_utc_offset(time = Time.now)
1082
+ period_for(time).base_utc_offset
1083
+ end
1084
+
1085
+ # Returns the observed offset from UTC in seconds at the given time. This
1086
+ # includes adjustments made for daylight savings time.
1087
+ #
1088
+ # @param time [Object] a `Time`, `DateTime` or `Timestamp`.
1089
+ # @return [Integer] the observed offset from UTC in seconds at the given
1090
+ # time.
1091
+ # @raise [ArgumentError] if `time` is `nil`.
1092
+ # @raise [ArgumentError] if `time` is a {Timestamp} with an unspecified UTC
1093
+ # offset.
1094
+ def observed_utc_offset(time = Time.now)
1095
+ period_for(time).observed_utc_offset
1096
+ end
1097
+ alias utc_offset observed_utc_offset
1098
+
1099
+ # Compares this {Timezone} with another based on the {identifier}.
1100
+ #
1101
+ # @param tz [Object] an `Object` to compare this {Timezone} with.
1102
+ # @return [Integer] -1 if `tz` is less than `self`, 0 if `tz` is equal to
1103
+ # `self` and +1 if `tz` is greater than `self`, or `nil` if `tz` is not an
1104
+ # instance of {Timezone}.
624
1105
  def <=>(tz)
625
1106
  return nil unless tz.is_a?(Timezone)
626
1107
  identifier <=> tz.identifier
627
1108
  end
628
1109
 
629
- # Returns true if and only if the identifier of tz is equal to the
630
- # identifier of this Timezone.
1110
+ # @param tz [Object] an `Object` to compare this {Timezone} with.
1111
+ # @return [Boolean] `true` if `tz` is an instance of {Timezone} and has the
1112
+ # same {identifier} as `self`, otherwise `false`.
631
1113
  def eql?(tz)
632
1114
  self == tz
633
1115
  end
634
-
635
- # Returns a hash of this Timezone.
1116
+
1117
+ # @return [Integer] a hash based on the {identifier}.
636
1118
  def hash
637
1119
  identifier.hash
638
1120
  end
639
-
640
- # Dumps this Timezone for marshalling.
1121
+
1122
+ # Returns a serialized representation of this {Timezone}. This method is
1123
+ # called when using `Marshal.dump` with an instance of {Timezone}.
1124
+ #
1125
+ # @param limit [Integer] the maximum depth to dump - ignored.
1126
+ # @return [String] a serialized representation of this {Timezone}.
641
1127
  def _dump(limit)
642
1128
  identifier
643
1129
  end
644
-
645
- # Loads a marshalled Timezone.
1130
+
1131
+ # Loads a {Timezone} from the serialized representation returned by {_dump}.
1132
+ # This is method is called when using `Marshal.load` or `Marshal.restore`
1133
+ # to restore a serialized {Timezone}.
1134
+ #
1135
+ # @param data [String] a serialized representation of a {Timezone}.
1136
+ # @return [Timezone] the result of converting `data` back into a {Timezone}.
646
1137
  def self._load(data)
647
1138
  Timezone.get(data)
648
1139
  end
649
-
1140
+
650
1141
  private
651
- # Initializes @@loaded_zones.
652
- def self.init_loaded_zones
653
- @@loaded_zones = ThreadSafe::Cache.new
654
- end
655
- init_loaded_zones
656
-
657
- # Returns an array of proxies corresponding to the given array of
658
- # identifiers.
659
- def self.get_proxies(identifiers)
660
- identifiers.collect {|identifier| get_proxy(identifier)}
661
- end
662
-
663
- # Returns the current DataSource.
664
- def self.data_source
665
- DataSource.get
666
- end
667
1142
 
668
- # Raises an UnknownTimezone exception.
669
- def raise_unknown_timezone
670
- raise UnknownTimezone, 'TZInfo::Timezone constructed directly'
671
- end
672
- end
1143
+ # Raises an {UnknownTimezone} exception.
1144
+ #
1145
+ # @raise [UnknownTimezone] always.
1146
+ def raise_unknown_timezone
1147
+ raise UnknownTimezone, 'TZInfo::Timezone should not be constructed directly (use TZInfo::Timezone.get instead)'
1148
+ end
1149
+ end
673
1150
  end