tzinfo 1.2.6 → 2.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (145) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +1 -1
  4. data/.yardopts +3 -0
  5. data/CHANGES.md +494 -380
  6. data/LICENSE +13 -13
  7. data/README.md +368 -114
  8. data/lib/tzinfo.rb +59 -29
  9. data/lib/tzinfo/country.rb +141 -129
  10. data/lib/tzinfo/country_timezone.rb +70 -112
  11. data/lib/tzinfo/data_source.rb +389 -144
  12. data/lib/tzinfo/data_sources.rb +8 -0
  13. data/lib/tzinfo/data_sources/constant_offset_data_timezone_info.rb +56 -0
  14. data/lib/tzinfo/data_sources/country_info.rb +42 -0
  15. data/lib/tzinfo/data_sources/data_timezone_info.rb +91 -0
  16. data/lib/tzinfo/data_sources/linked_timezone_info.rb +33 -0
  17. data/lib/tzinfo/data_sources/ruby_data_source.rb +145 -0
  18. data/lib/tzinfo/data_sources/timezone_info.rb +47 -0
  19. data/lib/tzinfo/data_sources/transitions_data_timezone_info.rb +214 -0
  20. data/lib/tzinfo/data_sources/zoneinfo_data_source.rb +577 -0
  21. data/lib/tzinfo/data_sources/zoneinfo_reader.rb +288 -0
  22. data/lib/tzinfo/data_timezone.rb +33 -47
  23. data/lib/tzinfo/datetime_with_offset.rb +153 -0
  24. data/lib/tzinfo/format1.rb +10 -0
  25. data/lib/tzinfo/format1/country_definer.rb +17 -0
  26. data/lib/tzinfo/format1/country_index_definition.rb +64 -0
  27. data/lib/tzinfo/format1/timezone_definer.rb +64 -0
  28. data/lib/tzinfo/format1/timezone_definition.rb +39 -0
  29. data/lib/tzinfo/format1/timezone_index_definition.rb +77 -0
  30. data/lib/tzinfo/format2.rb +10 -0
  31. data/lib/tzinfo/format2/country_definer.rb +68 -0
  32. data/lib/tzinfo/format2/country_index_definer.rb +68 -0
  33. data/lib/tzinfo/format2/country_index_definition.rb +46 -0
  34. data/lib/tzinfo/format2/timezone_definer.rb +94 -0
  35. data/lib/tzinfo/format2/timezone_definition.rb +73 -0
  36. data/lib/tzinfo/format2/timezone_index_definer.rb +45 -0
  37. data/lib/tzinfo/format2/timezone_index_definition.rb +55 -0
  38. data/lib/tzinfo/info_timezone.rb +26 -21
  39. data/lib/tzinfo/linked_timezone.rb +33 -52
  40. data/lib/tzinfo/offset_timezone_period.rb +42 -0
  41. data/lib/tzinfo/string_deduper.rb +118 -0
  42. data/lib/tzinfo/time_with_offset.rb +128 -0
  43. data/lib/tzinfo/timestamp.rb +548 -0
  44. data/lib/tzinfo/timestamp_with_offset.rb +85 -0
  45. data/lib/tzinfo/timezone.rb +989 -502
  46. data/lib/tzinfo/timezone_offset.rb +84 -74
  47. data/lib/tzinfo/timezone_period.rb +151 -217
  48. data/lib/tzinfo/timezone_proxy.rb +70 -79
  49. data/lib/tzinfo/timezone_transition.rb +77 -109
  50. data/lib/tzinfo/transitions_timezone_period.rb +63 -0
  51. data/lib/tzinfo/untaint_ext.rb +18 -0
  52. data/lib/tzinfo/version.rb +7 -0
  53. data/lib/tzinfo/with_offset.rb +61 -0
  54. metadata +44 -100
  55. metadata.gz.sig +0 -0
  56. data/Rakefile +0 -107
  57. data/lib/tzinfo/country_index_definition.rb +0 -31
  58. data/lib/tzinfo/country_info.rb +0 -42
  59. data/lib/tzinfo/data_timezone_info.rb +0 -55
  60. data/lib/tzinfo/linked_timezone_info.rb +0 -26
  61. data/lib/tzinfo/offset_rationals.rb +0 -77
  62. data/lib/tzinfo/ruby_core_support.rb +0 -176
  63. data/lib/tzinfo/ruby_country_info.rb +0 -74
  64. data/lib/tzinfo/ruby_data_source.rb +0 -138
  65. data/lib/tzinfo/time_or_datetime.rb +0 -340
  66. data/lib/tzinfo/timezone_definition.rb +0 -36
  67. data/lib/tzinfo/timezone_index_definition.rb +0 -54
  68. data/lib/tzinfo/timezone_info.rb +0 -30
  69. data/lib/tzinfo/timezone_transition_definition.rb +0 -104
  70. data/lib/tzinfo/transition_data_timezone_info.rb +0 -274
  71. data/lib/tzinfo/zoneinfo_country_info.rb +0 -37
  72. data/lib/tzinfo/zoneinfo_data_source.rb +0 -496
  73. data/lib/tzinfo/zoneinfo_timezone_info.rb +0 -298
  74. data/test/tc_country.rb +0 -236
  75. data/test/tc_country_index_definition.rb +0 -69
  76. data/test/tc_country_info.rb +0 -16
  77. data/test/tc_country_timezone.rb +0 -173
  78. data/test/tc_data_source.rb +0 -218
  79. data/test/tc_data_timezone.rb +0 -99
  80. data/test/tc_data_timezone_info.rb +0 -18
  81. data/test/tc_info_timezone.rb +0 -34
  82. data/test/tc_linked_timezone.rb +0 -155
  83. data/test/tc_linked_timezone_info.rb +0 -23
  84. data/test/tc_offset_rationals.rb +0 -23
  85. data/test/tc_ruby_core_support.rb +0 -168
  86. data/test/tc_ruby_country_info.rb +0 -110
  87. data/test/tc_ruby_data_source.rb +0 -165
  88. data/test/tc_time_or_datetime.rb +0 -660
  89. data/test/tc_timezone.rb +0 -1359
  90. data/test/tc_timezone_definition.rb +0 -113
  91. data/test/tc_timezone_index_definition.rb +0 -73
  92. data/test/tc_timezone_info.rb +0 -11
  93. data/test/tc_timezone_london.rb +0 -143
  94. data/test/tc_timezone_melbourne.rb +0 -142
  95. data/test/tc_timezone_new_york.rb +0 -142
  96. data/test/tc_timezone_offset.rb +0 -126
  97. data/test/tc_timezone_period.rb +0 -555
  98. data/test/tc_timezone_proxy.rb +0 -136
  99. data/test/tc_timezone_transition.rb +0 -366
  100. data/test/tc_timezone_transition_definition.rb +0 -295
  101. data/test/tc_timezone_utc.rb +0 -27
  102. data/test/tc_transition_data_timezone_info.rb +0 -433
  103. data/test/tc_zoneinfo_country_info.rb +0 -78
  104. data/test/tc_zoneinfo_data_source.rb +0 -1204
  105. data/test/tc_zoneinfo_timezone_info.rb +0 -1234
  106. data/test/test_utils.rb +0 -188
  107. data/test/ts_all.rb +0 -7
  108. data/test/ts_all_ruby.rb +0 -5
  109. data/test/ts_all_zoneinfo.rb +0 -9
  110. data/test/tzinfo-data/tzinfo/data.rb +0 -8
  111. data/test/tzinfo-data/tzinfo/data/definitions/America/Argentina/Buenos_Aires.rb +0 -89
  112. data/test/tzinfo-data/tzinfo/data/definitions/America/New_York.rb +0 -315
  113. data/test/tzinfo-data/tzinfo/data/definitions/Australia/Melbourne.rb +0 -218
  114. data/test/tzinfo-data/tzinfo/data/definitions/EST.rb +0 -19
  115. data/test/tzinfo-data/tzinfo/data/definitions/Etc/GMT__m__1.rb +0 -21
  116. data/test/tzinfo-data/tzinfo/data/definitions/Etc/GMT__p__1.rb +0 -21
  117. data/test/tzinfo-data/tzinfo/data/definitions/Etc/UTC.rb +0 -21
  118. data/test/tzinfo-data/tzinfo/data/definitions/Europe/Amsterdam.rb +0 -261
  119. data/test/tzinfo-data/tzinfo/data/definitions/Europe/Andorra.rb +0 -186
  120. data/test/tzinfo-data/tzinfo/data/definitions/Europe/London.rb +0 -321
  121. data/test/tzinfo-data/tzinfo/data/definitions/Europe/Paris.rb +0 -265
  122. data/test/tzinfo-data/tzinfo/data/definitions/Europe/Prague.rb +0 -220
  123. data/test/tzinfo-data/tzinfo/data/definitions/UTC.rb +0 -16
  124. data/test/tzinfo-data/tzinfo/data/indexes/countries.rb +0 -927
  125. data/test/tzinfo-data/tzinfo/data/indexes/timezones.rb +0 -596
  126. data/test/tzinfo-data/tzinfo/data/version.rb +0 -14
  127. data/test/zoneinfo/America/Argentina/Buenos_Aires +0 -0
  128. data/test/zoneinfo/America/New_York +0 -0
  129. data/test/zoneinfo/Australia/Melbourne +0 -0
  130. data/test/zoneinfo/EST +0 -0
  131. data/test/zoneinfo/Etc/UTC +0 -0
  132. data/test/zoneinfo/Europe/Amsterdam +0 -0
  133. data/test/zoneinfo/Europe/Andorra +0 -0
  134. data/test/zoneinfo/Europe/London +0 -0
  135. data/test/zoneinfo/Europe/Paris +0 -0
  136. data/test/zoneinfo/Europe/Prague +0 -0
  137. data/test/zoneinfo/Factory +0 -0
  138. data/test/zoneinfo/iso3166.tab +0 -275
  139. data/test/zoneinfo/leapseconds +0 -61
  140. data/test/zoneinfo/posix/Europe/London +0 -0
  141. data/test/zoneinfo/posixrules +0 -0
  142. data/test/zoneinfo/right/Europe/London +0 -0
  143. data/test/zoneinfo/zone.tab +0 -439
  144. data/test/zoneinfo/zone1970.tab +0 -369
  145. data/tzinfo.gemspec +0 -21
@@ -0,0 +1,85 @@
1
+ # encoding: UTF-8
2
+ # frozen_string_literal: true
3
+
4
+ module TZInfo
5
+ # A subclass of {Timestamp} used to represent local times.
6
+ # {TimestampWithOffset} holds a reference to the related {TimezoneOffset} and
7
+ # overrides various methods to return results appropriate for the
8
+ # {TimezoneOffset}. Certain operations will clear the associated
9
+ # {TimezoneOffset} (if the {TimezoneOffset} would not necessarily be valid for
10
+ # the result). Once the {TimezoneOffset} has been cleared,
11
+ # {TimestampWithOffset} behaves identically to {Timestamp}.
12
+ class TimestampWithOffset < Timestamp
13
+ include WithOffset
14
+
15
+ # @return [TimezoneOffset] the {TimezoneOffset} associated with this
16
+ # instance.
17
+ attr_reader :timezone_offset
18
+
19
+ # Creates a new {TimestampWithOffset} from a given {Timestamp} and
20
+ # {TimezoneOffset}.
21
+ #
22
+ # @param timestamp [Timestamp] a {Timestamp}.
23
+ # @param timezone_offset [TimezoneOffset] a {TimezoneOffset} valid at the
24
+ # time of `timestamp`.
25
+ # @return [TimestampWithOffset] a {TimestampWithOffset} that has the same
26
+ # {value value} and {sub_second sub_second} as the `timestamp` parameter,
27
+ # a {utc_offset utc_offset} equal to the
28
+ # {TimezoneOffset#observed_utc_offset observed_utc_offset} of the
29
+ # `timezone_offset` parameter and {timezone_offset timezone_offset} set to
30
+ # the `timezone_offset` parameter.
31
+ # @raise [ArgumentError] if `timestamp` or `timezone_offset` is `nil`.
32
+ def self.set_timezone_offset(timestamp, timezone_offset)
33
+ raise ArgumentError, 'timestamp must be specified' unless timestamp
34
+ raise ArgumentError, 'timezone_offset must be specified' unless timezone_offset
35
+ new!(timestamp.value, timestamp.sub_second, timezone_offset.observed_utc_offset).set_timezone_offset(timezone_offset)
36
+ end
37
+
38
+ # Sets the associated {TimezoneOffset} of this {TimestampWithOffset}.
39
+ #
40
+ # @param timezone_offset [TimezoneOffset] a {TimezoneOffset} valid at the time
41
+ # and for the offset of this {TimestampWithOffset}.
42
+ # @return [TimestampWithOffset] `self`.
43
+ # @raise [ArgumentError] if `timezone_offset` is `nil`.
44
+ # @raise [ArgumentError] if {utc? self.utc?} is `true`.
45
+ # @raise [ArgumentError] if `timezone_offset.observed_utc_offset` does not equal
46
+ # `self.utc_offset`.
47
+ def set_timezone_offset(timezone_offset)
48
+ raise ArgumentError, 'timezone_offset must be specified' unless timezone_offset
49
+ raise ArgumentError, 'timezone_offset.observed_utc_offset does not match self.utc_offset' if utc? || utc_offset != timezone_offset.observed_utc_offset
50
+ @timezone_offset = timezone_offset
51
+ self
52
+ end
53
+
54
+ # An overridden version of {Timestamp#to_time} that, if there is an
55
+ # associated {TimezoneOffset}, returns a {TimeWithOffset} with that offset.
56
+ #
57
+ # @return [Time] if there is an associated {TimezoneOffset}, a
58
+ # {TimeWithOffset} representation of this {TimestampWithOffset}, otherwise
59
+ # a `Time` representation.
60
+ def to_time
61
+ to = timezone_offset
62
+ if to
63
+ new_time(TimeWithOffset).set_timezone_offset(to)
64
+ else
65
+ super
66
+ end
67
+ end
68
+
69
+ # An overridden version of {Timestamp#to_datetime}, if there is an
70
+ # associated {TimezoneOffset}, returns a {DateTimeWithOffset} with that
71
+ # offset.
72
+ #
73
+ # @return [DateTime] if there is an associated {TimezoneOffset}, a
74
+ # {DateTimeWithOffset} representation of this {TimestampWithOffset},
75
+ # otherwise a `DateTime` representation.
76
+ def to_datetime
77
+ to = timezone_offset
78
+ if to
79
+ new_datetime(DateTimeWithOffset).set_timezone_offset(to)
80
+ else
81
+ super
82
+ end
83
+ end
84
+ end
85
+ end
@@ -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,856 @@ 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.
672
+ #
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.
676
+ #
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.
697
+ #
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.
485
764
  #
486
- # Offsets may be returned in any order.
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.
487
873
  #
488
- # utc_to and utc_from can be specified using either DateTime, Time or
489
- # integer timestamps (Time.to_i).
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.
490
928
  #
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
-
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.
529
982
  #
530
- # This is a shortcut for calling canonical_zone.identifier. Please refer
531
- # to the canonical_zone documentation for further information.
983
+ # This is a shortcut for calling `canonical_zone.identifier`. Please refer
984
+ # to the {canonical_zone} documentation for further information.
985
+ #
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 http://rubyinstaller.org/downloads/archives), this
608
- # 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]
1017
+ end
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:
1030
+ #
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
618
1083
  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.
1084
+
1085
+ # Returns the observed offset from UTC in seconds at the given time. This
1086
+ # includes adjustments made for daylight savings time.
622
1087
  #
623
- # Returns nil if tz is not comparable with Timezone instances.
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
+ # Matches `regexp` against the {identifier} of this {Timezone}.
1123
+ #
1124
+ # @param regexp [Regexp] a `Regexp` to match against the {identifier} of
1125
+ # this {Timezone}.
1126
+ # @return [Integer] the position the match starts, or `nil` if there is no
1127
+ # match.
1128
+ def =~(regexp)
1129
+ regexp =~ identifier
1130
+ end
1131
+
1132
+ # Returns a serialized representation of this {Timezone}. This method is
1133
+ # called when using `Marshal.dump` with an instance of {Timezone}.
1134
+ #
1135
+ # @param limit [Integer] the maximum depth to dump - ignored.
1136
+ # @return [String] a serialized representation of this {Timezone}.
641
1137
  def _dump(limit)
642
1138
  identifier
643
1139
  end
644
-
645
- # Loads a marshalled Timezone.
1140
+
1141
+ # Loads a {Timezone} from the serialized representation returned by {_dump}.
1142
+ # This is method is called when using `Marshal.load` or `Marshal.restore`
1143
+ # to restore a serialized {Timezone}.
1144
+ #
1145
+ # @param data [String] a serialized representation of a {Timezone}.
1146
+ # @return [Timezone] the result of converting `data` back into a {Timezone}.
646
1147
  def self._load(data)
647
1148
  Timezone.get(data)
648
1149
  end
649
-
1150
+
650
1151
  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
1152
 
668
- # Raises an UnknownTimezone exception.
669
- def raise_unknown_timezone
670
- raise UnknownTimezone, 'TZInfo::Timezone constructed directly'
671
- end
672
- end
1153
+ # Raises an {UnknownTimezone} exception.
1154
+ #
1155
+ # @raise [UnknownTimezone] always.
1156
+ def raise_unknown_timezone
1157
+ raise UnknownTimezone, 'TZInfo::Timezone should not be constructed directly (use TZInfo::Timezone.get instead)'
1158
+ end
1159
+ end
673
1160
  end