tzinfo 1.2.5 → 1.2.9

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of tzinfo might be problematic. Click here for more details.

Files changed (66) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +3 -3
  3. data.tar.gz.sig +0 -0
  4. data/CHANGES.md +86 -48
  5. data/LICENSE +1 -1
  6. data/README.md +9 -8
  7. data/lib/tzinfo.rb +3 -0
  8. data/lib/tzinfo/annual_rules.rb +51 -0
  9. data/lib/tzinfo/data_source.rb +1 -1
  10. data/lib/tzinfo/posix_time_zone_parser.rb +136 -0
  11. data/lib/tzinfo/ruby_core_support.rb +24 -1
  12. data/lib/tzinfo/ruby_data_source.rb +24 -20
  13. data/lib/tzinfo/time_or_datetime.rb +11 -0
  14. data/lib/tzinfo/timezone.rb +10 -6
  15. data/lib/tzinfo/transition_rule.rb +325 -0
  16. data/lib/tzinfo/zoneinfo_data_source.rb +10 -1
  17. data/lib/tzinfo/zoneinfo_timezone_info.rb +264 -40
  18. data/test/tc_annual_rules.rb +95 -0
  19. data/test/tc_country.rb +6 -2
  20. data/test/tc_posix_time_zone_parser.rb +261 -0
  21. data/test/tc_ruby_data_source.rb +26 -2
  22. data/test/tc_time_or_datetime.rb +26 -6
  23. data/test/tc_timezone.rb +13 -2
  24. data/test/tc_transition_data_timezone_info.rb +11 -1
  25. data/test/tc_transition_rule.rb +663 -0
  26. data/test/tc_zoneinfo_data_source.rb +11 -2
  27. data/test/tc_zoneinfo_timezone_info.rb +1034 -113
  28. data/test/test_utils.rb +32 -3
  29. data/test/ts_all_zoneinfo.rb +3 -1
  30. data/test/tzinfo-data/tzinfo/data/definitions/America/Argentina/Buenos_Aires.rb +5 -5
  31. data/test/tzinfo-data/tzinfo/data/definitions/America/New_York.rb +13 -1
  32. data/test/tzinfo-data/tzinfo/data/definitions/Australia/Melbourne.rb +13 -1
  33. data/test/tzinfo-data/tzinfo/data/definitions/EST.rb +1 -1
  34. data/test/tzinfo-data/tzinfo/data/definitions/Etc/GMT__m__1.rb +2 -2
  35. data/test/tzinfo-data/tzinfo/data/definitions/Etc/GMT__p__1.rb +2 -2
  36. data/test/tzinfo-data/tzinfo/data/definitions/Etc/UTC.rb +1 -1
  37. data/test/tzinfo-data/tzinfo/data/definitions/Europe/Amsterdam.rb +15 -3
  38. data/test/tzinfo-data/tzinfo/data/definitions/Europe/Andorra.rb +13 -1
  39. data/test/tzinfo-data/tzinfo/data/definitions/Europe/London.rb +13 -1
  40. data/test/tzinfo-data/tzinfo/data/definitions/Europe/Paris.rb +15 -3
  41. data/test/tzinfo-data/tzinfo/data/definitions/Europe/Prague.rb +19 -4
  42. data/test/tzinfo-data/tzinfo/data/definitions/UTC.rb +1 -1
  43. data/test/tzinfo-data/tzinfo/data/indexes/countries.rb +197 -184
  44. data/test/tzinfo-data/tzinfo/data/indexes/timezones.rb +60 -47
  45. data/test/tzinfo-data/tzinfo/data/version.rb +9 -3
  46. data/test/zoneinfo/America/Argentina/Buenos_Aires +0 -0
  47. data/test/zoneinfo/America/New_York +0 -0
  48. data/test/zoneinfo/Australia/Melbourne +0 -0
  49. data/test/zoneinfo/EST +0 -0
  50. data/test/zoneinfo/Etc/UTC +0 -0
  51. data/test/zoneinfo/Europe/Amsterdam +0 -0
  52. data/test/zoneinfo/Europe/Andorra +0 -0
  53. data/test/zoneinfo/Europe/London +0 -0
  54. data/test/zoneinfo/Europe/Paris +0 -0
  55. data/test/zoneinfo/Europe/Prague +0 -0
  56. data/test/zoneinfo/Factory +0 -0
  57. data/test/zoneinfo/iso3166.tab +13 -14
  58. data/test/zoneinfo/leapseconds +38 -21
  59. data/test/zoneinfo/posix/Europe/London +0 -0
  60. data/test/zoneinfo/posixrules +0 -0
  61. data/test/zoneinfo/right/Europe/London +0 -0
  62. data/test/zoneinfo/zone.tab +172 -159
  63. data/test/zoneinfo/zone1970.tab +185 -170
  64. data/tzinfo.gemspec +2 -2
  65. metadata +28 -24
  66. metadata.gz.sig +0 -0
data/test/tc_timezone.rb CHANGED
@@ -2,6 +2,10 @@ require File.join(File.expand_path(File.dirname(__FILE__)), 'test_utils')
2
2
 
3
3
  include TZInfo
4
4
 
5
+ # Use send as a workaround for erroneous 'wrong number of arguments' errors with
6
+ # JRuby 9.0.5.0 when calling methods with Java implementations. See #114.
7
+ send(:using, TaintExt) if Module.const_defined?(:TaintExt)
8
+
5
9
  class TCTimezone < Minitest::Test
6
10
 
7
11
  class BlockCalled < StandardError
@@ -242,7 +246,7 @@ class TCTimezone < Minitest::Test
242
246
  def test_get_tainted_loaded
243
247
  Timezone.get('Europe/Andorra')
244
248
 
245
- safe_test do
249
+ safe_test(:unavailable => :skip) do
246
250
  identifier = 'Europe/Andorra'.dup.taint
247
251
  assert(identifier.tainted?)
248
252
  tz = Timezone.get(identifier)
@@ -261,7 +265,9 @@ class TCTimezone < Minitest::Test
261
265
  end
262
266
 
263
267
  def test_get_tainted_not_previously_loaded
264
- safe_test do
268
+ skip_if_has_bug_14060
269
+
270
+ safe_test(:unavailable => :skip) do
265
271
  identifier = 'Europe/Andorra'.dup.taint
266
272
  assert(identifier.tainted?)
267
273
  tz = Timezone.get(identifier)
@@ -271,6 +277,8 @@ class TCTimezone < Minitest::Test
271
277
  end
272
278
 
273
279
  def test_get_tainted_and_frozen_not_previously_loaded
280
+ skip_if_has_bug_14060
281
+
274
282
  safe_test do
275
283
  tz = Timezone.get('Europe/Amsterdam'.dup.taint.freeze)
276
284
  assert_equal('Europe/Amsterdam', tz.identifier)
@@ -1260,6 +1268,7 @@ class TCTimezone < Minitest::Test
1260
1268
  assert_equal('BST BST', tz.strftime('%Z %Z', dt))
1261
1269
  assert_equal('BST %Z %BST %%Z %%BST', tz.strftime('%Z %%Z %%%Z %%%%Z %%%%%Z', dt))
1262
1270
  assert_equal('+0100 +01:00 +01:00:00 +01 %::::z', tz.strftime('%z %:z %::z %:::z %::::z', dt))
1271
+ assert_equal('1153001522 %s %1153001522', tz.strftime('%s %%s %%%s', dt))
1263
1272
  end
1264
1273
 
1265
1274
  def test_strftime_time
@@ -1271,6 +1280,7 @@ class TCTimezone < Minitest::Test
1271
1280
  assert_equal('BST BST', tz.strftime('%Z %Z', t))
1272
1281
  assert_equal('BST %Z %BST %%Z %%BST', tz.strftime('%Z %%Z %%%Z %%%%Z %%%%%Z', t))
1273
1282
  assert_equal('+0100 +01:00 +01:00:00 +01 %::::z', tz.strftime('%z %:z %::z %:::z %::::z', t))
1283
+ assert_equal('1153001522 %s %1153001522', tz.strftime('%s %%s %%%s', t))
1274
1284
  end
1275
1285
 
1276
1286
  def test_strftime_int
@@ -1282,6 +1292,7 @@ class TCTimezone < Minitest::Test
1282
1292
  assert_equal('BST BST', tz.strftime('%Z %Z', i))
1283
1293
  assert_equal('BST %Z %BST %%Z %%BST', tz.strftime('%Z %%Z %%%Z %%%%Z %%%%%Z', i))
1284
1294
  assert_equal('+0100 +01:00 +01:00:00 +01 %::::z', tz.strftime('%z %:z %::z %:::z %::::z', i))
1295
+ assert_equal('1153001522 %s %1153001522', tz.strftime('%s %%s %%%s', i))
1285
1296
  end
1286
1297
 
1287
1298
  def test_get_missing_data_source
@@ -359,7 +359,17 @@ class TCTransitionDataTimezoneInfo < Minitest::Test
359
359
  assert_equal([t1,t2,t3,t4,t5], dti.transitions_up_to(DateTime.new(2011,10,1,1,0,Rational(DATETIME_RESOLUTION,1000000))))
360
360
  assert_equal([t1,t2,t3,t4,t5], dti.transitions_up_to(DateTime.new(2011,10,1,1,0,1), DateTime.new(2010,4,1,1,0,0)))
361
361
  assert_equal([t2,t3,t4,t5], dti.transitions_up_to(DateTime.new(2011,10,1,1,0,1), DateTime.new(2010,4,1,1,0,1)))
362
- assert_equal([t2,t3,t4,t5], dti.transitions_up_to(DateTime.new(2011,10,1,1,0,1), DateTime.new(2010,4,1,1,0,Rational(DATETIME_RESOLUTION,1000000))))
362
+
363
+ # Ruby 1.8.7 (built with GCC 8.3.0 on Ubuntu 19.04) fails to perform
364
+ # DateTime comparisons correctly when sub-seconds are used.
365
+ to = DateTime.new(2011,10,1,1,0,1)
366
+ from = DateTime.new(2010,4,1,1,0,Rational(DATETIME_RESOLUTION,1000000))
367
+ if to > from
368
+ assert_equal([t2,t3,t4,t5], dti.transitions_up_to(to, from))
369
+ else
370
+ puts "This platform does not consider the DateTime #{to.strftime('%Y-%m-%d %H:%m:%S.%N')} to be later than the DateTime #{from.strftime('%Y-%m-%d %H:%m:%S.%N')}"
371
+ end
372
+
363
373
  assert_equal([t5], dti.transitions_up_to(DateTime.new(2015,1,1,0,0,0), DateTime.new(2011,10,1,1,0,0)))
364
374
  assert_equal([], dti.transitions_up_to(DateTime.new(2015,1,1,0,0,0), DateTime.new(2011,10,1,1,0,1)))
365
375
  end
@@ -0,0 +1,663 @@
1
+ require File.join(File.expand_path(File.dirname(__FILE__)), 'test_utils')
2
+
3
+ include TZInfo
4
+
5
+ class TCTransitionRule < Minitest::Test
6
+ [-1, 0, 1].each do |transition_at|
7
+ define_method "test_transition_at_#{transition_at}" do
8
+ rule = TestTransitionRule.new(transition_at)
9
+ assert_equal(transition_at, rule.transition_at)
10
+ end
11
+ end
12
+
13
+ [
14
+ [-1, 19, 23, 59, 59],
15
+ [0, 20, 0, 0, 0],
16
+ [1, 20, 0, 0, 1],
17
+ [60, 20, 0, 1, 0],
18
+ [86399, 20, 23, 59, 59],
19
+ [86400, 21, 0, 0, 0]
20
+ ].each do |(transition_at, expected_day, expected_hour, expected_minute, expected_second)|
21
+ define_method "test_at_with_transition_at_#{transition_at}" do
22
+ offset = TimezoneOffset.new(0, 0, 'TEST')
23
+ rule = TestTransitionRule.new(transition_at) do |y|
24
+ assert_equal(2020, y)
25
+ TimeOrDateTime.wrap(Time.utc(2020, 3, 20))
26
+ end
27
+
28
+ result = rule.at(offset, 2020)
29
+
30
+ assert_kind_of(TimeOrDateTime, result)
31
+ assert_equal(Time.utc(2020, 3, expected_day, expected_hour, expected_minute, expected_second), result.to_time)
32
+ end
33
+ end
34
+
35
+ [-7200, 0, 7200].each do |offset_seconds|
36
+ define_method "test_at_with_offset_#{offset_seconds}" do
37
+ offset = TimezoneOffset.new(offset_seconds, 0, 'TEST')
38
+ rule = TestTransitionRule.new(3600) do |y|
39
+ assert_equal(2020, y)
40
+ TimeOrDateTime.wrap(Time.utc(2020, 3, 20))
41
+ end
42
+
43
+ result = rule.at(offset, 2020)
44
+
45
+ assert_kind_of(TimeOrDateTime, result)
46
+ assert_equal(Time.utc(2020, 3, 20, 1, 0, 0) - offset_seconds, result.to_time)
47
+ end
48
+ end
49
+
50
+ [2020, 2021].each do |year|
51
+ define_method "test_at_with_year_#{year}" do
52
+ offset = TimezoneOffset.new(0, 0, 'TEST')
53
+ rule = TestTransitionRule.new(3600) do |y|
54
+ assert_equal(year, y)
55
+ TimeOrDateTime.wrap(Time.utc(year, 3, 20))
56
+ end
57
+
58
+ result = rule.at(offset, year)
59
+
60
+ assert_kind_of(TimeOrDateTime, result)
61
+ assert_equal(Time.utc(year, 3, 20, 1, 0, 0), result.to_time)
62
+ end
63
+ end
64
+
65
+ def test_at_crosses_64_bit_boundary
66
+ offset = TimezoneOffset.new(0, 0, 'TEST')
67
+ rule = TestTransitionRule.new(11648) do |y|
68
+ assert_equal(2038, y)
69
+ TimeOrDateTime.wrap(Time.utc(2038, 1, 19))
70
+ end
71
+
72
+ result = rule.at(offset, 2038)
73
+
74
+ assert_kind_of(TimeOrDateTime, result)
75
+ if RubyCoreSupport.time_supports_64bit
76
+ assert_kind_of(Time, result.to_orig)
77
+ else
78
+ assert_kind_of(DateTime, result.to_orig)
79
+ end
80
+ assert_equal(DateTime.new(2038, 1, 19, 3, 14, 8), result.to_datetime)
81
+ end
82
+
83
+ def test_at_crosses_negative_boundary
84
+ offset = TimezoneOffset.new(0, 0, 'TEST')
85
+ rule = TestTransitionRule.new(-1) do |y|
86
+ assert_equal(1970, y)
87
+ TimeOrDateTime.wrap(Time.utc(1970, 1, 1))
88
+ end
89
+
90
+ result = rule.at(offset, 1970)
91
+
92
+ assert_kind_of(TimeOrDateTime, result)
93
+ if RubyCoreSupport.time_supports_negative
94
+ assert_kind_of(Time, result.to_orig)
95
+ else
96
+ assert_kind_of(DateTime, result.to_orig)
97
+ end
98
+ assert_equal(DateTime.new(1969, 12, 31, 23, 59, 59), result.to_datetime)
99
+ end
100
+
101
+ NEGATIVE_DATES = [[1969, 12, 31]]
102
+ B32_DATES = [[1970, 1, 1], [2038, 1, 19]]
103
+ B64_DATES = [[2038, 1, 20], [2038, 2, 1], [2039, 1, 1]]
104
+
105
+ B32_DATES.concat(RubyCoreSupport.time_supports_negative ? NEGATIVE_DATES : []).concat(RubyCoreSupport.time_supports_64bit ? B64_DATES : []).each do |(year, month, day)|
106
+ define_method "test_new_time_or_datetime_returns_time_based_instance_for_#{year}_#{month}_#{day}" do
107
+ result = TestTransitionRule.new(0).new_time_or_datetime!(year, month, day)
108
+ assert_kind_of(TimeOrDateTime, result)
109
+ assert_kind_of(Time, result.to_orig)
110
+ assert_equal(Time.utc(year, month, day), result.to_orig)
111
+ end
112
+ end
113
+
114
+ (RubyCoreSupport.time_supports_negative ? [] : NEGATIVE_DATES).concat(RubyCoreSupport.time_supports_64bit ? [] : B64_DATES).each do |(year, month, day)|
115
+ define_method "test_new_time_or_datetime_returns_datetime_based_instance_for_#{year}_#{month}_#{day}" do
116
+ result = TestTransitionRule.new(0).new_time_or_datetime!(year, month, day)
117
+ assert_kind_of(TimeOrDateTime, result)
118
+ assert_kind_of(DateTime, result.to_orig)
119
+ assert_equal(DateTime.new(year, month, day), result.to_orig)
120
+ end
121
+ end
122
+
123
+ [1900, 2021, 2039].each do |year|
124
+ define_method "test_new_time_or_datetime_returns_march_1_for_feb_29_on_non_leap_year_#{year}" do
125
+ result = TestTransitionRule.new(0).new_time_or_datetime!(year, 2, 29)
126
+ assert_kind_of(TimeOrDateTime, result)
127
+ assert_equal(DateTime.new(year, 3, 1), result.to_datetime)
128
+ end
129
+ end
130
+
131
+ [1968, 2000, 2040].each do |year|
132
+ define_method "test_new_time_or_datetime_returns_feb_29_on_leap_year_#{year}" do
133
+ result = TestTransitionRule.new(0).new_time_or_datetime!(year, 2, 29)
134
+ assert_kind_of(TimeOrDateTime, result)
135
+ assert_equal(DateTime.new(year, 2, 29), result.to_datetime)
136
+ end
137
+ end
138
+
139
+ class TestTransitionRule < TransitionRule
140
+ def initialize(transition_at, &block)
141
+ super(transition_at)
142
+ @get_day = block
143
+ end
144
+
145
+ alias new_time_or_datetime! new_time_or_datetime
146
+ public :new_time_or_datetime!
147
+
148
+ protected
149
+
150
+ def get_day(year)
151
+ @get_day.call(year)
152
+ end
153
+ end
154
+ end
155
+
156
+ module BaseTransitionRuleTestHelper
157
+ def test_invalid_transition_at
158
+ error = assert_raises(ArgumentError) { create_with_transition_at('0') }
159
+ assert_match(/\btransition_at(\b.*)?/, error.message)
160
+ end
161
+
162
+ [:==, :eql?].each do |method|
163
+ define_method "test_not_equal_by_transition_at_with_#{method}" do
164
+ rule1 = create_with_transition_at(0)
165
+ rule2 = create_with_transition_at(1)
166
+ assert_equal(false, rule1.send(method, rule2))
167
+ assert_equal(false, rule2.send(method, rule1))
168
+ end
169
+
170
+ define_method "test_not_equal_to_other_with_#{method}" do
171
+ rule = create_with_transition_at(0)
172
+ assert_equal(false, rule.send(method, Object.new))
173
+ end
174
+ end
175
+ end
176
+
177
+ class TCAbsoluteDayOfYearTransitionRule < Minitest::Test
178
+ include BaseTransitionRuleTestHelper
179
+
180
+ [-1, 366, '0'].each do |value|
181
+ define_method "test_invalid_day_#{value}" do
182
+ error = assert_raises(ArgumentError) { AbsoluteDayOfYearTransitionRule.new(value) }
183
+ assert_match(/\bday\b/, error.message)
184
+ end
185
+ end
186
+
187
+ [
188
+ [2020, 0, 1, 1],
189
+ [2020, 58, 2, 28],
190
+ [2020, 59, 2, 29],
191
+ [2020, 365, 12, 31],
192
+ [2021, 59, 3, 1],
193
+ [2021, 365, 13, 1],
194
+ [2100, 59, 3, 1],
195
+ [2100, 365, 13, 1],
196
+ [2000, 59, 2, 29],
197
+ [2000, 365, 12, 31]
198
+ ].each do |(year, day, expected_month, expected_day)|
199
+ define_method "test_day_#{day}_of_year_#{year}" do
200
+ rule = AbsoluteDayOfYearTransitionRule.new(day, 3600)
201
+ offset = TimezoneOffset.new(0, 0, 'TEST')
202
+
203
+ result = rule.at(offset, year)
204
+
205
+ expected_year = year
206
+ if expected_month == 13
207
+ expected_year += 1
208
+ expected_month = 1
209
+ end
210
+
211
+ assert_kind_of(TimeOrDateTime, result)
212
+ assert_equal(DateTime.new(expected_year, expected_month, expected_day, 1, 0, 0), result.to_datetime)
213
+ end
214
+ end
215
+
216
+ def test_crosses_64_bit_boundary
217
+ # Internally the calculation starts with Jan 1 and adds days. Jan 20 2038
218
+ # will require a DateTime on 32-bit only systems.
219
+
220
+ rule = AbsoluteDayOfYearTransitionRule.new(19, 3600)
221
+ offset = TimezoneOffset.new(0, 0, 'TEST')
222
+
223
+ result = rule.at(offset, 2038)
224
+
225
+ assert_kind_of(TimeOrDateTime, result)
226
+ if RubyCoreSupport.time_supports_64bit
227
+ assert_kind_of(Time, result.to_orig)
228
+ else
229
+ assert_kind_of(DateTime, result.to_orig)
230
+ end
231
+ assert_equal(DateTime.new(2038, 1, 20, 1, 0, 0), result.to_datetime)
232
+ end
233
+
234
+ def test_day_0_is_always_first_day_of_year
235
+ rule = AbsoluteDayOfYearTransitionRule.new(0)
236
+ assert_equal(true, rule.is_always_first_day_of_year?)
237
+ end
238
+
239
+ [1, 365].each do |day|
240
+ define_method "test_day_#{day}_is_not_always_first_day_of_year" do
241
+ rule = AbsoluteDayOfYearTransitionRule.new(day)
242
+ assert_equal(false, rule.is_always_first_day_of_year?)
243
+ end
244
+
245
+ define_method "test_day_#{day}_is_not_always_last_day_of_year" do
246
+ rule = AbsoluteDayOfYearTransitionRule.new(day)
247
+ assert_equal(false, rule.is_always_last_day_of_year?)
248
+ end
249
+ end
250
+
251
+ [:==, :eql?].each do |method|
252
+ [
253
+ [0, 0],
254
+ [0, 3600],
255
+ [365, 0]
256
+ ].each do |(day, transition_at)|
257
+ define_method "test_equal_for_day_#{day}_and_transition_at_#{transition_at}_with_#{method}" do
258
+ rule1 = AbsoluteDayOfYearTransitionRule.new(day, transition_at)
259
+ rule2 = AbsoluteDayOfYearTransitionRule.new(day, transition_at)
260
+ assert_equal(true, rule1.send(method, rule2))
261
+ assert_equal(true, rule2.send(method, rule1))
262
+ end
263
+ end
264
+
265
+ define_method "test_not_equal_by_day_with_#{method}" do
266
+ rule1 = AbsoluteDayOfYearTransitionRule.new(0, 3600)
267
+ rule2 = AbsoluteDayOfYearTransitionRule.new(1, 3600)
268
+ assert_equal(false, rule1.send(method, rule2))
269
+ assert_equal(false, rule2.send(method, rule1))
270
+ end
271
+
272
+ define_method "test_not_equal_to_julian_with_#{method}" do
273
+ rule1 = AbsoluteDayOfYearTransitionRule.new(1, 0)
274
+ rule2 = JulianDayOfYearTransitionRule.new(1, 0)
275
+ assert_equal(false, rule1.send(method, rule2))
276
+ assert_equal(false, rule2.send(method, rule1))
277
+ end
278
+ end
279
+
280
+ [
281
+ [0, 0],
282
+ [0, 3600],
283
+ [365, 0]
284
+ ].each do |(day, transition_at)|
285
+ define_method "test_hash_for_day_#{day}_and_transition_at_#{transition_at}" do
286
+ rule = AbsoluteDayOfYearTransitionRule.new(day, transition_at)
287
+ expected = [AbsoluteDayOfYearTransitionRule, day * 86400, transition_at].hash
288
+ assert_equal(expected, rule.hash)
289
+ end
290
+ end
291
+
292
+ protected
293
+
294
+ def create_with_transition_at(transition_at)
295
+ AbsoluteDayOfYearTransitionRule.new(1, transition_at)
296
+ end
297
+ end
298
+
299
+ class TCJulianDayOfYearTransitionRule < Minitest::Test
300
+ include BaseTransitionRuleTestHelper
301
+
302
+ [0, 366, '1'].each do |value|
303
+ define_method "test_invalid_day_#{value}" do
304
+ error = assert_raises(ArgumentError) { JulianDayOfYearTransitionRule.new(value) }
305
+ assert_match(/\bday\b/, error.message)
306
+ end
307
+ end
308
+
309
+ [2020, 2021, 2100, 2000].each do |year|
310
+ [
311
+ [1, 1, 1],
312
+ [59, 2, 28],
313
+ [60, 3, 1],
314
+ [365, 12, 31]
315
+ ].each do |(day, expected_month, expected_day)|
316
+ define_method "test_day_#{day}_of_year_#{year}" do
317
+ rule = JulianDayOfYearTransitionRule.new(day, 3600)
318
+ offset = TimezoneOffset.new(0, 0, 'TEST')
319
+
320
+ result = rule.at(offset, year)
321
+
322
+ assert_kind_of(TimeOrDateTime, result)
323
+ assert_equal(DateTime.new(year, expected_month, expected_day, 1, 0, 0), result.to_datetime)
324
+ end
325
+ end
326
+ end
327
+
328
+ def test_day_1_is_always_first_day_of_year
329
+ rule = JulianDayOfYearTransitionRule.new(1)
330
+ assert_equal(true, rule.is_always_first_day_of_year?)
331
+ end
332
+
333
+ [2, 365].each do |day|
334
+ define_method "test_day_#{day}_is_not_always_first_day_of_year" do
335
+ rule = JulianDayOfYearTransitionRule.new(day)
336
+ assert_equal(false, rule.is_always_first_day_of_year?)
337
+ end
338
+ end
339
+
340
+ def test_day_365_is_always_last_day_of_year
341
+ rule = JulianDayOfYearTransitionRule.new(365)
342
+ assert_equal(true, rule.is_always_last_day_of_year?)
343
+ end
344
+
345
+ [1, 364].each do |day|
346
+ define_method "test_day_#{day}_is_not_always_last_day_of_year" do
347
+ rule = JulianDayOfYearTransitionRule.new(day)
348
+ assert_equal(false, rule.is_always_last_day_of_year?)
349
+ end
350
+ end
351
+
352
+ [:==, :eql?].each do |method|
353
+ [
354
+ [1, 0],
355
+ [1, 3600],
356
+ [365, 0]
357
+ ].each do |(day, transition_at)|
358
+ define_method "test_equal_for_day_#{day}_and_transition_at_#{transition_at}_with_#{method}" do
359
+ rule1 = JulianDayOfYearTransitionRule.new(day, transition_at)
360
+ rule2 = JulianDayOfYearTransitionRule.new(day, transition_at)
361
+ assert_equal(true, rule1.send(method, rule2))
362
+ assert_equal(true, rule2.send(method, rule1))
363
+ end
364
+ end
365
+
366
+ define_method "test_not_equal_by_day_with_#{method}" do
367
+ rule1 = JulianDayOfYearTransitionRule.new(1, 0)
368
+ rule2 = JulianDayOfYearTransitionRule.new(2, 0)
369
+ assert_equal(false, rule1.send(method, rule2))
370
+ assert_equal(false, rule2.send(method, rule1))
371
+ end
372
+
373
+ define_method "test_not_equal_to_absolute_with_#{method}" do
374
+ rule1 = JulianDayOfYearTransitionRule.new(1, 0)
375
+ rule2 = AbsoluteDayOfYearTransitionRule.new(1, 0)
376
+ assert_equal(false, rule1.send(method, rule2))
377
+ assert_equal(false, rule2.send(method, rule1))
378
+ end
379
+ end
380
+
381
+ [
382
+ [1, 0],
383
+ [1, 3600],
384
+ [365, 0]
385
+ ].each do |(day, transition_at)|
386
+ define_method "test_hash_for_day_#{day}_and_transition_at_#{transition_at}" do
387
+ rule = JulianDayOfYearTransitionRule.new(day, transition_at)
388
+ expected = [JulianDayOfYearTransitionRule, day * 86400, transition_at].hash
389
+ assert_equal(expected, rule.hash)
390
+ end
391
+ end
392
+
393
+ protected
394
+
395
+ def create_with_transition_at(transition_at)
396
+ JulianDayOfYearTransitionRule.new(1, transition_at)
397
+ end
398
+ end
399
+
400
+ module DayOfWeekTransitionRuleTestHelper
401
+ [-1, 0, 13, '1'].each do |month|
402
+ define_method "test_invalid_month_#{month}" do
403
+ error = assert_raises(ArgumentError) { create_with_month_and_day_of_week(month, 0) }
404
+ assert_match(/\bmonth\b/, error.message)
405
+ end
406
+ end
407
+
408
+ [-1, 7, '0'].each do |day_of_week|
409
+ define_method "test_invalid_day_of_week_#{day_of_week}" do
410
+ error = assert_raises(ArgumentError) { create_with_month_and_day_of_week(1, day_of_week) }
411
+ assert_match(/\bday_of_week\b/, error.message)
412
+ end
413
+ end
414
+
415
+ def test_is_not_always_first_day_of_year
416
+ rule = create_with_month_and_day_of_week(1, 0)
417
+ assert_equal(false, rule.is_always_first_day_of_year?)
418
+ end
419
+
420
+ def test_is_not_always_last_day_of_year
421
+ rule = create_with_month_and_day_of_week(12, 6)
422
+ assert_equal(false, rule.is_always_last_day_of_year?)
423
+ end
424
+
425
+ [:==, :eql?].each do |method|
426
+ define_method "test_not_equal_by_month_with_#{method}" do
427
+ rule1 = create_with_month_and_day_of_week(1, 0)
428
+ rule2 = create_with_month_and_day_of_week(2, 0)
429
+ assert_equal(false, rule1.send(method, rule2))
430
+ assert_equal(false, rule2.send(method, rule1))
431
+ end
432
+
433
+ define_method "test_not_equal_by_day_of_week_with_#{method}" do
434
+ rule1 = create_with_month_and_day_of_week(1, 0)
435
+ rule2 = create_with_month_and_day_of_week(1, 1)
436
+ assert_equal(false, rule1.send(method, rule2))
437
+ assert_equal(false, rule2.send(method, rule1))
438
+ end
439
+ end
440
+ end
441
+
442
+ class TCDayOfMonthTransitionRule < Minitest::Test
443
+ include BaseTransitionRuleTestHelper
444
+ include DayOfWeekTransitionRuleTestHelper
445
+
446
+ [-1, 0, 5, '1'].each do |week|
447
+ define_method "test_invalid_week_#{week}" do
448
+ error = assert_raises(ArgumentError) { DayOfMonthTransitionRule.new(1, week, 0) }
449
+ assert_match(/\bweek\b/, error.message)
450
+ end
451
+ end
452
+
453
+ [
454
+ # All possible first week start days.
455
+ [2020, 3, [1, 2, 3, 4, 5, 6, 7]],
456
+ [2021, 3, [7, 1, 2, 3, 4, 5, 6]],
457
+ [2022, 3, [6, 7, 1, 2, 3, 4, 5]],
458
+ [2023, 3, [5, 6, 7, 1, 2, 3, 4]],
459
+ [2018, 3, [4, 5, 6, 7, 1, 2, 3]],
460
+ [2024, 3, [3, 4, 5, 6, 7, 1, 2]],
461
+ [2025, 3, [2, 3, 4, 5, 6, 7, 1]],
462
+
463
+ # All possible months.
464
+ [2019, 1, [6]],
465
+ [2019, 2, [3]],
466
+ [2019, 3, [3]],
467
+ [2019, 4, [7]],
468
+ [2019, 5, [5]],
469
+ [2019, 6, [2]],
470
+ [2019, 7, [7]],
471
+ [2019, 8, [4]],
472
+ [2019, 9, [1]],
473
+ [2019, 10, [6]],
474
+ [2019, 11, [3]],
475
+ [2019, 12, [1]]
476
+ ].each do |(year, month, days)|
477
+ days.each_with_index do |expected_day, day_of_week|
478
+ (1..4).each do |week|
479
+ define_method "test_month_#{month}_week_#{week}_and_day_of_week_#{day_of_week}_year_#{year}" do
480
+ rule = DayOfMonthTransitionRule.new(month, week, day_of_week, 3600)
481
+ offset = TimezoneOffset.new(0, 0, 'TEST')
482
+
483
+ result = rule.at(offset, year)
484
+
485
+ assert_kind_of(TimeOrDateTime, result)
486
+ assert_equal(Time.utc(year, month, expected_day + (week - 1) * 7, 1, 0, 0), result.to_time)
487
+ end
488
+ end
489
+ end
490
+ end
491
+
492
+ [[3, 3, 20], [4, 6, 23]].each do |(week, day_of_week, expected_day)|
493
+ define_method "test_crosses_64_bit_boundary_for_day_of_week_#{day_of_week}" do
494
+ rule = DayOfMonthTransitionRule.new(1, week, day_of_week, 3600)
495
+ offset = TimezoneOffset.new(0, 0, 'TEST')
496
+
497
+ result = rule.at(offset, 2038)
498
+
499
+ assert_kind_of(TimeOrDateTime, result)
500
+ if RubyCoreSupport.time_supports_64bit
501
+ assert_kind_of(Time, result.to_orig)
502
+ else
503
+ assert_kind_of(DateTime, result.to_orig)
504
+ end
505
+ assert_equal(DateTime.new(2038, 1, expected_day, 1, 0, 0), result.to_datetime)
506
+ end
507
+ end
508
+
509
+ [:==, :eql?].each do |method|
510
+ [
511
+ [1, 1, 0, 0],
512
+ [1, 1, 0, 1],
513
+ [1, 1, 1, 0],
514
+ [1, 2, 0, 0],
515
+ [2, 1, 0, 0]
516
+ ].each do |(month, week, day_of_week, transition_at)|
517
+ define_method "test_equal_for_month_#{month}_week_#{week}_day_of_week_#{day_of_week}_and_transition_at_#{transition_at}_with_#{method}" do
518
+ rule1 = DayOfMonthTransitionRule.new(month, week, day_of_week, transition_at)
519
+ rule2 = DayOfMonthTransitionRule.new(month, week, day_of_week, transition_at)
520
+ assert_equal(true, rule1.send(method, rule2))
521
+ assert_equal(true, rule2.send(method, rule1))
522
+ end
523
+ end
524
+
525
+ define_method "test_not_equal_by_week_with_#{method}" do
526
+ rule1 = DayOfMonthTransitionRule.new(1, 1, 0, 0)
527
+ rule2 = DayOfMonthTransitionRule.new(1, 2, 0, 0)
528
+ assert_equal(false, rule1.send(method, rule2))
529
+ assert_equal(false, rule2.send(method, rule1))
530
+ end
531
+
532
+ define_method "test_not_equal_to_last_day_of_month_with_#{method}" do
533
+ rule1 = DayOfMonthTransitionRule.new(1, 1, 0, 0)
534
+ rule2 = LastDayOfMonthTransitionRule.new(1, 0, 0)
535
+ assert_equal(false, rule1.send(method, rule2))
536
+ assert_equal(false, rule2.send(method, rule1))
537
+ end
538
+ end
539
+
540
+ [
541
+ [1, 1, 0, 0],
542
+ [1, 1, 0, 1],
543
+ [1, 1, 1, 0],
544
+ [1, 2, 0, 0],
545
+ [2, 1, 0, 0]
546
+ ].each do |(month, week, day_of_week, transition_at)|
547
+ define_method "test_hash_for_month_#{month}_week_#{week}_day_of_week_#{day_of_week}_and_transition_at_#{transition_at}" do
548
+ rule = DayOfMonthTransitionRule.new(month, week, day_of_week, transition_at)
549
+ expected = [(week - 1) * 7 + 1, month, day_of_week, transition_at].hash
550
+ assert_equal(expected, rule.hash)
551
+ end
552
+ end
553
+
554
+ protected
555
+
556
+ def create_with_transition_at(transition_at)
557
+ DayOfMonthTransitionRule.new(1, 1, 0, transition_at)
558
+ end
559
+
560
+ def create_with_month_and_day_of_week(month, day_of_week)
561
+ DayOfMonthTransitionRule.new(month, 1, day_of_week)
562
+ end
563
+ end
564
+
565
+ class TCLastDayOfMonthTransitionRule < Minitest::Test
566
+ include BaseTransitionRuleTestHelper
567
+ include DayOfWeekTransitionRuleTestHelper
568
+
569
+ [
570
+ # All possible last days.
571
+ [2021, 10, [31, 25, 26, 27, 28, 29, 30]],
572
+ [2022, 10, [30, 31, 25, 26, 27, 28, 29]],
573
+ [2023, 10, [29, 30, 31, 25, 26, 27, 28]],
574
+ [2018, 10, [28, 29, 30, 31, 25, 26, 27]],
575
+ [2024, 10, [27, 28, 29, 30, 31, 25, 26]],
576
+ [2025, 10, [26, 27, 28, 29, 30, 31, 25]],
577
+ [2026, 10, [25, 26, 27, 28, 29, 30, 31]],
578
+
579
+ # All possible months.
580
+ [2020, 1, [26]],
581
+ [2020, 2, [23]],
582
+ [2020, 3, [29]],
583
+ [2020, 4, [26]],
584
+ [2020, 5, [31]],
585
+ [2020, 6, [28]],
586
+ [2020, 7, [26]],
587
+ [2020, 8, [30]],
588
+ [2020, 9, [27]],
589
+ [2020, 10, [25]],
590
+ [2020, 11, [29]],
591
+ [2020, 12, [27]]
592
+ ].each do |(year, month, days)|
593
+ days.each_with_index do |expected_day, day_of_week|
594
+ define_method "test_month_#{month}_day_of_week_#{day_of_week}_year_#{year}" do
595
+ rule = LastDayOfMonthTransitionRule.new(month, day_of_week, 7200)
596
+ offset = TimezoneOffset.new(0, 0, 'TEST')
597
+
598
+ result = rule.at(offset, year)
599
+
600
+ assert_kind_of(TimeOrDateTime, result)
601
+ assert_equal(Time.utc(year, month, expected_day, 2, 0, 0), result.to_time)
602
+ end
603
+ end
604
+ end
605
+
606
+ [[2020, 6, 29], [2021, 0, 28], [2000, 2, 29], [2100, 0, 28]].each do |(year, day_of_week, expected_day)|
607
+ define_method "test_#{expected_day == 29 ? '' : 'non_'}leap_year_#{year}" do
608
+ rule = LastDayOfMonthTransitionRule.new(2, day_of_week, 7200)
609
+ offset = TimezoneOffset.new(0, 0, 'TEST')
610
+
611
+ result = rule.at(offset, year)
612
+
613
+ assert_kind_of(TimeOrDateTime, result)
614
+ assert_equal(DateTime.new(year, 2, expected_day, 2, 0, 0), result.to_datetime)
615
+ end
616
+ end
617
+
618
+ [:==, :eql?].each do |method|
619
+ [
620
+ [1, 0, 0],
621
+ [1, 0, 1],
622
+ [1, 1, 0],
623
+ [2, 0, 0]
624
+ ].each do |(month, day_of_week, transition_at)|
625
+ define_method "test_equal_for_month_#{month}_day_of_week_#{day_of_week}_and_transition_at_#{transition_at}_with_#{method}" do
626
+ rule1 = LastDayOfMonthTransitionRule.new(month, day_of_week, transition_at)
627
+ rule2 = LastDayOfMonthTransitionRule.new(month, day_of_week, transition_at)
628
+ assert_equal(true, rule1.send(method, rule2))
629
+ assert_equal(true, rule2.send(method, rule1))
630
+ end
631
+ end
632
+
633
+ define_method "test_not_equal_to_day_of_month_with_#{method}" do
634
+ rule1 = LastDayOfMonthTransitionRule.new(1, 0, 0)
635
+ rule2 = DayOfMonthTransitionRule.new(1, 1, 0, 0)
636
+ assert_equal(false, rule1.send(method, rule2))
637
+ assert_equal(false, rule2.send(method, rule1))
638
+ end
639
+ end
640
+
641
+ [
642
+ [1, 0, 0],
643
+ [1, 0, 1],
644
+ [1, 1, 0],
645
+ [2, 0, 0]
646
+ ].each do |(month, day_of_week, transition_at)|
647
+ define_method "test_hash_for_month_#{month}_day_of_week_#{day_of_week}_and_transition_at_#{transition_at}" do
648
+ rule = LastDayOfMonthTransitionRule.new(month, day_of_week, transition_at)
649
+ expected = [month, day_of_week, transition_at].hash
650
+ assert_equal(expected, rule.hash)
651
+ end
652
+ end
653
+
654
+ protected
655
+
656
+ def create_with_transition_at(transition_at)
657
+ LastDayOfMonthTransitionRule.new(1, 0, transition_at)
658
+ end
659
+
660
+ def create_with_month_and_day_of_week(month, day_of_week)
661
+ LastDayOfMonthTransitionRule.new(month, day_of_week)
662
+ end
663
+ end