tzinfo 1.2.11 → 2.0.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (152) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/.yardopts +3 -0
  4. data/CHANGES.md +580 -394
  5. data/LICENSE +12 -12
  6. data/README.md +368 -114
  7. data/lib/tzinfo/annual_rules.rb +32 -12
  8. data/lib/tzinfo/country.rb +141 -129
  9. data/lib/tzinfo/country_timezone.rb +70 -112
  10. data/lib/tzinfo/data_source.rb +400 -144
  11. data/lib/tzinfo/data_sources/constant_offset_data_timezone_info.rb +56 -0
  12. data/lib/tzinfo/data_sources/country_info.rb +42 -0
  13. data/lib/tzinfo/data_sources/data_timezone_info.rb +91 -0
  14. data/lib/tzinfo/data_sources/linked_timezone_info.rb +33 -0
  15. data/lib/tzinfo/data_sources/posix_time_zone_parser.rb +177 -0
  16. data/lib/tzinfo/data_sources/ruby_data_source.rb +141 -0
  17. data/lib/tzinfo/data_sources/timezone_info.rb +47 -0
  18. data/lib/tzinfo/data_sources/transitions_data_timezone_info.rb +214 -0
  19. data/lib/tzinfo/data_sources/zoneinfo_data_source.rb +592 -0
  20. data/lib/tzinfo/data_sources/zoneinfo_reader.rb +482 -0
  21. data/lib/tzinfo/data_sources.rb +8 -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/country_definer.rb +17 -0
  25. data/lib/tzinfo/format1/country_index_definition.rb +64 -0
  26. data/lib/tzinfo/format1/timezone_definer.rb +64 -0
  27. data/lib/tzinfo/format1/timezone_definition.rb +39 -0
  28. data/lib/tzinfo/format1/timezone_index_definition.rb +77 -0
  29. data/lib/tzinfo/format1.rb +10 -0
  30. data/lib/tzinfo/format2/country_definer.rb +68 -0
  31. data/lib/tzinfo/format2/country_index_definer.rb +68 -0
  32. data/lib/tzinfo/format2/country_index_definition.rb +46 -0
  33. data/lib/tzinfo/format2/timezone_definer.rb +94 -0
  34. data/lib/tzinfo/format2/timezone_definition.rb +73 -0
  35. data/lib/tzinfo/format2/timezone_index_definer.rb +45 -0
  36. data/lib/tzinfo/format2/timezone_index_definition.rb +55 -0
  37. data/lib/tzinfo/format2.rb +10 -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/ruby_core_support.rb +25 -163
  42. data/lib/tzinfo/string_deduper.rb +118 -0
  43. data/lib/tzinfo/time_with_offset.rb +154 -0
  44. data/lib/tzinfo/timestamp.rb +552 -0
  45. data/lib/tzinfo/timestamp_with_offset.rb +85 -0
  46. data/lib/tzinfo/timezone.rb +989 -502
  47. data/lib/tzinfo/timezone_offset.rb +84 -74
  48. data/lib/tzinfo/timezone_period.rb +151 -217
  49. data/lib/tzinfo/timezone_proxy.rb +70 -79
  50. data/lib/tzinfo/timezone_transition.rb +77 -109
  51. data/lib/tzinfo/transition_rule.rb +207 -77
  52. data/lib/tzinfo/transitions_timezone_period.rb +63 -0
  53. data/lib/tzinfo/version.rb +7 -0
  54. data/lib/tzinfo/with_offset.rb +61 -0
  55. data/lib/tzinfo.rb +78 -40
  56. data.tar.gz.sig +0 -0
  57. metadata +48 -103
  58. metadata.gz.sig +1 -3
  59. data/Rakefile +0 -107
  60. data/lib/tzinfo/country_index_definition.rb +0 -31
  61. data/lib/tzinfo/country_info.rb +0 -42
  62. data/lib/tzinfo/data_timezone_info.rb +0 -55
  63. data/lib/tzinfo/linked_timezone_info.rb +0 -26
  64. data/lib/tzinfo/offset_rationals.rb +0 -77
  65. data/lib/tzinfo/posix_time_zone_parser.rb +0 -136
  66. data/lib/tzinfo/ruby_country_info.rb +0 -74
  67. data/lib/tzinfo/ruby_data_source.rb +0 -136
  68. data/lib/tzinfo/time_or_datetime.rb +0 -351
  69. data/lib/tzinfo/timezone_definition.rb +0 -36
  70. data/lib/tzinfo/timezone_index_definition.rb +0 -54
  71. data/lib/tzinfo/timezone_info.rb +0 -30
  72. data/lib/tzinfo/timezone_transition_definition.rb +0 -104
  73. data/lib/tzinfo/transition_data_timezone_info.rb +0 -274
  74. data/lib/tzinfo/zoneinfo_country_info.rb +0 -37
  75. data/lib/tzinfo/zoneinfo_data_source.rb +0 -504
  76. data/lib/tzinfo/zoneinfo_timezone_info.rb +0 -516
  77. data/test/assets/payload.rb +0 -1
  78. data/test/tc_annual_rules.rb +0 -95
  79. data/test/tc_country.rb +0 -240
  80. data/test/tc_country_index_definition.rb +0 -69
  81. data/test/tc_country_info.rb +0 -16
  82. data/test/tc_country_timezone.rb +0 -173
  83. data/test/tc_data_source.rb +0 -218
  84. data/test/tc_data_timezone.rb +0 -99
  85. data/test/tc_data_timezone_info.rb +0 -18
  86. data/test/tc_info_timezone.rb +0 -34
  87. data/test/tc_linked_timezone.rb +0 -155
  88. data/test/tc_linked_timezone_info.rb +0 -23
  89. data/test/tc_offset_rationals.rb +0 -23
  90. data/test/tc_posix_time_zone_parser.rb +0 -261
  91. data/test/tc_ruby_core_support.rb +0 -168
  92. data/test/tc_ruby_country_info.rb +0 -110
  93. data/test/tc_ruby_data_source.rb +0 -175
  94. data/test/tc_time_or_datetime.rb +0 -674
  95. data/test/tc_timezone.rb +0 -1361
  96. data/test/tc_timezone_definition.rb +0 -113
  97. data/test/tc_timezone_index_definition.rb +0 -73
  98. data/test/tc_timezone_info.rb +0 -11
  99. data/test/tc_timezone_london.rb +0 -143
  100. data/test/tc_timezone_melbourne.rb +0 -142
  101. data/test/tc_timezone_new_york.rb +0 -142
  102. data/test/tc_timezone_offset.rb +0 -126
  103. data/test/tc_timezone_period.rb +0 -555
  104. data/test/tc_timezone_proxy.rb +0 -136
  105. data/test/tc_timezone_transition.rb +0 -366
  106. data/test/tc_timezone_transition_definition.rb +0 -295
  107. data/test/tc_timezone_utc.rb +0 -27
  108. data/test/tc_transition_data_timezone_info.rb +0 -433
  109. data/test/tc_transition_rule.rb +0 -663
  110. data/test/tc_zoneinfo_country_info.rb +0 -78
  111. data/test/tc_zoneinfo_data_source.rb +0 -1226
  112. data/test/tc_zoneinfo_timezone_info.rb +0 -2149
  113. data/test/test_utils.rb +0 -214
  114. data/test/ts_all.rb +0 -7
  115. data/test/ts_all_ruby.rb +0 -5
  116. data/test/ts_all_zoneinfo.rb +0 -9
  117. data/test/tzinfo-data/tzinfo/data/definitions/America/Argentina/Buenos_Aires.rb +0 -89
  118. data/test/tzinfo-data/tzinfo/data/definitions/America/New_York.rb +0 -327
  119. data/test/tzinfo-data/tzinfo/data/definitions/Australia/Melbourne.rb +0 -230
  120. data/test/tzinfo-data/tzinfo/data/definitions/EST.rb +0 -19
  121. data/test/tzinfo-data/tzinfo/data/definitions/Etc/GMT__m__1.rb +0 -21
  122. data/test/tzinfo-data/tzinfo/data/definitions/Etc/GMT__p__1.rb +0 -21
  123. data/test/tzinfo-data/tzinfo/data/definitions/Etc/UTC.rb +0 -21
  124. data/test/tzinfo-data/tzinfo/data/definitions/Europe/Amsterdam.rb +0 -273
  125. data/test/tzinfo-data/tzinfo/data/definitions/Europe/Andorra.rb +0 -198
  126. data/test/tzinfo-data/tzinfo/data/definitions/Europe/London.rb +0 -333
  127. data/test/tzinfo-data/tzinfo/data/definitions/Europe/Paris.rb +0 -277
  128. data/test/tzinfo-data/tzinfo/data/definitions/Europe/Prague.rb +0 -235
  129. data/test/tzinfo-data/tzinfo/data/definitions/UTC.rb +0 -16
  130. data/test/tzinfo-data/tzinfo/data/indexes/countries.rb +0 -940
  131. data/test/tzinfo-data/tzinfo/data/indexes/timezones.rb +0 -609
  132. data/test/tzinfo-data/tzinfo/data/version.rb +0 -20
  133. data/test/tzinfo-data/tzinfo/data.rb +0 -8
  134. data/test/zoneinfo/America/Argentina/Buenos_Aires +0 -0
  135. data/test/zoneinfo/America/New_York +0 -0
  136. data/test/zoneinfo/Australia/Melbourne +0 -0
  137. data/test/zoneinfo/EST +0 -0
  138. data/test/zoneinfo/Etc/UTC +0 -0
  139. data/test/zoneinfo/Europe/Amsterdam +0 -0
  140. data/test/zoneinfo/Europe/Andorra +0 -0
  141. data/test/zoneinfo/Europe/London +0 -0
  142. data/test/zoneinfo/Europe/Paris +0 -0
  143. data/test/zoneinfo/Europe/Prague +0 -0
  144. data/test/zoneinfo/Factory +0 -0
  145. data/test/zoneinfo/iso3166.tab +0 -274
  146. data/test/zoneinfo/leapseconds +0 -78
  147. data/test/zoneinfo/posix/Europe/London +0 -0
  148. data/test/zoneinfo/posixrules +0 -0
  149. data/test/zoneinfo/right/Europe/London +0 -0
  150. data/test/zoneinfo/zone.tab +0 -452
  151. data/test/zoneinfo/zone1970.tab +0 -384
  152. data/tzinfo.gemspec +0 -21
@@ -1,79 +1,96 @@
1
- require 'date'
1
+ # encoding: UTF-8
2
+ # frozen_string_literal: true
2
3
 
3
4
  module TZInfo
4
5
  # Base class for rules definining the transition between standard and daylight
5
6
  # savings time.
7
+ #
8
+ # @abstract
9
+ # @private
6
10
  class TransitionRule #:nodoc:
7
11
  # Returns the number of seconds after midnight local time on the day
8
12
  # identified by the rule at which the transition occurs. Can be negative to
9
13
  # denote a time on the prior day. Can be greater than or equal to 86,400 to
10
14
  # denote a time of the following day.
15
+ #
16
+ # @return [Integer] the time in seconds after midnight local time at which
17
+ # the transition occurs.
11
18
  attr_reader :transition_at
12
19
 
13
- # Initializes a new TransitionRule.
20
+ # Initializes a new {TransitionRule}.
21
+ #
22
+ # @param transition_at [Integer] the time in seconds after midnight local
23
+ # time at which the transition occurs.
24
+ # @raise [ArgumentError] if `transition_at` is not an `Integer`.
14
25
  def initialize(transition_at)
15
26
  raise ArgumentError, 'Invalid transition_at' unless transition_at.kind_of?(Integer)
16
27
  @transition_at = transition_at
17
28
  end
18
29
 
19
- # Calculates the UTC time of the transition from a given offset on a given
20
- # year.
30
+ # Calculates the time of the transition from a given offset on a given year.
31
+ #
32
+ # @param offset [TimezoneOffset] the current offset at the time the rule
33
+ # will transition.
34
+ # @param year [Integer] the year in which the transition occurs (local
35
+ # time).
36
+ # @return [TimestampWithOffset] the time at which the transition occurs.
21
37
  def at(offset, year)
22
- day = get_day(year)
23
- day.add_with_convert(@transition_at - offset.utc_total_offset)
38
+ day = get_day(offset, year)
39
+ TimestampWithOffset.set_timezone_offset(Timestamp.for(day + @transition_at), offset)
24
40
  end
25
41
 
26
- # Determines if this TransitionRule is equal to another instance.
42
+ # Determines if this {TransitionRule} is equal to another instance.
43
+ #
44
+ # @param r [Object] the instance to test for equality.
45
+ # @return [Boolean] `true` if `r` is a {TransitionRule} with the same
46
+ # {transition_at} as this {TransitionRule}, otherwise `false`.
27
47
  def ==(r)
28
48
  r.kind_of?(TransitionRule) && @transition_at == r.transition_at
29
49
  end
30
50
  alias eql? ==
31
51
 
32
- # Returns a hash based on hash_args (defaulting to transition_at).
52
+ # @return [Integer] a hash based on {hash_args} (defaulting to
53
+ # {transition_at}).
33
54
  def hash
34
55
  hash_args.hash
35
56
  end
36
57
 
37
58
  protected
38
59
 
39
- # Returns an Array of parameters that will influence the output of hash.
60
+ # @return [Array] an `Array` of parameters that will influence the output of
61
+ # {hash}.
40
62
  def hash_args
41
63
  [@transition_at]
42
64
  end
43
-
44
- def new_time_or_datetime(year, month = 1, day = 1)
45
- result = if ((year >= 2039 || (year == 2038 && (month >= 2 || (month == 1 && day >= 20)))) && !RubyCoreSupport.time_supports_64bit) ||
46
- (year < 1970 && !RubyCoreSupport.time_supports_negative)
47
-
48
- # Time handles 29 February on a non-leap year as 1 March.
49
- # DateTime rejects. Advance manually.
50
- if month == 2 && day == 29 && !Date.gregorian_leap?(year)
51
- month = 3
52
- day = 1
53
- end
54
-
55
- RubyCoreSupport.datetime_new(year, month, day)
56
- else
57
- Time.utc(year, month, day)
58
- end
59
-
60
- TimeOrDateTime.wrap(result)
61
- end
62
65
  end
66
+ private_constant :TransitionRule
63
67
 
64
68
  # A base class for transition rules that activate based on an integer day of
65
69
  # the year.
66
70
  #
71
+ # @abstract
67
72
  # @private
68
73
  class DayOfYearTransitionRule < TransitionRule #:nodoc:
69
- # Initializes a new DayOfYearTransitionRule.
74
+ # Initializes a new {DayOfYearTransitionRule}.
75
+ #
76
+ # @param day [Integer] the day of the year on which the transition occurs.
77
+ # The precise meaning is defined by subclasses.
78
+ # @param transition_at [Integer] the time in seconds after midnight local
79
+ # time at which the transition occurs.
80
+ # @raise [ArgumentError] if `transition_at` is not an `Integer`.
81
+ # @raise [ArgumentError] if `day` is not an `Integer`.
70
82
  def initialize(day, transition_at)
71
83
  super(transition_at)
72
84
  raise ArgumentError, 'Invalid day' unless day.kind_of?(Integer)
73
85
  @seconds = day * 86400
74
86
  end
75
87
 
76
- # Determines if this DayOfYearTransitionRule is equal to another instance.
88
+ # Determines if this {DayOfYearTransitionRule} is equal to another instance.
89
+ #
90
+ # @param r [Object] the instance to test for equality.
91
+ # @return [Boolean] `true` if `r` is a {DayOfYearTransitionRule} with the
92
+ # same {transition_at} and day as this {DayOfYearTransitionRule},
93
+ # otherwise `false`.
77
94
  def ==(r)
78
95
  super(r) && r.kind_of?(DayOfYearTransitionRule) && @seconds == r.seconds
79
96
  end
@@ -84,11 +101,12 @@ module TZInfo
84
101
  # @return [Integer] the day multipled by the number of seconds in a day.
85
102
  attr_reader :seconds
86
103
 
87
- # Returns an Array of parameters that will influence the output of hash.
104
+ # (see TransitionRule#hash_args)
88
105
  def hash_args
89
106
  [@seconds] + super
90
107
  end
91
108
  end
109
+ private_constant :DayOfYearTransitionRule
92
110
 
93
111
  # Defines transitions that occur on the zero-based nth day of the year.
94
112
  #
@@ -100,25 +118,38 @@ module TZInfo
100
118
  #
101
119
  # @private
102
120
  class AbsoluteDayOfYearTransitionRule < DayOfYearTransitionRule #:nodoc:
103
- # Initializes a new AbsoluteDayOfYearTransitionRule.
121
+ # Initializes a new {AbsoluteDayOfYearTransitionRule}.
122
+ #
123
+ # @param day [Integer] the zero-based day of the year on which the
124
+ # transition occurs (0 to 365 inclusive).
125
+ # @param transition_at [Integer] the time in seconds after midnight local
126
+ # time at which the transition occurs.
127
+ # @raise [ArgumentError] if `transition_at` is not an `Integer`.
128
+ # @raise [ArgumentError] if `day` is not an `Integer`.
129
+ # @raise [ArgumentError] if `day` is less than 0 or greater than 365.
104
130
  def initialize(day, transition_at = 0)
105
131
  super(day, transition_at)
106
132
  raise ArgumentError, 'Invalid day' unless day >= 0 && day <= 365
107
133
  end
108
134
 
109
- # Returns true if the day specified by this transition is the first in the
110
- # year (a day number of 0), otherwise false.
135
+ # @return [Boolean] `true` if the day specified by this transition is the
136
+ # first in the year (a day number of 0), otherwise `false`.
111
137
  def is_always_first_day_of_year?
112
138
  seconds == 0
113
139
  end
114
140
 
115
- # @returns false.
141
+ # @return [Boolean] `false`.
116
142
  def is_always_last_day_of_year?
117
143
  false
118
144
  end
119
145
 
120
- # Determines if this AbsoluteDayOfYearTransitionRule is equal to another
146
+ # Determines if this {AbsoluteDayOfYearTransitionRule} is equal to another
121
147
  # instance.
148
+ #
149
+ # @param r [Object] the instance to test for equality.
150
+ # @return [Boolean] `true` if `r` is a {AbsoluteDayOfYearTransitionRule}
151
+ # with the same {transition_at} and day as this
152
+ # {AbsoluteDayOfYearTransitionRule}, otherwise `false`.
122
153
  def ==(r)
123
154
  super(r) && r.kind_of?(AbsoluteDayOfYearTransitionRule)
124
155
  end
@@ -126,13 +157,19 @@ module TZInfo
126
157
 
127
158
  protected
128
159
 
129
- # Returns a TimeOrDateTime representing midnight local time on the day
130
- # specified by the rule for the given offset and year.
131
- def get_day(year)
132
- new_time_or_datetime(year).add_with_convert(seconds)
160
+ # Returns a `Time` representing midnight local time on the day specified by
161
+ # the rule for the given offset and year.
162
+ #
163
+ # @param offset [TimezoneOffset] the current offset at the time of the
164
+ # transition.
165
+ # @param year [Integer] the year in which the transition occurs.
166
+ # @return [Time] midnight local time on the day specified by the rule for
167
+ # the given offset and year.
168
+ def get_day(offset, year)
169
+ Time.new(year, 1, 1, 0, 0, 0, offset.observed_utc_offset) + seconds
133
170
  end
134
171
 
135
- # Returns an Array of parameters that will influence the output of hash.
172
+ # (see TransitionRule#hash_args)
136
173
  def hash_args
137
174
  [AbsoluteDayOfYearTransitionRule] + super
138
175
  end
@@ -147,30 +184,45 @@ module TZInfo
147
184
  class JulianDayOfYearTransitionRule < DayOfYearTransitionRule #:nodoc:
148
185
  # The 60 days in seconds.
149
186
  LEAP = 60 * 86400
187
+ private_constant :LEAP
150
188
 
151
189
  # The length of a non-leap year in seconds.
152
190
  YEAR = 365 * 86400
153
-
154
- # Initializes a new JulianDayOfYearTransitionRule.
191
+ private_constant :YEAR
192
+
193
+ # Initializes a new {JulianDayOfYearTransitionRule}.
194
+ #
195
+ # @param day [Integer] the one-based Julian day of the year on which the
196
+ # transition occurs (1 to 365 inclusive).
197
+ # @param transition_at [Integer] the time in seconds after midnight local
198
+ # time at which the transition occurs.
199
+ # @raise [ArgumentError] if `transition_at` is not an `Integer`.
200
+ # @raise [ArgumentError] if `day` is not an `Integer`.
201
+ # @raise [ArgumentError] if `day` is less than 1 or greater than 365.
155
202
  def initialize(day, transition_at = 0)
156
203
  super(day, transition_at)
157
204
  raise ArgumentError, 'Invalid day' unless day >= 1 && day <= 365
158
205
  end
159
206
 
160
- # Returns true if the day specified by this transition is the first in the
161
- # year (a day number of 1), otherwise false.
207
+ # @return [Boolean] `true` if the day specified by this transition is the
208
+ # first in the year (a day number of 1), otherwise `false`.
162
209
  def is_always_first_day_of_year?
163
210
  seconds == 86400
164
211
  end
165
212
 
166
- # Returns true if the day specified by this transition is the last in the
167
- # year (a day number of 365), otherwise false.
213
+ # @return [Boolean] `true` if the day specified by this transition is the
214
+ # last in the year (a day number of 365), otherwise `false`.
168
215
  def is_always_last_day_of_year?
169
216
  seconds == YEAR
170
217
  end
171
218
 
172
- # Determines if this JulianDayOfYearTransitionRule is equal to another
219
+ # Determines if this {JulianDayOfYearTransitionRule} is equal to another
173
220
  # instance.
221
+ #
222
+ # @param r [Object] the instance to test for equality.
223
+ # @return [Boolean] `true` if `r` is a {JulianDayOfYearTransitionRule} with
224
+ # the same {transition_at} and day as this
225
+ # {JulianDayOfYearTransitionRule}, otherwise `false`.
174
226
  def ==(r)
175
227
  super(r) && r.kind_of?(JulianDayOfYearTransitionRule)
176
228
  end
@@ -178,28 +230,47 @@ module TZInfo
178
230
 
179
231
  protected
180
232
 
181
- # Returns a TimeOrDateTime representing midnight local time on the day
182
- # specified by the rule for the given offset and year.
183
- def get_day(year)
233
+ # Returns a `Time` representing midnight local time on the day specified by
234
+ # the rule for the given offset and year.
235
+ #
236
+ # @param offset [TimezoneOffset] the current offset at the time of the
237
+ # transition.
238
+ # @param year [Integer] the year in which the transition occurs.
239
+ # @return [Time] midnight local time on the day specified by the rule for
240
+ # the given offset and year.
241
+ def get_day(offset, year)
184
242
  # Returns 1 March on non-leap years.
185
- leap = new_time_or_datetime(year, 2, 29)
243
+ leap = Time.new(year, 2, 29, 0, 0, 0, offset.observed_utc_offset)
186
244
  diff = seconds - LEAP
187
245
  diff += 86400 if diff >= 0 && leap.mday == 29
188
- leap.add_with_convert(diff)
246
+ leap + diff
189
247
  end
190
248
 
191
- # Returns an Array of parameters that will influence the output of hash.
249
+ # (see TransitionRule#hash_args)
192
250
  def hash_args
193
251
  [JulianDayOfYearTransitionRule] + super
194
252
  end
195
253
  end
254
+ private_constant :JulianDayOfYearTransitionRule
196
255
 
197
256
  # A base class for rules that transition on a particular day of week of a
198
257
  # given week (subclasses specify which week of the month).
199
258
  #
259
+ # @abstract
200
260
  # @private
201
261
  class DayOfWeekTransitionRule < TransitionRule #:nodoc:
202
- # Initializes a new DayOfWeekTransitionRule.
262
+ # Initializes a new {DayOfWeekTransitionRule}.
263
+ #
264
+ # @param month [Integer] the month of the year when the transition occurs.
265
+ # @param day_of_week [Integer] the day of the week when the transition
266
+ # occurs. 0 is Sunday, 6 is Saturday.
267
+ # @param transition_at [Integer] the time in seconds after midnight local
268
+ # time at which the transition occurs.
269
+ # @raise [ArgumentError] if `transition_at` is not an `Integer`.
270
+ # @raise [ArgumentError] if `month` is not an `Integer`.
271
+ # @raise [ArgumentError] if `month` is less than 1 or greater than 12.
272
+ # @raise [ArgumentError] if `day_of_week` is not an `Integer`.
273
+ # @raise [ArgumentError] if `day_of_week` is less than 0 or greater than 6.
203
274
  def initialize(month, day_of_week, transition_at)
204
275
  super(transition_at)
205
276
  raise ArgumentError, 'Invalid month' unless month.kind_of?(Integer) && month >= 1 && month <= 12
@@ -208,17 +279,23 @@ module TZInfo
208
279
  @day_of_week = day_of_week
209
280
  end
210
281
 
211
- # Returns false.
282
+ # @return [Boolean] `false`.
212
283
  def is_always_first_day_of_year?
213
284
  false
214
285
  end
215
286
 
216
- # Returns false.
287
+ # @return [Boolean] `false`.
217
288
  def is_always_last_day_of_year?
218
289
  false
219
290
  end
220
291
 
221
- # Determines if this DayOfWeekTransitionRule is equal to another instance.
292
+ # Determines if this {DayOfWeekTransitionRule} is equal to another
293
+ # instance.
294
+ #
295
+ # @param r [Object] the instance to test for equality.
296
+ # @return [Boolean] `true` if `r` is a {DayOfWeekTransitionRule} with the
297
+ # same {transition_at}, month and day of week as this
298
+ # {DayOfWeekTransitionRule}, otherwise `false`.
222
299
  def ==(r)
223
300
  super(r) && r.kind_of?(DayOfWeekTransitionRule) && @month == r.month && @day_of_week == r.day_of_week
224
301
  end
@@ -226,31 +303,53 @@ module TZInfo
226
303
 
227
304
  protected
228
305
 
229
- # Returns the month of the year (1 to 12).
306
+ # @return [Integer] the month of the year (1 to 12).
230
307
  attr_reader :month
231
308
 
232
- # Returns the day of the week (0 to 6 for Sunday to Monday).
309
+ # @return [Integer] the day of the week (0 to 6 for Sunday to Monday).
233
310
  attr_reader :day_of_week
234
311
 
235
- # Returns an Array of parameters that will influence the output of hash.
312
+ # (see TransitionRule#hash_args)
236
313
  def hash_args
237
314
  [@month, @day_of_week] + super
238
315
  end
239
316
  end
317
+ private_constant :DayOfWeekTransitionRule
240
318
 
241
319
  # A rule that transitions on the nth occurrence of a particular day of week
242
320
  # of a calendar month.
243
321
  #
244
322
  # @private
245
323
  class DayOfMonthTransitionRule < DayOfWeekTransitionRule #:nodoc:
246
- # Initializes a new DayOfMonthTransitionRule.
324
+ # Initializes a new {DayOfMonthTransitionRule}.
325
+ #
326
+ # @param month [Integer] the month of the year when the transition occurs.
327
+ # @param week [Integer] the week of the month when the transition occurs (1
328
+ # to 4).
329
+ # @param day_of_week [Integer] the day of the week when the transition
330
+ # occurs. 0 is Sunday, 6 is Saturday.
331
+ # @param transition_at [Integer] the time in seconds after midnight local
332
+ # time at which the transition occurs.
333
+ # @raise [ArgumentError] if `transition_at` is not an `Integer`.
334
+ # @raise [ArgumentError] if `month` is not an `Integer`.
335
+ # @raise [ArgumentError] if `month` is less than 1 or greater than 12.
336
+ # @raise [ArgumentError] if `week` is not an `Integer`.
337
+ # @raise [ArgumentError] if `week` is less than 1 or greater than 4.
338
+ # @raise [ArgumentError] if `day_of_week` is not an `Integer`.
339
+ # @raise [ArgumentError] if `day_of_week` is less than 0 or greater than 6.
247
340
  def initialize(month, week, day_of_week, transition_at = 0)
248
341
  super(month, day_of_week, transition_at)
249
342
  raise ArgumentError, 'Invalid week' unless week.kind_of?(Integer) && week >= 1 && week <= 4
250
343
  @offset_start = (week - 1) * 7 + 1
251
344
  end
252
345
 
253
- # Determines if this DayOfMonthTransitionRule is equal to another instance.
346
+ # Determines if this {DayOfMonthTransitionRule} is equal to another
347
+ # instance.
348
+ #
349
+ # @param r [Object] the instance to test for equality.
350
+ # @return [Boolean] `true` if `r` is a {DayOfMonthTransitionRule} with the
351
+ # same {transition_at}, month, week and day of week as this
352
+ # {DayOfMonthTransitionRule}, otherwise `false`.
254
353
  def ==(r)
255
354
  super(r) && r.kind_of?(DayOfMonthTransitionRule) && @offset_start == r.offset_start
256
355
  end
@@ -258,42 +357,66 @@ module TZInfo
258
357
 
259
358
  protected
260
359
 
261
- # Returns the day the week starts on for a month starting on a Sunday.
360
+ # @return [Integer] the day the week starts on for a month starting on a
361
+ # Sunday.
262
362
  attr_reader :offset_start
263
363
 
264
- # Returns a TimeOrDateTime representing midnight local time on the day
265
- # specified by the rule for the given offset and year.
266
- def get_day(year)
267
- candidate = new_time_or_datetime(year, month, @offset_start)
364
+ # Returns a `Time` representing midnight local time on the day specified by
365
+ # the rule for the given offset and year.
366
+ #
367
+ # @param offset [TimezoneOffset] the current offset at the time of the
368
+ # transition.
369
+ # @param year [Integer] the year in which the transition occurs.
370
+ # @return [Time] midnight local time on the day specified by the rule for
371
+ # the given offset and year.
372
+ def get_day(offset, year)
373
+ candidate = Time.new(year, month, @offset_start, 0, 0, 0, offset.observed_utc_offset)
268
374
  diff = day_of_week - candidate.wday
269
375
 
270
376
  if diff < 0
271
- candidate.add_with_convert((7 + diff) * 86400)
377
+ candidate + (7 + diff) * 86400
272
378
  elsif diff > 0
273
- candidate.add_with_convert(diff * 86400)
379
+ candidate + diff * 86400
274
380
  else
275
381
  candidate
276
382
  end
277
383
  end
278
384
 
279
- # Returns an Array of parameters that will influence the output of hash.
385
+ # (see TransitionRule#hash_args)
280
386
  def hash_args
281
387
  [@offset_start] + super
282
388
  end
283
389
  end
390
+ private_constant :DayOfMonthTransitionRule
284
391
 
285
392
  # A rule that transitions on the last occurrence of a particular day of week
286
393
  # of a calendar month.
287
394
  #
288
395
  # @private
289
396
  class LastDayOfMonthTransitionRule < DayOfWeekTransitionRule #:nodoc:
290
- # Initializes a new LastDayOfMonthTransitionRule.
397
+ # Initializes a new {LastDayOfMonthTransitionRule}.
398
+ #
399
+ # @param month [Integer] the month of the year when the transition occurs.
400
+ # @param day_of_week [Integer] the day of the week when the transition
401
+ # occurs. 0 is Sunday, 6 is Saturday.
402
+ # @param transition_at [Integer] the time in seconds after midnight local
403
+ # time at which the transition occurs.
404
+ # @raise [ArgumentError] if `transition_at` is not an `Integer`.
405
+ # @raise [ArgumentError] if `month` is not an `Integer`.
406
+ # @raise [ArgumentError] if `month` is less than 1 or greater than 12.
407
+ # @raise [ArgumentError] if `day_of_week` is not an `Integer`.
408
+ # @raise [ArgumentError] if `day_of_week` is less than 0 or greater than 6.
291
409
  def initialize(month, day_of_week, transition_at = 0)
292
410
  super(month, day_of_week, transition_at)
293
411
  end
294
412
 
295
- # Determines if this LastDayOfMonthTransitionRule is equal to another
413
+ # Determines if this {LastDayOfMonthTransitionRule} is equal to another
296
414
  # instance.
415
+ #
416
+ # @param r [Object] the instance to test for equality.
417
+ # @return [Boolean] `true` if `r` is a {LastDayOfMonthTransitionRule} with
418
+ # the same {transition_at}, month and day of week as this
419
+ # {LastDayOfMonthTransitionRule}, otherwise `false`.
297
420
  def ==(r)
298
421
  super(r) && r.kind_of?(LastDayOfMonthTransitionRule)
299
422
  end
@@ -301,16 +424,22 @@ module TZInfo
301
424
 
302
425
  protected
303
426
 
304
- # Returns a TimeOrDateTime representing midnight local time on the day
305
- # specified by the rule for the given offset and year.
306
- def get_day(year)
427
+ # Returns a `Time` representing midnight local time on the day specified by
428
+ # the rule for the given offset and year.
429
+ #
430
+ # @param offset [TimezoneOffset] the current offset at the time of the
431
+ # transition.
432
+ # @param year [Integer] the year in which the transition occurs.
433
+ # @return [Time] midnight local time on the day specified by the rule for
434
+ # the given offset and year.
435
+ def get_day(offset, year)
307
436
  next_month = month + 1
308
437
  if next_month == 13
309
438
  year += 1
310
439
  next_month = 1
311
440
  end
312
441
 
313
- candidate = new_time_or_datetime(year, next_month).add_with_convert(-86400)
442
+ candidate = Time.new(year, next_month, 1, 0, 0, 0, offset.observed_utc_offset) - 86400
314
443
  diff = candidate.wday - day_of_week
315
444
 
316
445
  if diff < 0
@@ -322,4 +451,5 @@ module TZInfo
322
451
  end
323
452
  end
324
453
  end
454
+ private_constant :LastDayOfMonthTransitionRule
325
455
  end
@@ -0,0 +1,63 @@
1
+ # encoding: UTF-8
2
+ # frozen_string_literal: true
3
+
4
+ module TZInfo
5
+ # Represents a period of time in a time zone where the same offset from UTC
6
+ # applies. The period of time is bounded at at least one end, either having a
7
+ # start transition, end transition or both start and end transitions.
8
+ class TransitionsTimezonePeriod < TimezonePeriod
9
+ # @return [TimezoneTransition] the transition that defines the start of this
10
+ # {TimezonePeriod} (`nil` if the start is unbounded).
11
+ attr_reader :start_transition
12
+
13
+ # @return [TimezoneTransition] the transition that defines the end of this
14
+ # {TimezonePeriod} (`nil` if the end is unbounded).
15
+ attr_reader :end_transition
16
+
17
+ # Initializes a {TransitionsTimezonePeriod}.
18
+ #
19
+ # At least one of `start_transition` and `end_transition` must be specified.
20
+ #
21
+ # @param start_transition [TimezoneTransition] the transition that defines
22
+ # the start of the period, or `nil` if the start is unbounded.
23
+ # @param end_transition [TimezoneTransition] the transition that defines the
24
+ # end of the period, or `nil` if the end is unbounded.
25
+ # @raise [ArgumentError] if both `start_transition` and `end_transition` are
26
+ # `nil`.
27
+ def initialize(start_transition, end_transition)
28
+ if start_transition
29
+ super(start_transition.offset)
30
+ elsif end_transition
31
+ super(end_transition.previous_offset)
32
+ else
33
+ raise ArgumentError, 'At least one of start_transition and end_transition must be specified'
34
+ end
35
+
36
+ @start_transition = start_transition
37
+ @end_transition = end_transition
38
+ end
39
+
40
+ # Determines if this {TransitionsTimezonePeriod} is equal to another
41
+ # instance.
42
+ #
43
+ # @param p [Object] the instance to test for equality.
44
+ # @return [Boolean] `true` if `p` is a {TransitionsTimezonePeriod} with the
45
+ # same {offset}, {start_transition} and {end_transition}, otherwise
46
+ # `false`.
47
+ def ==(p)
48
+ p.kind_of?(TransitionsTimezonePeriod) && start_transition == p.start_transition && end_transition == p.end_transition
49
+ end
50
+ alias eql? ==
51
+
52
+ # @return [Integer] a hash based on {start_transition} and {end_transition}.
53
+ def hash
54
+ [@start_transition, @end_transition].hash
55
+ end
56
+
57
+ # @return [String] the internal object state as a programmer-readable
58
+ # `String`.
59
+ def inspect
60
+ "#<#{self.class}: @start_transition=#{@start_transition.inspect}, @end_transition=#{@end_transition.inspect}>"
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,7 @@
1
+ # encoding: UTF-8
2
+ # frozen_string_literal: true
3
+
4
+ module TZInfo
5
+ # The TZInfo version number.
6
+ VERSION = '2.0.6'
7
+ end
@@ -0,0 +1,61 @@
1
+ # encoding: UTF-8
2
+ # frozen_string_literal: true
3
+
4
+ module TZInfo
5
+ # The {WithOffset} module is included in {TimeWithOffset},
6
+ # {DateTimeWithOffset} and {TimestampWithOffset}. It provides an override for
7
+ # the {strftime} method that handles expanding the `%Z` directive according to
8
+ # the {TimezoneOffset#abbreviation abbreviation} of the {TimezoneOffset}
9
+ # associated with a local time.
10
+ module WithOffset
11
+ # Overrides the `Time`, `DateTime` or {Timestamp} version of `strftime`,
12
+ # replacing `%Z` with the {TimezoneOffset#abbreviation abbreviation} of the
13
+ # associated {TimezoneOffset}. If there is no associated offset, `%Z` is
14
+ # expanded by the base class instead.
15
+ #
16
+ # All the format directives handled by the base class are supported.
17
+ #
18
+ # @param format [String] the format string.
19
+ # @return [String] the formatted time.
20
+ # @raise [ArgumentError] if `format` is `nil`.
21
+ def strftime(format)
22
+ raise ArgumentError, 'format must be specified' unless format
23
+
24
+ if_timezone_offset do |o|
25
+ abbreviation = nil
26
+
27
+ format = format.gsub(/%(%*)Z/) do
28
+ if $1.length.odd?
29
+ # Return %%Z so the real strftime treats it as a literal %Z too.
30
+ "#$1%Z"
31
+ else
32
+ "#$1#{abbreviation ||= o.abbreviation.gsub(/%/, '%%')}"
33
+ end
34
+ end
35
+ end
36
+
37
+ super
38
+ end
39
+
40
+ protected
41
+
42
+ # Performs a calculation if there is an associated {TimezoneOffset}.
43
+ #
44
+ # @param result [Object] a result value that can be manipulated by the block
45
+ # if there is an associated {TimezoneOffset}.
46
+ # @yield [period, result] if there is an associated {TimezoneOffset}, the
47
+ # block is yielded to in order to calculate the method result.
48
+ # @yieldparam period [TimezoneOffset] the associated {TimezoneOffset}.
49
+ # @yieldparam result [Object] the `result` parameter.
50
+ # @yieldreturn [Object] the result of the calculation performed if there is
51
+ # an associated {TimezoneOffset}.
52
+ # @return [Object] the result of the block if there is an associated
53
+ # {TimezoneOffset}, otherwise the `result` parameter.
54
+ #
55
+ # @private
56
+ def if_timezone_offset(result = nil) #:nodoc:
57
+ to = timezone_offset
58
+ to ? yield(to, result) : result
59
+ end
60
+ end
61
+ end