Skip to content

Commit

Permalink
Realign recurrence rules to their time parts
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
avit committed Sep 18, 2017
1 parent 70d2e59 commit 4ba3297
Show file tree
Hide file tree
Showing 10 changed files with 86 additions and 8 deletions.
2 changes: 1 addition & 1 deletion lib/ice_cube/rules/weekly_rule.rb
Expand Up @@ -28,7 +28,7 @@ def realign(step_time, start_time)
time = TimeUtil::TimeWrapper.new(start_time)
offset = wday_offset(step_time, start_time)
time.add(:day, offset)
time.to_time
super step_time, time.to_time
end

# Calculate how many days to the first wday validation in the correct
Expand Down
12 changes: 11 additions & 1 deletion lib/ice_cube/time_util.rb
Expand Up @@ -311,7 +311,17 @@ def clear_below(type)
end
end

private
def hour=(value)
@time += (value * ONE_HOUR) - (@time.hour * ONE_HOUR)
end

def min=(value)
@time += (value * ONE_MINUTE) - (@time.min * ONE_MINUTE)
end

def sec=(value)
@time += (value) - (@time.sec)
end

def clear_sec
@time.sec > 0 ? @time -= @time.sec : @time
Expand Down
16 changes: 16 additions & 0 deletions lib/ice_cube/validations/hour_of_day.rb
Expand Up @@ -17,6 +17,22 @@ def hour_of_day(*hours)
self
end

def realign(opening_time, start_time)
return super unless validations[:hour_of_day]
freq = base_interval_validation.interval

first_hour = Array(validations[:hour_of_day]).min_by(&:value)
time = TimeUtil::TimeWrapper.new(start_time, false)
if freq > 1
offset = first_hour.validate(opening_time, start_time)
time.add(:hour, offset - freq)
else
time.hour = first_hour.value
end

super opening_time, time.to_time
end

class Validation < Validations::FixedValue

attr_reader :hour
Expand Down
9 changes: 9 additions & 0 deletions lib/ice_cube/validations/minute_of_hour.rb
Expand Up @@ -16,6 +16,15 @@ def minute_of_hour(*minutes)
self
end

def realign(opening_time, start_time)
return super unless validations[:minute_of_hour]

first_minute = validations[:minute_of_hour].min_by(&:value)
time = TimeUtil::TimeWrapper.new(start_time, false)
time.min = first_minute.value
super opening_time, time.to_time
end

class Validation < Validations::FixedValue

attr_reader :minute
Expand Down
9 changes: 9 additions & 0 deletions lib/ice_cube/validations/second_of_minute.rb
Expand Up @@ -16,6 +16,15 @@ def second_of_minute(*seconds)
self
end

def realign(opening_time, start_time)
return super unless validations[:second_of_minute]

first_second = Array(validations[:second_of_minute]).min_by(&:value)
time = TimeUtil::TimeWrapper.new(start_time, false)
time.sec = first_second.value
super opening_time, time.to_time
end

class Validation < Validations::FixedValue

attr_reader :second
Expand Down
16 changes: 16 additions & 0 deletions spec/examples/hourly_rule_spec.rb
Expand Up @@ -69,6 +69,22 @@ module IceCube
expect(dates).to eq([DAY, DAY + 3 * ONE_HOUR, DAY + 6 * ONE_HOUR])
end

it "should realign to the first hour_of_day with interval" do
t0 = Time.utc(2017, 1, 1, 20, 30, 40)
schedule = IceCube::Schedule.new(t0)
schedule.rrule IceCube::Rule.hourly(5).hour_of_day(5, 10)

expect(schedule.first(2)).to eq [t0 + 9*ONE_HOUR, t0 + 14*ONE_HOUR]
end

it "should realign to the first hour_of_day without interval" do
t0 = Time.utc(2017, 1, 1, 20, 30, 40)
schedule = IceCube::Schedule.new(t0)
schedule.rrule IceCube::Rule.hourly.hour_of_day(5, 10)

expect(schedule.first(2)).to eq [t0 + 9*ONE_HOUR, t0 + 14*ONE_HOUR]
end

it "raises errors for misaligned interval and hour_of_day values" do
expect {
IceCube::Rule.hourly(10).hour_of_day(3, 6)
Expand Down
8 changes: 8 additions & 0 deletions spec/examples/minutely_rule_spec.rb
Expand Up @@ -72,6 +72,14 @@
expect(schedule.next_occurrence(Time.new(2013, 11, 1, 1, 4, 0))).to eq(Time.new(2013, 11, 1, 1, 8, 0))
end

it "should realign to the first minute_of_hour" do
t0 = Time.utc(2017, 1, 1, 20, 30, 40)
schedule = IceCube::Schedule.new(t0)
schedule.rrule IceCube::Rule.minutely(10).minute_of_hour(5, 15)

expect(schedule.first(2)).to eq [t0 + 35*ONE_MINUTE, t0 + 45*ONE_MINUTE]
end

it "raises errors for misaligned interval and minute_of_hour values" do
expect {
IceCube::Rule.minutely(10).minute_of_hour(3, 6)
Expand Down
6 changes: 0 additions & 6 deletions spec/examples/regression_spec.rb
Expand Up @@ -36,12 +36,6 @@ module IceCube
expect(schedule.occurrences(Date.today >> 12)).to be_an Array
end

it 'should not regress [#40]' do
schedule = Schedule.new(Time.local(2011, 11, 16, 11, 31, 58), :duration => 3600)
schedule.add_recurrence_rule Rule.minutely(60).day(4).hour_of_day(14, 15, 16).minute_of_hour(0)
expect(schedule.occurring_at?(Time.local(2011, 11, 17, 15, 30))).to be_falsey
end

it 'should not choke on parsing [#26]' do
schedule = Schedule.new(Time.local(2011, 8, 9, 14, 52, 14))
schedule.rrule Rule.weekly(1).day(1, 2, 3, 4, 5)
Expand Down
8 changes: 8 additions & 0 deletions spec/examples/secondly_rule_spec.rb
Expand Up @@ -24,6 +24,14 @@ module IceCube
}.to raise_error(ArgumentError, "'invalid' is not a valid input for interval. Please pass a postive integer.")
end

it "should realign to the first second_of_minute" do
t0 = Time.utc(2017, 1, 1, 20, 30, 40)
schedule = IceCube::Schedule.new(t0)
schedule.rrule IceCube::Rule.secondly(10).second_of_minute(5, 15)

expect(schedule.first(2)).to eq [t0 + 25*ONE_SECOND, t0 + 35*ONE_SECOND]
end

it "raises errors for misaligned interval and minute_of_hour values" do
expect {
IceCube::Rule.secondly(10).second_of_minute(3, 6)
Expand Down
8 changes: 8 additions & 0 deletions spec/examples/weekly_rule_spec.rb
Expand Up @@ -309,6 +309,14 @@ module IceCube
end
end

it "should align next_occurrence with the earliest hour validation" do
t0 = Time.utc(2017, 7, 28, 20, 30, 40)
schedule = IceCube::Schedule.new(t0)
schedule.add_recurrence_rule IceCube::Rule.weekly.day(:saturday).hour_of_day(19).minute_of_hour(29).second_of_minute(39)

expect(schedule.next_occurrence(t0)).to eq Time.utc(2017, 7, 29, 19, 29, 39)
end

describe "using occurs_between with a biweekly schedule" do
[[0, 1, 2], [0, 6, 1], [5, 1, 6], [6, 5, 7]].each do |wday, offset, lead|
start_time = Time.utc(2014, 1, 5, 9, 0, 0)
Expand Down

0 comments on commit 4ba3297

Please sign in to comment.