Skip to content

Commit 2028392

Browse files
committed
Fix whole-day skip with date inputs
Given a Date input, schedule will convert to the beginning of day based on the schedule time zone. This fixes jumping by + 1 day for the next occurrence, which should be + 1 second.
1 parent 5164d5b commit 2028392

File tree

4 files changed

+91
-16
lines changed

4 files changed

+91
-16
lines changed

lib/ice_cube/schedule.rb

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -167,26 +167,28 @@ def each_occurrence(&block)
167167

168168
# The next n occurrences after now
169169
def next_occurrences(num, from = nil)
170-
from ||= TimeUtil.now(@start_time)
170+
from = TimeUtil.match_zone(from, start_time) || TimeUtil.now(start_time)
171171
enumerate_occurrences(from + 1, nil).take(num)
172172
end
173173

174174
# The next occurrence after now (overridable)
175175
def next_occurrence(from = nil)
176-
from ||= TimeUtil.now(@start_time)
176+
from = TimeUtil.match_zone(from, start_time) || TimeUtil.now(start_time)
177177
enumerate_occurrences(from + 1, nil).next
178178
rescue StopIteration
179179
nil
180180
end
181181

182182
# The previous occurrence from a given time
183183
def previous_occurrence(from)
184+
from = TimeUtil.match_zone(from, start_time) or raise ArgumentError, "Time required, got #{time.inspect}"
184185
return nil if from <= start_time
185186
enumerate_occurrences(start_time, from - 1).to_a.last
186187
end
187188

188189
# The previous n occurrences before a given time
189190
def previous_occurrences(num, from)
191+
from = TimeUtil.match_zone(from, start_time) or raise ArgumentError, "Time required, got #{time.inspect}"
190192
return [] if from <= start_time
191193
a = enumerate_occurrences(start_time, from - 1).to_a
192194
a.size > num ? a[-1*num,a.size] : a
@@ -231,14 +233,15 @@ def occurring_between?(opening_time, closing_time)
231233

232234
# Return a boolean indicating if an occurrence falls on a certain date
233235
def occurs_on?(date)
234-
date = TimeUtil.ensure_date date
236+
date = TimeUtil.ensure_date(date)
235237
begin_time = TimeUtil.beginning_of_date(date, start_time)
236238
closing_time = TimeUtil.end_of_date(date, start_time)
237239
occurs_between?(begin_time, closing_time)
238240
end
239241

240242
# Determine if the schedule is occurring at a given time
241243
def occurring_at?(time)
244+
time = TimeUtil.match_zone(time, start_time) or raise ArgumentError, "Time required, got #{time.inspect}"
242245
if duration > 0
243246
return false if exception_time?(time)
244247
occurs_between?(time - duration + 1, time)

lib/ice_cube/time_util.rb

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -36,19 +36,20 @@ def self.build_in_zone(args, reference)
3636
end
3737
end
3838

39-
def self.match_zone(time, reference)
40-
return unless time = ensure_time(time)
41-
if reference.respond_to? :time_zone
42-
time.in_time_zone(reference.time_zone)
43-
else
44-
if reference.utc?
45-
time.utc
46-
elsif reference.zone
47-
time.getlocal
48-
else
49-
time.getlocal(reference.utc_offset)
50-
end
51-
end
39+
def self.match_zone(input_time, reference)
40+
return unless time = ensure_time(input_time)
41+
time = if reference.respond_to? :time_zone
42+
time.in_time_zone(reference.time_zone)
43+
else
44+
if reference.utc?
45+
time.utc
46+
elsif reference.zone
47+
time.getlocal
48+
else
49+
time.getlocal(reference.utc_offset)
50+
end
51+
end
52+
(Date === input_time) ? beginning_of_date(time, reference) : time
5253
end
5354

5455
# Ensure that this is either nil, or a time

spec/examples/schedule_spec.rb

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,14 @@
3333
lambda { schedule.next_occurrence }.should_not raise_error
3434
end
3535

36+
it "should not skip ahead a day when called with a date" do
37+
schedule = IceCube::Schedule.new(Time.utc(2014, 1, 1, 12, 34, 56)) do |s|
38+
s.add_recurrence_rule IceCube::Rule.hourly
39+
end
40+
next_hour = schedule.next_occurrence(Date.new(2014, 1, 2))
41+
expect( next_hour ).to eq Time.utc(2014, 1, 2, 00, 34 , 56)
42+
end
43+
3644
end
3745

3846
describe :duration do
@@ -54,6 +62,20 @@
5462

5563
end
5664

65+
describe :occurring_at? do
66+
67+
it "should not capture multiple days when called with a date" do
68+
schedule = IceCube::Schedule.new do |s|
69+
s.start_time = Time.utc(2013, 12, 31, 23, 59, 50)
70+
s.duration = 20
71+
s.add_recurrence_rule IceCube::Rule.daily(2)
72+
end
73+
expect( schedule.occurring_at?(Date.new(2014, 1, 1)) ).to eq true
74+
expect( schedule.occurring_at?(Date.new(2014, 1, 2)) ).to eq false
75+
end
76+
77+
end
78+
5779
describe :recurrence_times do
5880

5981
it 'should start empty' do
@@ -375,6 +397,15 @@
375397
schedule.next_occurrences(1).should be_empty
376398
end
377399

400+
it "should not skip ahead a day when called with a date" do
401+
schedule = IceCube::Schedule.new(Time.utc(2014, 1, 1, 12, 34, 56)) do |s|
402+
s.add_recurrence_rule IceCube::Rule.hourly
403+
end
404+
next_hours = schedule.next_occurrences(2, Date.new(2014, 1, 2))
405+
expect( next_hours ).to eq [Time.utc(2014, 1, 2, 00, 34 , 56),
406+
Time.utc(2014, 1, 2, 01, 34 , 56)]
407+
end
408+
378409
end
379410

380411
describe :next_occurrence do
@@ -438,6 +469,14 @@
438469
previous.should be_nil
439470
end
440471

472+
it "should not skip back a day when called with a date" do
473+
schedule = IceCube::Schedule.new(Time.utc(2014, 1, 1, 12, 34, 56)) do |s|
474+
s.add_recurrence_rule IceCube::Rule.hourly
475+
end
476+
prev_hour = schedule.previous_occurrence(Date.new(2014, 1, 2))
477+
expect( prev_hour ).to eq Time.utc(2014, 1, 1, 23, 34 , 56)
478+
end
479+
441480
end
442481

443482
describe :previous_occurrences do
@@ -466,6 +505,15 @@
466505
previous.should == []
467506
end
468507

508+
it "should not skip back a day when called with a date" do
509+
schedule = IceCube::Schedule.new(Time.utc(2014, 1, 1, 12, 34, 56)) do |s|
510+
s.add_recurrence_rule IceCube::Rule.hourly
511+
end
512+
prev_hours = schedule.previous_occurrences(2, Date.new(2014, 1, 2))
513+
expect( prev_hours ).to eq [Time.utc(2014, 1, 1, 22, 34 , 56),
514+
Time.utc(2014, 1, 1, 23, 34 , 56)]
515+
end
516+
469517
end
470518

471519
describe :last do

spec/examples/time_util_spec.rb

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,5 +96,28 @@ module IceCube
9696
end
9797
end
9898

99+
describe :match_zone do
100+
101+
let(:date) { Date.new(2014, 1, 1) }
102+
103+
WORLD_TIME_ZONES.each do |zone|
104+
context "in #{zone}", :system_time_zone => zone do
105+
let(:local_time) { Time.local(2014, 1, 1, 0, 0, 1) }
106+
107+
it 'converts Date to beginning of date of reference time' do
108+
expect(TimeUtil.match_zone(date, local_time)).to eq local_time - 1
109+
end
110+
end
111+
end
112+
113+
context "in UTC" do
114+
let(:utc_time) { Time.utc(2014, 1, 1, 0, 0, 1) }
115+
116+
it 'converts Date to beginning of date of reference time' do
117+
expect(TimeUtil.match_zone(date, utc_time)).to eq utc_time - 1
118+
end
119+
end
120+
end
121+
99122
end
100123
end

0 commit comments

Comments
 (0)