tzinfo 1.2.5 → 2.0.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (148) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/.yardopts +3 -0
  4. data/CHANGES.md +607 -377
  5. data/LICENSE +13 -13
  6. data/README.md +368 -113
  7. data/lib/tzinfo/annual_rules.rb +71 -0
  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 +181 -0
  16. data/lib/tzinfo/data_sources/ruby_data_source.rb +145 -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 +596 -0
  20. data/lib/tzinfo/data_sources/zoneinfo_reader.rb +486 -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/string_deduper.rb +118 -0
  42. data/lib/tzinfo/time_with_offset.rb +154 -0
  43. data/lib/tzinfo/timestamp.rb +552 -0
  44. data/lib/tzinfo/timestamp_with_offset.rb +85 -0
  45. data/lib/tzinfo/timezone.rb +989 -498
  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/transition_rule.rb +455 -0
  51. data/lib/tzinfo/transitions_timezone_period.rb +63 -0
  52. data/lib/tzinfo/untaint_ext.rb +18 -0
  53. data/lib/tzinfo/version.rb +7 -0
  54. data/lib/tzinfo/with_offset.rb +61 -0
  55. data/lib/tzinfo.rb +74 -29
  56. data.tar.gz.sig +0 -0
  57. metadata +72 -122
  58. metadata.gz.sig +0 -0
  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/ruby_core_support.rb +0 -146
  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 -340
  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 -488
  76. data/lib/tzinfo/zoneinfo_timezone_info.rb +0 -296
  77. data/test/tc_country.rb +0 -234
  78. data/test/tc_country_index_definition.rb +0 -69
  79. data/test/tc_country_info.rb +0 -16
  80. data/test/tc_country_timezone.rb +0 -173
  81. data/test/tc_data_source.rb +0 -218
  82. data/test/tc_data_timezone.rb +0 -99
  83. data/test/tc_data_timezone_info.rb +0 -18
  84. data/test/tc_info_timezone.rb +0 -34
  85. data/test/tc_linked_timezone.rb +0 -155
  86. data/test/tc_linked_timezone_info.rb +0 -23
  87. data/test/tc_offset_rationals.rb +0 -23
  88. data/test/tc_ruby_core_support.rb +0 -168
  89. data/test/tc_ruby_country_info.rb +0 -110
  90. data/test/tc_ruby_data_source.rb +0 -143
  91. data/test/tc_time_or_datetime.rb +0 -654
  92. data/test/tc_timezone.rb +0 -1350
  93. data/test/tc_timezone_definition.rb +0 -113
  94. data/test/tc_timezone_index_definition.rb +0 -73
  95. data/test/tc_timezone_info.rb +0 -11
  96. data/test/tc_timezone_london.rb +0 -143
  97. data/test/tc_timezone_melbourne.rb +0 -142
  98. data/test/tc_timezone_new_york.rb +0 -142
  99. data/test/tc_timezone_offset.rb +0 -126
  100. data/test/tc_timezone_period.rb +0 -555
  101. data/test/tc_timezone_proxy.rb +0 -136
  102. data/test/tc_timezone_transition.rb +0 -366
  103. data/test/tc_timezone_transition_definition.rb +0 -295
  104. data/test/tc_timezone_utc.rb +0 -27
  105. data/test/tc_transition_data_timezone_info.rb +0 -423
  106. data/test/tc_zoneinfo_country_info.rb +0 -78
  107. data/test/tc_zoneinfo_data_source.rb +0 -1195
  108. data/test/tc_zoneinfo_timezone_info.rb +0 -1232
  109. data/test/test_utils.rb +0 -163
  110. data/test/ts_all.rb +0 -7
  111. data/test/ts_all_ruby.rb +0 -5
  112. data/test/ts_all_zoneinfo.rb +0 -7
  113. data/test/tzinfo-data/tzinfo/data/definitions/America/Argentina/Buenos_Aires.rb +0 -89
  114. data/test/tzinfo-data/tzinfo/data/definitions/America/New_York.rb +0 -315
  115. data/test/tzinfo-data/tzinfo/data/definitions/Australia/Melbourne.rb +0 -218
  116. data/test/tzinfo-data/tzinfo/data/definitions/EST.rb +0 -19
  117. data/test/tzinfo-data/tzinfo/data/definitions/Etc/GMT__m__1.rb +0 -21
  118. data/test/tzinfo-data/tzinfo/data/definitions/Etc/GMT__p__1.rb +0 -21
  119. data/test/tzinfo-data/tzinfo/data/definitions/Etc/UTC.rb +0 -21
  120. data/test/tzinfo-data/tzinfo/data/definitions/Europe/Amsterdam.rb +0 -261
  121. data/test/tzinfo-data/tzinfo/data/definitions/Europe/Andorra.rb +0 -186
  122. data/test/tzinfo-data/tzinfo/data/definitions/Europe/London.rb +0 -321
  123. data/test/tzinfo-data/tzinfo/data/definitions/Europe/Paris.rb +0 -265
  124. data/test/tzinfo-data/tzinfo/data/definitions/Europe/Prague.rb +0 -220
  125. data/test/tzinfo-data/tzinfo/data/definitions/UTC.rb +0 -16
  126. data/test/tzinfo-data/tzinfo/data/indexes/countries.rb +0 -927
  127. data/test/tzinfo-data/tzinfo/data/indexes/timezones.rb +0 -596
  128. data/test/tzinfo-data/tzinfo/data/version.rb +0 -14
  129. data/test/tzinfo-data/tzinfo/data.rb +0 -8
  130. data/test/zoneinfo/America/Argentina/Buenos_Aires +0 -0
  131. data/test/zoneinfo/America/New_York +0 -0
  132. data/test/zoneinfo/Australia/Melbourne +0 -0
  133. data/test/zoneinfo/EST +0 -0
  134. data/test/zoneinfo/Etc/UTC +0 -0
  135. data/test/zoneinfo/Europe/Amsterdam +0 -0
  136. data/test/zoneinfo/Europe/Andorra +0 -0
  137. data/test/zoneinfo/Europe/London +0 -0
  138. data/test/zoneinfo/Europe/Paris +0 -0
  139. data/test/zoneinfo/Europe/Prague +0 -0
  140. data/test/zoneinfo/Factory +0 -0
  141. data/test/zoneinfo/iso3166.tab +0 -275
  142. data/test/zoneinfo/leapseconds +0 -61
  143. data/test/zoneinfo/posix/Europe/London +0 -0
  144. data/test/zoneinfo/posixrules +0 -0
  145. data/test/zoneinfo/right/Europe/London +0 -0
  146. data/test/zoneinfo/zone.tab +0 -439
  147. data/test/zoneinfo/zone1970.tab +0 -369
  148. data/tzinfo.gemspec +0 -21
@@ -0,0 +1,455 @@
1
+ # encoding: UTF-8
2
+ # frozen_string_literal: true
3
+
4
+ module TZInfo
5
+ # Base class for rules definining the transition between standard and daylight
6
+ # savings time.
7
+ #
8
+ # @abstract
9
+ # @private
10
+ class TransitionRule #:nodoc:
11
+ # Returns the number of seconds after midnight local time on the day
12
+ # identified by the rule at which the transition occurs. Can be negative to
13
+ # denote a time on the prior day. Can be greater than or equal to 86,400 to
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.
18
+ attr_reader :transition_at
19
+
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`.
25
+ def initialize(transition_at)
26
+ raise ArgumentError, 'Invalid transition_at' unless transition_at.kind_of?(Integer)
27
+ @transition_at = transition_at
28
+ end
29
+
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.
37
+ def at(offset, year)
38
+ day = get_day(offset, year)
39
+ TimestampWithOffset.set_timezone_offset(Timestamp.for(day + @transition_at), offset)
40
+ end
41
+
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`.
47
+ def ==(r)
48
+ r.kind_of?(TransitionRule) && @transition_at == r.transition_at
49
+ end
50
+ alias eql? ==
51
+
52
+ # @return [Integer] a hash based on {hash_args} (defaulting to
53
+ # {transition_at}).
54
+ def hash
55
+ hash_args.hash
56
+ end
57
+
58
+ protected
59
+
60
+ # @return [Array] an `Array` of parameters that will influence the output of
61
+ # {hash}.
62
+ def hash_args
63
+ [@transition_at]
64
+ end
65
+ end
66
+ private_constant :TransitionRule
67
+
68
+ # A base class for transition rules that activate based on an integer day of
69
+ # the year.
70
+ #
71
+ # @abstract
72
+ # @private
73
+ class DayOfYearTransitionRule < TransitionRule #:nodoc:
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`.
82
+ def initialize(day, transition_at)
83
+ super(transition_at)
84
+ raise ArgumentError, 'Invalid day' unless day.kind_of?(Integer)
85
+ @seconds = day * 86400
86
+ end
87
+
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`.
94
+ def ==(r)
95
+ super(r) && r.kind_of?(DayOfYearTransitionRule) && @seconds == r.seconds
96
+ end
97
+ alias eql? ==
98
+
99
+ protected
100
+
101
+ # @return [Integer] the day multipled by the number of seconds in a day.
102
+ attr_reader :seconds
103
+
104
+ # (see TransitionRule#hash_args)
105
+ def hash_args
106
+ [@seconds] + super
107
+ end
108
+ end
109
+ private_constant :DayOfYearTransitionRule
110
+
111
+ # Defines transitions that occur on the zero-based nth day of the year.
112
+ #
113
+ # Day 0 is 1 January.
114
+ #
115
+ # Leap days are counted. Day 59 will be 29 February on a leap year and 1 March
116
+ # on a non-leap year. Day 365 will be 31 December on a leap year and 1 January
117
+ # the following year on a non-leap year.
118
+ #
119
+ # @private
120
+ class AbsoluteDayOfYearTransitionRule < DayOfYearTransitionRule #:nodoc:
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.
130
+ def initialize(day, transition_at = 0)
131
+ super(day, transition_at)
132
+ raise ArgumentError, 'Invalid day' unless day >= 0 && day <= 365
133
+ end
134
+
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`.
137
+ def is_always_first_day_of_year?
138
+ seconds == 0
139
+ end
140
+
141
+ # @return [Boolean] `false`.
142
+ def is_always_last_day_of_year?
143
+ false
144
+ end
145
+
146
+ # Determines if this {AbsoluteDayOfYearTransitionRule} is equal to another
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`.
153
+ def ==(r)
154
+ super(r) && r.kind_of?(AbsoluteDayOfYearTransitionRule)
155
+ end
156
+ alias eql? ==
157
+
158
+ protected
159
+
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
170
+ end
171
+
172
+ # (see TransitionRule#hash_args)
173
+ def hash_args
174
+ [AbsoluteDayOfYearTransitionRule] + super
175
+ end
176
+ end
177
+
178
+ # Defines transitions that occur on the one-based nth Julian day of the year.
179
+ #
180
+ # Leap days are not counted. Day 1 is 1 January. Day 60 is always 1 March.
181
+ # Day 365 is always 31 December.
182
+ #
183
+ # @private
184
+ class JulianDayOfYearTransitionRule < DayOfYearTransitionRule #:nodoc:
185
+ # The 60 days in seconds.
186
+ LEAP = 60 * 86400
187
+ private_constant :LEAP
188
+
189
+ # The length of a non-leap year in seconds.
190
+ YEAR = 365 * 86400
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.
202
+ def initialize(day, transition_at = 0)
203
+ super(day, transition_at)
204
+ raise ArgumentError, 'Invalid day' unless day >= 1 && day <= 365
205
+ end
206
+
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`.
209
+ def is_always_first_day_of_year?
210
+ seconds == 86400
211
+ end
212
+
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`.
215
+ def is_always_last_day_of_year?
216
+ seconds == YEAR
217
+ end
218
+
219
+ # Determines if this {JulianDayOfYearTransitionRule} is equal to another
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`.
226
+ def ==(r)
227
+ super(r) && r.kind_of?(JulianDayOfYearTransitionRule)
228
+ end
229
+ alias eql? ==
230
+
231
+ protected
232
+
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)
242
+ # Returns 1 March on non-leap years.
243
+ leap = Time.new(year, 2, 29, 0, 0, 0, offset.observed_utc_offset)
244
+ diff = seconds - LEAP
245
+ diff += 86400 if diff >= 0 && leap.mday == 29
246
+ leap + diff
247
+ end
248
+
249
+ # (see TransitionRule#hash_args)
250
+ def hash_args
251
+ [JulianDayOfYearTransitionRule] + super
252
+ end
253
+ end
254
+ private_constant :JulianDayOfYearTransitionRule
255
+
256
+ # A base class for rules that transition on a particular day of week of a
257
+ # given week (subclasses specify which week of the month).
258
+ #
259
+ # @abstract
260
+ # @private
261
+ class DayOfWeekTransitionRule < TransitionRule #:nodoc:
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.
274
+ def initialize(month, day_of_week, transition_at)
275
+ super(transition_at)
276
+ raise ArgumentError, 'Invalid month' unless month.kind_of?(Integer) && month >= 1 && month <= 12
277
+ raise ArgumentError, 'Invalid day_of_week' unless day_of_week.kind_of?(Integer) && day_of_week >= 0 && day_of_week <= 6
278
+ @month = month
279
+ @day_of_week = day_of_week
280
+ end
281
+
282
+ # @return [Boolean] `false`.
283
+ def is_always_first_day_of_year?
284
+ false
285
+ end
286
+
287
+ # @return [Boolean] `false`.
288
+ def is_always_last_day_of_year?
289
+ false
290
+ end
291
+
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`.
299
+ def ==(r)
300
+ super(r) && r.kind_of?(DayOfWeekTransitionRule) && @month == r.month && @day_of_week == r.day_of_week
301
+ end
302
+ alias eql? ==
303
+
304
+ protected
305
+
306
+ # @return [Integer] the month of the year (1 to 12).
307
+ attr_reader :month
308
+
309
+ # @return [Integer] the day of the week (0 to 6 for Sunday to Monday).
310
+ attr_reader :day_of_week
311
+
312
+ # (see TransitionRule#hash_args)
313
+ def hash_args
314
+ [@month, @day_of_week] + super
315
+ end
316
+ end
317
+ private_constant :DayOfWeekTransitionRule
318
+
319
+ # A rule that transitions on the nth occurrence of a particular day of week
320
+ # of a calendar month.
321
+ #
322
+ # @private
323
+ class DayOfMonthTransitionRule < DayOfWeekTransitionRule #:nodoc:
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.
340
+ def initialize(month, week, day_of_week, transition_at = 0)
341
+ super(month, day_of_week, transition_at)
342
+ raise ArgumentError, 'Invalid week' unless week.kind_of?(Integer) && week >= 1 && week <= 4
343
+ @offset_start = (week - 1) * 7 + 1
344
+ end
345
+
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`.
353
+ def ==(r)
354
+ super(r) && r.kind_of?(DayOfMonthTransitionRule) && @offset_start == r.offset_start
355
+ end
356
+ alias eql? ==
357
+
358
+ protected
359
+
360
+ # @return [Integer] the day the week starts on for a month starting on a
361
+ # Sunday.
362
+ attr_reader :offset_start
363
+
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)
374
+ diff = day_of_week - candidate.wday
375
+
376
+ if diff < 0
377
+ candidate + (7 + diff) * 86400
378
+ elsif diff > 0
379
+ candidate + diff * 86400
380
+ else
381
+ candidate
382
+ end
383
+ end
384
+
385
+ # (see TransitionRule#hash_args)
386
+ def hash_args
387
+ [@offset_start] + super
388
+ end
389
+ end
390
+ private_constant :DayOfMonthTransitionRule
391
+
392
+ # A rule that transitions on the last occurrence of a particular day of week
393
+ # of a calendar month.
394
+ #
395
+ # @private
396
+ class LastDayOfMonthTransitionRule < DayOfWeekTransitionRule #:nodoc:
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.
409
+ def initialize(month, day_of_week, transition_at = 0)
410
+ super(month, day_of_week, transition_at)
411
+ end
412
+
413
+ # Determines if this {LastDayOfMonthTransitionRule} is equal to another
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`.
420
+ def ==(r)
421
+ super(r) && r.kind_of?(LastDayOfMonthTransitionRule)
422
+ end
423
+ alias eql? ==
424
+
425
+ protected
426
+
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)
436
+ next_month = month + 1
437
+ if next_month == 13
438
+ year += 1
439
+ next_month = 1
440
+ end
441
+
442
+ candidate = Time.new(year, next_month, 1, 0, 0, 0, offset.observed_utc_offset) - 86400
443
+ diff = candidate.wday - day_of_week
444
+
445
+ if diff < 0
446
+ candidate - (diff + 7) * 86400
447
+ elsif diff > 0
448
+ candidate - diff * 86400
449
+ else
450
+ candidate
451
+ end
452
+ end
453
+ end
454
+ private_constant :LastDayOfMonthTransitionRule
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,18 @@
1
+ # encoding: UTF-8
2
+ # frozen_string_literal: true
3
+
4
+ module TZInfo
5
+ # Object#untaint is deprecated in Ruby >= 2.7 and will be removed in 3.2.
6
+ # UntaintExt adds a refinement to make Object#untaint a no-op and avoid the
7
+ # warning.
8
+ #
9
+ # @private
10
+ module UntaintExt # :nodoc:
11
+ refine Object do
12
+ def untaint
13
+ self
14
+ end
15
+ end
16
+ end
17
+ private_constant :UntaintExt
18
+ 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.5'
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