Skip to content

Commit 8b72469

Browse files
committed
Realign to schedule start time before enumerating
1 parent 123f879 commit 8b72469

File tree

3 files changed

+64
-27
lines changed

3 files changed

+64
-27
lines changed

lib/ice_cube/schedule.rb

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -403,7 +403,7 @@ def enumerate_occurrences(opening_time, closing_time = nil, &block)
403403
opening_time = start_time if opening_time < start_time
404404
Enumerator.new do |yielder|
405405
reset
406-
t1 = full_required? ? start_time : opening_time
406+
t1 = full_required? ? start_time : realign(opening_time)
407407
loop do
408408
break unless (t0 = next_time(t1, closing_time))
409409
break if closing_time && t0 > closing_time
@@ -488,6 +488,28 @@ def wind_back_dst
488488
end
489489
end
490490

491+
# If any rule has validations for values within the period, (overriding the
492+
# interval from start time, e.g. `day[_of_week]`), and the opening time is
493+
# offset from the interval multiplier such that it might miss the first
494+
# correct occurrence (e.g. repeat is every N weeks, but selecting from end
495+
# of week N-1, the first jump would go to end of week N and miss any
496+
# earlier validations in the week). This realigns the opening time to
497+
# the start of the interval's correct period (e.g. move to start of week N)
498+
# TODO: check if this is needed for validations other than `:wday`
499+
#
500+
def realign(opening_time)
501+
time = TimeUtil::TimeWrapper.new(opening_time)
502+
recurrence_rules.each do |rule|
503+
wday_validations = rule.other_interval_validations.select { |v| v.type == :wday } or next
504+
interval = rule.base_interval_validation.validate(opening_time, self).to_i
505+
offset = wday_validations
506+
.map { |v| v.validate(opening_time, self).to_i }
507+
.reduce(0) { |least, i| i > 0 && i <= interval && (i < least || least == 0) ? i : least }
508+
time.add(rule.base_interval_type, 7 - time.to_time.wday) if offset > 0
509+
end
510+
time.to_time
511+
end
512+
491513
end
492514

493515
end

lib/ice_cube/validated_rule.rb

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,24 @@ class ValidatedRule < Rule
3030
:interval
3131
]
3232

33+
attr_reader :validations
34+
3335
def initialize(interval = 1, *)
3436
@validations = Hash.new
3537
end
3638

39+
def base_interval_validation
40+
@validations[:interval].first
41+
end
42+
43+
def other_interval_validations
44+
Array(@validations[base_interval_validation.type])
45+
end
46+
47+
def base_interval_type
48+
base_interval_validation.type
49+
end
50+
3751
# Compute the next time after (or including) the specified time in respect
3852
# to the given schedule
3953
def next_time(time, schedule, closing_time)

spec/examples/regression_spec.rb

Lines changed: 27 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -226,36 +226,37 @@ module IceCube
226226
time.usec.should == 0
227227
end
228228

229-
describe 'weekly schedule using occurs_between' do
230-
let(:schedule) {Schedule.new('2014-01-08'.to_date)}
231-
let(:expected_occurrence) {'2014-07-24'.to_date.to_time}
232-
before :each do
233-
schedule.add_recurrence_rule rule
234-
end
235-
context "with interval" do
236-
let(:rule) {Rule.weekly(2).day([4,5,6])}
237-
it "should include the expected date [#241]" do
238-
expect(schedule.occurs_on?(expected_occurrence)).to be_true
239-
expect(schedule.occurrences_between(expected_occurrence - 7.days, expected_occurrence + 7.days)).to include(expected_occurrence)
240-
expect(schedule.occurrences_between(expected_occurrence - 1.day, expected_occurrence + 1.day)).to include(expected_occurrence)
241-
expect(schedule.occurrences_between(expected_occurrence - 6.days, expected_occurrence + 6.days)).to include(expected_occurrence)
242-
expect(schedule.occurrences_between(expected_occurrence - 5.days, expected_occurrence + 5.days)).to include(expected_occurrence)
243-
end
244-
end
245-
context "without interval" do
246-
let(:rule) {Rule.weekly(1).day([4,5,6])}
247-
it "should include the expected date" do
248-
expect(schedule.occurs_on?(expected_occurrence)).to be_true
249-
expect(schedule.occurrences_between(expected_occurrence - 7.days, expected_occurrence + 7.days)).to include(expected_occurrence)
250-
expect(schedule.occurrences_between(expected_occurrence - 1.day, expected_occurrence + 1.day)).to include(expected_occurrence)
251-
expect(schedule.occurrences_between(expected_occurrence - 6.days, expected_occurrence + 6.days)).to include(expected_occurrence)
252-
expect(schedule.occurrences_between(expected_occurrence - 5.days, expected_occurrence + 5.days)).to include(expected_occurrence)
253-
end
254-
end
229+
end
230+
end
255231

232+
describe 'weekly schedule using occurs_between' do
233+
let(:schedule) {Schedule.new('2014-01-08'.to_date)}
234+
let(:expected_occurrence) {'2014-07-24'.to_date.to_time}
235+
before :each do
236+
schedule.add_recurrence_rule rule
237+
end
238+
239+
context "with interval" do
240+
let(:rule) {Rule.weekly(2).day([4,5,6])}
241+
it "should include the expected date [#241]" do
242+
expect(schedule.occurrences_between(expected_occurrence - 7.days, expected_occurrence + 7.days)).to include(expected_occurrence)
243+
expect(schedule.occurrences_between(expected_occurrence - 1.day, expected_occurrence + 1.day)).to include(expected_occurrence)
244+
expect(schedule.occurrences_between(expected_occurrence - 6.days, expected_occurrence + 6.days)).to include(expected_occurrence)
245+
expect(schedule.occurrences_between(expected_occurrence - 5.days, expected_occurrence + 5.days)).to include(expected_occurrence)
256246
end
247+
end
257248

249+
context "without interval" do
250+
let(:rule) {Rule.weekly(1).day([4,5,6])}
251+
it "should include the expected date" do
252+
expect(schedule.occurs_on?(expected_occurrence)).to be_true
253+
expect(schedule.occurrences_between(expected_occurrence - 7.days, expected_occurrence + 7.days)).to include(expected_occurrence)
254+
expect(schedule.occurrences_between(expected_occurrence - 1.day, expected_occurrence + 1.day)).to include(expected_occurrence)
255+
expect(schedule.occurrences_between(expected_occurrence - 6.days, expected_occurrence + 6.days)).to include(expected_occurrence)
256+
expect(schedule.occurrences_between(expected_occurrence - 5.days, expected_occurrence + 5.days)).to include(expected_occurrence)
257+
end
258258
end
259+
259260
end
260261

261262
end

0 commit comments

Comments
 (0)