tzinfo 1.2.7 → 1.2.8

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 (54) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +0 -0
  4. data/CHANGES.md +11 -0
  5. data/README.md +1 -1
  6. data/lib/tzinfo.rb +3 -0
  7. data/lib/tzinfo/annual_rules.rb +51 -0
  8. data/lib/tzinfo/posix_time_zone_parser.rb +136 -0
  9. data/lib/tzinfo/time_or_datetime.rb +11 -0
  10. data/lib/tzinfo/transition_rule.rb +325 -0
  11. data/lib/tzinfo/zoneinfo_data_source.rb +2 -1
  12. data/lib/tzinfo/zoneinfo_timezone_info.rb +255 -40
  13. data/test/tc_annual_rules.rb +95 -0
  14. data/test/tc_posix_time_zone_parser.rb +261 -0
  15. data/test/tc_time_or_datetime.rb +14 -0
  16. data/test/tc_transition_rule.rb +663 -0
  17. data/test/tc_zoneinfo_timezone_info.rb +952 -113
  18. data/test/tzinfo-data/tzinfo/data/definitions/America/Argentina/Buenos_Aires.rb +5 -5
  19. data/test/tzinfo-data/tzinfo/data/definitions/America/New_York.rb +13 -1
  20. data/test/tzinfo-data/tzinfo/data/definitions/Australia/Melbourne.rb +13 -1
  21. data/test/tzinfo-data/tzinfo/data/definitions/EST.rb +1 -1
  22. data/test/tzinfo-data/tzinfo/data/definitions/Etc/GMT__m__1.rb +2 -2
  23. data/test/tzinfo-data/tzinfo/data/definitions/Etc/GMT__p__1.rb +2 -2
  24. data/test/tzinfo-data/tzinfo/data/definitions/Etc/UTC.rb +1 -1
  25. data/test/tzinfo-data/tzinfo/data/definitions/Europe/Amsterdam.rb +15 -3
  26. data/test/tzinfo-data/tzinfo/data/definitions/Europe/Andorra.rb +13 -1
  27. data/test/tzinfo-data/tzinfo/data/definitions/Europe/London.rb +13 -1
  28. data/test/tzinfo-data/tzinfo/data/definitions/Europe/Paris.rb +15 -3
  29. data/test/tzinfo-data/tzinfo/data/definitions/Europe/Prague.rb +19 -4
  30. data/test/tzinfo-data/tzinfo/data/definitions/UTC.rb +1 -1
  31. data/test/tzinfo-data/tzinfo/data/indexes/countries.rb +197 -184
  32. data/test/tzinfo-data/tzinfo/data/indexes/timezones.rb +60 -47
  33. data/test/tzinfo-data/tzinfo/data/version.rb +9 -3
  34. data/test/zoneinfo/America/Argentina/Buenos_Aires +0 -0
  35. data/test/zoneinfo/America/New_York +0 -0
  36. data/test/zoneinfo/Australia/Melbourne +0 -0
  37. data/test/zoneinfo/EST +0 -0
  38. data/test/zoneinfo/Etc/UTC +0 -0
  39. data/test/zoneinfo/Europe/Amsterdam +0 -0
  40. data/test/zoneinfo/Europe/Andorra +0 -0
  41. data/test/zoneinfo/Europe/London +0 -0
  42. data/test/zoneinfo/Europe/Paris +0 -0
  43. data/test/zoneinfo/Europe/Prague +0 -0
  44. data/test/zoneinfo/Factory +0 -0
  45. data/test/zoneinfo/iso3166.tab +13 -14
  46. data/test/zoneinfo/leapseconds +38 -21
  47. data/test/zoneinfo/posix/Europe/London +0 -0
  48. data/test/zoneinfo/posixrules +0 -0
  49. data/test/zoneinfo/right/Europe/London +0 -0
  50. data/test/zoneinfo/zone.tab +172 -159
  51. data/test/zoneinfo/zone1970.tab +185 -170
  52. data/tzinfo.gemspec +1 -1
  53. metadata +9 -3
  54. metadata.gz.sig +0 -0
@@ -0,0 +1,261 @@
1
+ require File.join(File.expand_path(File.dirname(__FILE__)), 'test_utils')
2
+
3
+ include TZInfo
4
+
5
+ class TCPosixTimeZoneParser < Minitest::Test
6
+ HOUR = 3600
7
+ MINUTE = 60
8
+
9
+ class << self
10
+ private
11
+
12
+ def append_time_to_rule(day_rule, time)
13
+ time ? "#{day_rule}/#{time}" : day_rule
14
+ end
15
+
16
+ def define_invalid_dst_rule_tests(type, rule)
17
+ define_method "test_#{type}_dst_start_rule_for_invalid_#{rule}" do
18
+ tz_string = "STD-1DST,#{rule},300"
19
+ assert_raises(InvalidPosixTimeZone) { @parser.parse(tz_string) }
20
+ end
21
+
22
+ define_method "test_#{type}_dst_end_rule_for_invalid_#{rule}" do
23
+ tz_string = "STD-1DST,60,#{rule}"
24
+ assert_raises(InvalidPosixTimeZone) { @parser.parse(tz_string) }
25
+ end
26
+ end
27
+ end
28
+
29
+ def setup
30
+ @parser = PosixTimeZoneParser.new
31
+ end
32
+
33
+ def test_empty_rule_returns_nil
34
+ result = @parser.parse('')
35
+ assert_nil(result)
36
+ end
37
+
38
+ ABBREVIATIONS_WITH_OFFSETS = [
39
+ ['UTC0', :UTC, 0],
40
+ ['U0', :U, 0],
41
+ ['West1', :West, -HOUR],
42
+ ['East-1', :East, HOUR],
43
+ ['<-05>5', :'-05', -5 * HOUR],
44
+ ['<+12>-12', :'+12', 12 * HOUR],
45
+ ['HMM2:30', :HMM, -(2 * HOUR + 30 * MINUTE)],
46
+ ['HHMM02:30', :HHMM, -(2 * HOUR + 30 * MINUTE)],
47
+ ['HHMM+02:30', :HHMM, -(2 * HOUR + 30 * MINUTE)],
48
+ ['HHMSS-12:5:50', :HHMSS, 12 * HOUR + 5 * MINUTE + 50],
49
+ ['HHMMSS-12:05:50', :HHMMSS, 12 * HOUR + 5 * MINUTE + 50],
50
+ ['HHMMS-12:05:7', :HHMMS, 12 * HOUR + 5 * MINUTE + 7]
51
+ ]
52
+
53
+ ABBREVIATIONS_WITH_OFFSETS.each do |(tz_string, expected_abbrev, expected_base_offset)|
54
+ define_method "test_std_only_returns_std_offset_#{tz_string}" do
55
+ result = @parser.parse(tz_string)
56
+ expected = TimezoneOffset.new(expected_base_offset, 0, expected_abbrev)
57
+ assert_equal(expected, result)
58
+ end
59
+ end
60
+
61
+ ABBREVIATIONS_WITH_OFFSETS.each do |(abbrev_and_offset, expected_abbrev, expected_base_offset)|
62
+ define_method "test_std_offset_#{abbrev_and_offset}" do
63
+ result = @parser.parse(abbrev_and_offset + 'DST,60,300')
64
+ expected_std_offset = TimezoneOffset.new(expected_base_offset, 0, expected_abbrev)
65
+ assert_equal(expected_std_offset, result.std_offset)
66
+ end
67
+ end
68
+
69
+ [
70
+ ['Zero0One-1', :One, 0, HOUR],
71
+ ['Zero0One', :One, 0, HOUR],
72
+ ['Z0O', :O, 0, HOUR],
73
+ ['West1WestS0', :WestS, -HOUR, HOUR],
74
+ ['West1WestS', :WestS, -HOUR, HOUR],
75
+ ['East-1EastS-2', :EastS, HOUR, HOUR],
76
+ ['East-1EastS', :EastS, HOUR, HOUR],
77
+ ['Neg2NegS3', :NegS, -2 * HOUR, -HOUR],
78
+ ['<-05>5<-04>4', :'-04', -5 * HOUR, HOUR],
79
+ ['STD5<-04>4', :'-04', -5 * HOUR, HOUR],
80
+ ['<+12>-12<+13>-13', :'+13', 12 * HOUR, HOUR],
81
+ ['STD-12<+13>-13', :'+13', 12 * HOUR, HOUR],
82
+ ['HMM2:30SHMM1:15', :SHMM, -(2 * HOUR + 30 * MINUTE), HOUR + 15 * MINUTE],
83
+ ['HHMM02:30SHHMM01:15', :SHHMM, -(2 * HOUR + 30 * MINUTE), HOUR + 15 * MINUTE],
84
+ ['HHMM+02:30SHHMM+01:15', :SHHMM, -(2 * HOUR + 30 * MINUTE), HOUR + 15 * MINUTE],
85
+ ['HHMSS-12:5:50SHHMSS-13:4:30', :SHHMSS, 12 * HOUR + 5 * MINUTE + 50, 58 * MINUTE + 40],
86
+ ['HHMMSS-12:05:50SHHMMSS-13:04:30', :SHHMMSS, 12 * HOUR + 5 * MINUTE + 50, 58 * MINUTE + 40],
87
+ ['HHMMS-12:05:7SHHMMSS-13:06:8', :SHHMMSS, 12 * HOUR + 5 * MINUTE + 7, HOUR + MINUTE + 1],
88
+ ].each do |(abbrevs_and_offsets, expected_abbrev, expected_base_offset, expected_std_offset)|
89
+ define_method "test_dst_offset_#{abbrevs_and_offsets}" do
90
+ result = @parser.parse(abbrevs_and_offsets + ',60,300')
91
+ expected_dst_offset = TimezoneOffset.new(expected_base_offset, expected_std_offset, expected_abbrev)
92
+ assert_equal(expected_dst_offset, result.dst_offset)
93
+ end
94
+ end
95
+
96
+ ['<M-1>01:-1', '<M60>01:60', '<S-1>01:00:-1', '<S60>01:00:60'].each do |abbrev_and_offset|
97
+ ['', 'DST,60,300'].each do |dst_suffix|
98
+ tz_string = abbrev_and_offset + dst_suffix
99
+ define_method "test_std_offset_invalid_#{tz_string}" do
100
+ assert_raises(InvalidPosixTimeZone) { @parser.parse(tz_string) }
101
+ end
102
+ end
103
+
104
+ tz_string = "STD1#{abbrev_and_offset},60,300"
105
+ define_method "test_dst_offset_invalid_#{tz_string}" do
106
+ assert_raises(InvalidPosixTimeZone) { @parser.parse(tz_string) }
107
+ end
108
+ end
109
+
110
+ [
111
+ [nil, 2 * HOUR],
112
+ ['2', 2 * HOUR],
113
+ ['+2', 2 * HOUR],
114
+ ['-2', -2 * HOUR],
115
+ ['2:3:4', 2 * HOUR + 3 * MINUTE + 4],
116
+ ['02:03:04', 2 * HOUR + 3 * MINUTE + 4],
117
+ ['-2:3:4', -2 * HOUR + 3 * MINUTE + 4], # 22:03:04 on the day prior to the one specified
118
+ ['-02:03:04', -2 * HOUR + 3 * MINUTE + 4],
119
+ ['167', 167 * HOUR],
120
+ ['-167', -167 * HOUR]
121
+ ].each do |(time, expected_offset_from_midnight)|
122
+ [
123
+ ['J1', 1],
124
+ ['J365', 365]
125
+ ].each do |(julian_day_rule, expected_julian_day)|
126
+ rule = append_time_to_rule(julian_day_rule, time)
127
+
128
+ define_method "test_julian_day_dst_start_rule_for_#{rule}" do
129
+ result = @parser.parse("STD-1DST,#{rule},300")
130
+ expected_dst_start_rule = JulianDayOfYearTransitionRule.new(expected_julian_day, expected_offset_from_midnight)
131
+ assert_equal(expected_dst_start_rule, result.dst_start_rule)
132
+ end
133
+
134
+ define_method "test_julian_day_dst_end_rule_for_#{rule}" do
135
+ result = @parser.parse("STD-1DST,60,#{rule}")
136
+ expected_dst_end_rule = JulianDayOfYearTransitionRule.new(expected_julian_day, expected_offset_from_midnight)
137
+ assert_equal(expected_dst_end_rule, result.dst_end_rule)
138
+ end
139
+ end
140
+
141
+ [
142
+ ['0', 0],
143
+ ['365', 365]
144
+ ].each do |(absolute_day_rule, expected_day)|
145
+ rule = append_time_to_rule(absolute_day_rule, time)
146
+
147
+ define_method "test_absolute_day_dst_start_rule_for_#{rule}" do
148
+ result = @parser.parse("STD-1DST,#{rule},J300")
149
+ expected_dst_start_rule = AbsoluteDayOfYearTransitionRule.new(expected_day, expected_offset_from_midnight)
150
+ assert_equal(expected_dst_start_rule, result.dst_start_rule)
151
+ end
152
+
153
+ define_method "test_absolute_day_dst_end_rule_for_#{rule}" do
154
+ result = @parser.parse("STD-1DST,J60,#{rule}")
155
+ expected_dst_end_rule = AbsoluteDayOfYearTransitionRule.new(expected_day, expected_offset_from_midnight)
156
+ assert_equal(expected_dst_end_rule, result.dst_end_rule)
157
+ end
158
+ end
159
+
160
+ [
161
+ ['M1.1.0', 1, 1, 0],
162
+ ['M12.4.6', 12, 4, 6]
163
+ ].each do |(day_of_month_rule, expected_month, expected_week, expected_day_of_week)|
164
+ rule = append_time_to_rule(day_of_month_rule, time)
165
+
166
+ define_method "test_day_of_month_dst_start_rule_for_#{rule}" do
167
+ result = @parser.parse("STD-1DST,#{rule},300")
168
+ expected_dst_start_rule = DayOfMonthTransitionRule.new(expected_month, expected_week, expected_day_of_week, expected_offset_from_midnight)
169
+ assert_equal(expected_dst_start_rule, result.dst_start_rule)
170
+ end
171
+
172
+ define_method "test_day_of_month_dst_end_rule_for_#{rule}" do
173
+ result = @parser.parse("STD-1DST,60,#{rule}")
174
+ expected_dst_end_rule = DayOfMonthTransitionRule.new(expected_month, expected_week, expected_day_of_week, expected_offset_from_midnight)
175
+ assert_equal(expected_dst_end_rule, result.dst_end_rule)
176
+ end
177
+ end
178
+
179
+ [
180
+ ['M1.5.0', 1, 0],
181
+ ['M12.5.6', 12, 6]
182
+ ].each do |(last_day_of_month_rule, expected_month, expected_day_of_week)|
183
+ rule = append_time_to_rule(last_day_of_month_rule, time)
184
+
185
+ define_method "test_last_day_of_month_dst_start_rule_for_#{rule}" do
186
+ result = @parser.parse("STD-1DST,#{rule},300")
187
+ expected_dst_start_rule = LastDayOfMonthTransitionRule.new(expected_month, expected_day_of_week, expected_offset_from_midnight)
188
+ assert_equal(expected_dst_start_rule, result.dst_start_rule)
189
+ end
190
+
191
+ define_method "test_last_day_of_month_dst_end_rule_for_#{rule}" do
192
+ result = @parser.parse("STD-1DST,60,#{rule}")
193
+ expected_dst_end_rule = LastDayOfMonthTransitionRule.new(expected_month, expected_day_of_week, expected_offset_from_midnight)
194
+ assert_equal(expected_dst_end_rule, result.dst_end_rule)
195
+ end
196
+ end
197
+ end
198
+
199
+ ['J0', 'J366'].each do |julian_day_rule|
200
+ define_invalid_dst_rule_tests('julian_day', julian_day_rule)
201
+ end
202
+
203
+ ['-1', '366'].each do |absolute_day_rule|
204
+ define_invalid_dst_rule_tests('absolute_day', absolute_day_rule)
205
+ end
206
+
207
+ ['M0,1,0', 'M13,1,0', 'M6,0,0', 'M6,6,0', 'M6,1,-1', 'M6,1,7'].each do |day_of_month_rule|
208
+ define_invalid_dst_rule_tests('day_of_month', day_of_month_rule)
209
+ end
210
+
211
+ ['M0,5,0', 'M13,5,0', 'M6,5,-1', 'M6,5,7'].each do |last_day_of_month_rule|
212
+ define_invalid_dst_rule_tests('last_day_of_month', last_day_of_month_rule)
213
+ end
214
+
215
+ def test_invalid_dst_start_rule
216
+ assert_raises(InvalidPosixTimeZone) { @parser.parse('STD1DST,X60,300') }
217
+ end
218
+
219
+ def test_invalid_dst_end_rule
220
+ assert_raises(InvalidPosixTimeZone) { @parser.parse('STD1DST,60,X300') }
221
+ end
222
+
223
+ [
224
+ ['STD5DST,0/0,J365/25', :DST, -5 * HOUR, HOUR],
225
+ ['STD-5DST,0/0,J365/25', :DST, 5 * HOUR, HOUR],
226
+ ['STD5DST3,0/0,J365/26', :DST, -5 * HOUR, 2 * HOUR],
227
+ ['STD5DST6,0/0,J365/23', :DST, -5 * HOUR, -HOUR],
228
+ ['STD5DST,J1/0,J365/25', :DST, -5 * HOUR, HOUR],
229
+ ['Winter5Summer,0/0,J365/25', :Summer, -5 * HOUR, HOUR],
230
+ ['<-05>5<-06>,0/0,J365/25', :'-06', -5 * HOUR, HOUR]
231
+ ].each do |(tz_string, expected_abbrev, expected_base_offset, expected_std_offset)|
232
+ define_method "test_dst_only_returns_continuous_offset_for_#{tz_string}" do
233
+ result = @parser.parse(tz_string)
234
+ expected = TimezoneOffset.new(expected_base_offset, expected_std_offset, expected_abbrev)
235
+ assert_equal(expected, result)
236
+ end
237
+ end
238
+
239
+ def test_parses_tainted_string_in_safe_mode_and_returns_untainted_abbreviations
240
+ safe_test(:unavailable => :skip) do
241
+ result = @parser.parse('STD1DST,60,300'.dup.taint)
242
+
243
+ assert_equal(:STD, result.std_offset.abbreviation)
244
+ assert_equal(:DST, result.dst_offset.abbreviation)
245
+ end
246
+ end
247
+
248
+ ['STD1', 'STD1DST,60,300'].each do |tz_string|
249
+ tz_string += "-"
250
+ define_method "test_content_after_end_for_#{tz_string}" do
251
+ error = assert_raises(InvalidPosixTimeZone) { @parser.parse(tz_string) }
252
+ assert_equal("Expected the end of a POSIX-style time zone string but found '-'.", error.message)
253
+ end
254
+ end
255
+
256
+ ['X', 0].each do |invalid_tz_string|
257
+ define_method "test_invalid_tz_string_#{invalid_tz_string}" do
258
+ assert_raises(InvalidPosixTimeZone) { @parser.parse(invalid_tz_string) }
259
+ end
260
+ end
261
+ end
@@ -249,6 +249,20 @@ class TCTimeOrDateTime < Minitest::Test
249
249
  assert_equal(24, TimeOrDateTime.new(DateTime.new(2006, 3, 24, 15, 32, 3)).day)
250
250
  assert_equal(24, TimeOrDateTime.new(1143214323).day)
251
251
  end
252
+
253
+ 19.upto(25).each_with_index do |mday,i|
254
+ define_method "test_wday_time_#{i}" do
255
+ assert_equal(i, TimeOrDateTime.new(Time.utc(2006, 3, mday)).wday)
256
+ end
257
+
258
+ define_method "test_wday_datetime_#{i}" do
259
+ assert_equal(i, TimeOrDateTime.new(DateTime.new(2006, 3, mday)).wday)
260
+ end
261
+
262
+ define_method "test_wday_timestamp_#{i}" do
263
+ assert_equal(i, TimeOrDateTime.new(Time.utc(2006, 3, mday).to_i).wday)
264
+ end
265
+ end
252
266
 
253
267
  def test_hour
254
268
  assert_equal(15, TimeOrDateTime.new(Time.utc(2006, 3, 24, 15, 32, 3)).hour)
@@ -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