Skip to content

Commit 4ba3297

Browse files
committed
Realign recurrence rules to their time parts
When time parts (hour_of_day, minute_of_hour, second_of_minute) are specified on recurrence rules, the rules would jump over valid occurrences unless they were realigned.
1 parent 70d2e59 commit 4ba3297

File tree

10 files changed

+86
-8
lines changed

10 files changed

+86
-8
lines changed

lib/ice_cube/rules/weekly_rule.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ def realign(step_time, start_time)
2828
time = TimeUtil::TimeWrapper.new(start_time)
2929
offset = wday_offset(step_time, start_time)
3030
time.add(:day, offset)
31-
time.to_time
31+
super step_time, time.to_time
3232
end
3333

3434
# Calculate how many days to the first wday validation in the correct

lib/ice_cube/time_util.rb

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -311,7 +311,17 @@ def clear_below(type)
311311
end
312312
end
313313

314-
private
314+
def hour=(value)
315+
@time += (value * ONE_HOUR) - (@time.hour * ONE_HOUR)
316+
end
317+
318+
def min=(value)
319+
@time += (value * ONE_MINUTE) - (@time.min * ONE_MINUTE)
320+
end
321+
322+
def sec=(value)
323+
@time += (value) - (@time.sec)
324+
end
315325

316326
def clear_sec
317327
@time.sec > 0 ? @time -= @time.sec : @time

lib/ice_cube/validations/hour_of_day.rb

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,22 @@ def hour_of_day(*hours)
1717
self
1818
end
1919

20+
def realign(opening_time, start_time)
21+
return super unless validations[:hour_of_day]
22+
freq = base_interval_validation.interval
23+
24+
first_hour = Array(validations[:hour_of_day]).min_by(&:value)
25+
time = TimeUtil::TimeWrapper.new(start_time, false)
26+
if freq > 1
27+
offset = first_hour.validate(opening_time, start_time)
28+
time.add(:hour, offset - freq)
29+
else
30+
time.hour = first_hour.value
31+
end
32+
33+
super opening_time, time.to_time
34+
end
35+
2036
class Validation < Validations::FixedValue
2137

2238
attr_reader :hour

lib/ice_cube/validations/minute_of_hour.rb

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,15 @@ def minute_of_hour(*minutes)
1616
self
1717
end
1818

19+
def realign(opening_time, start_time)
20+
return super unless validations[:minute_of_hour]
21+
22+
first_minute = validations[:minute_of_hour].min_by(&:value)
23+
time = TimeUtil::TimeWrapper.new(start_time, false)
24+
time.min = first_minute.value
25+
super opening_time, time.to_time
26+
end
27+
1928
class Validation < Validations::FixedValue
2029

2130
attr_reader :minute

lib/ice_cube/validations/second_of_minute.rb

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,15 @@ def second_of_minute(*seconds)
1616
self
1717
end
1818

19+
def realign(opening_time, start_time)
20+
return super unless validations[:second_of_minute]
21+
22+
first_second = Array(validations[:second_of_minute]).min_by(&:value)
23+
time = TimeUtil::TimeWrapper.new(start_time, false)
24+
time.sec = first_second.value
25+
super opening_time, time.to_time
26+
end
27+
1928
class Validation < Validations::FixedValue
2029

2130
attr_reader :second

spec/examples/hourly_rule_spec.rb

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,22 @@ module IceCube
6969
expect(dates).to eq([DAY, DAY + 3 * ONE_HOUR, DAY + 6 * ONE_HOUR])
7070
end
7171

72+
it "should realign to the first hour_of_day with interval" do
73+
t0 = Time.utc(2017, 1, 1, 20, 30, 40)
74+
schedule = IceCube::Schedule.new(t0)
75+
schedule.rrule IceCube::Rule.hourly(5).hour_of_day(5, 10)
76+
77+
expect(schedule.first(2)).to eq [t0 + 9*ONE_HOUR, t0 + 14*ONE_HOUR]
78+
end
79+
80+
it "should realign to the first hour_of_day without interval" do
81+
t0 = Time.utc(2017, 1, 1, 20, 30, 40)
82+
schedule = IceCube::Schedule.new(t0)
83+
schedule.rrule IceCube::Rule.hourly.hour_of_day(5, 10)
84+
85+
expect(schedule.first(2)).to eq [t0 + 9*ONE_HOUR, t0 + 14*ONE_HOUR]
86+
end
87+
7288
it "raises errors for misaligned interval and hour_of_day values" do
7389
expect {
7490
IceCube::Rule.hourly(10).hour_of_day(3, 6)

spec/examples/minutely_rule_spec.rb

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,14 @@
7272
expect(schedule.next_occurrence(Time.new(2013, 11, 1, 1, 4, 0))).to eq(Time.new(2013, 11, 1, 1, 8, 0))
7373
end
7474

75+
it "should realign to the first minute_of_hour" do
76+
t0 = Time.utc(2017, 1, 1, 20, 30, 40)
77+
schedule = IceCube::Schedule.new(t0)
78+
schedule.rrule IceCube::Rule.minutely(10).minute_of_hour(5, 15)
79+
80+
expect(schedule.first(2)).to eq [t0 + 35*ONE_MINUTE, t0 + 45*ONE_MINUTE]
81+
end
82+
7583
it "raises errors for misaligned interval and minute_of_hour values" do
7684
expect {
7785
IceCube::Rule.minutely(10).minute_of_hour(3, 6)

spec/examples/regression_spec.rb

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -36,12 +36,6 @@ module IceCube
3636
expect(schedule.occurrences(Date.today >> 12)).to be_an Array
3737
end
3838

39-
it 'should not regress [#40]' do
40-
schedule = Schedule.new(Time.local(2011, 11, 16, 11, 31, 58), :duration => 3600)
41-
schedule.add_recurrence_rule Rule.minutely(60).day(4).hour_of_day(14, 15, 16).minute_of_hour(0)
42-
expect(schedule.occurring_at?(Time.local(2011, 11, 17, 15, 30))).to be_falsey
43-
end
44-
4539
it 'should not choke on parsing [#26]' do
4640
schedule = Schedule.new(Time.local(2011, 8, 9, 14, 52, 14))
4741
schedule.rrule Rule.weekly(1).day(1, 2, 3, 4, 5)

spec/examples/secondly_rule_spec.rb

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,14 @@ module IceCube
2424
}.to raise_error(ArgumentError, "'invalid' is not a valid input for interval. Please pass a postive integer.")
2525
end
2626

27+
it "should realign to the first second_of_minute" do
28+
t0 = Time.utc(2017, 1, 1, 20, 30, 40)
29+
schedule = IceCube::Schedule.new(t0)
30+
schedule.rrule IceCube::Rule.secondly(10).second_of_minute(5, 15)
31+
32+
expect(schedule.first(2)).to eq [t0 + 25*ONE_SECOND, t0 + 35*ONE_SECOND]
33+
end
34+
2735
it "raises errors for misaligned interval and minute_of_hour values" do
2836
expect {
2937
IceCube::Rule.secondly(10).second_of_minute(3, 6)

spec/examples/weekly_rule_spec.rb

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -309,6 +309,14 @@ module IceCube
309309
end
310310
end
311311

312+
it "should align next_occurrence with the earliest hour validation" do
313+
t0 = Time.utc(2017, 7, 28, 20, 30, 40)
314+
schedule = IceCube::Schedule.new(t0)
315+
schedule.add_recurrence_rule IceCube::Rule.weekly.day(:saturday).hour_of_day(19).minute_of_hour(29).second_of_minute(39)
316+
317+
expect(schedule.next_occurrence(t0)).to eq Time.utc(2017, 7, 29, 19, 29, 39)
318+
end
319+
312320
describe "using occurs_between with a biweekly schedule" do
313321
[[0, 1, 2], [0, 6, 1], [5, 1, 6], [6, 5, 7]].each do |wday, offset, lead|
314322
start_time = Time.utc(2014, 1, 5, 9, 0, 0)

0 commit comments

Comments
 (0)