Skip to content

Commit ee3171d

Browse files
committed
support for bysetpos with freq=weekly
1 parent 621bf3d commit ee3171d

4 files changed

Lines changed: 121 additions & 0 deletions

File tree

lib/ice_cube.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ module Validations
4949
autoload :YearlyInterval, "ice_cube/validations/yearly_interval"
5050
autoload :HourlyInterval, "ice_cube/validations/hourly_interval"
5151

52+
autoload :WeeklyBySetPos, 'ice_cube/validations/weekly_by_set_pos'
5253
autoload :MonthlyBySetPos, 'ice_cube/validations/monthly_by_set_pos'
5354
autoload :YearlyBySetPos, 'ice_cube/validations/yearly_by_set_pos'
5455

lib/ice_cube/rules/weekly_rule.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ class WeeklyRule < ValidatedRule
1010
# include Validations::DayOfYear # n/a
1111

1212
include Validations::WeeklyInterval
13+
include Validations::WeeklyBySetPos
1314

1415
attr_reader :week_start
1516

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
module IceCube
2+
module Validations::WeeklyBySetPos
3+
def by_set_pos(*by_set_pos)
4+
by_set_pos.flatten!
5+
by_set_pos.each do |set_pos|
6+
unless (-366..366).include?(set_pos) && set_pos != 0
7+
raise ArgumentError, "Expecting number in [-366, -1] or [1, 366], got #{set_pos} (#{by_set_pos})"
8+
end
9+
end
10+
11+
@by_set_pos = by_set_pos
12+
replace_validations_for(:by_set_pos, [Validation.new(by_set_pos, self)])
13+
self
14+
end
15+
16+
class Validation
17+
18+
attr_reader :rule, :by_set_pos
19+
20+
def initialize(by_set_pos, rule)
21+
@by_set_pos = by_set_pos
22+
@rule = rule
23+
end
24+
25+
def type
26+
:day
27+
end
28+
29+
def dst_adjust?
30+
true
31+
end
32+
33+
def validate(step_time, start_time)
34+
# Use vanilla Ruby Date objects so we can add and subtract dates across DST changes
35+
step_time_date = step_time.to_date
36+
start_day_of_week = TimeUtil.sym_to_wday(rule.week_start)
37+
step_time_day_of_week = step_time_date.wday
38+
days_delta = step_time_day_of_week - start_day_of_week
39+
days_to_start = days_delta >= 0 ? days_delta : 7 + days_delta
40+
start_of_week_date = step_time_date - days_to_start
41+
end_of_week_date = start_of_week_date + 6
42+
start_of_week = IceCube::TimeUtil.build_in_zone(
43+
[start_of_week_date.year, start_of_week_date.month, start_of_week_date.day, 0, 0, 0], step_time
44+
)
45+
end_of_week = IceCube::TimeUtil.build_in_zone(
46+
[end_of_week_date.year, end_of_week_date.month, end_of_week_date.day, 23, 59, 59], step_time
47+
)
48+
49+
# Needs to start on the first day of the week at the step_time's hour, min, sec
50+
start_of_week_adjusted = IceCube::TimeUtil.build_in_zone(
51+
[
52+
start_of_week_date.year, start_of_week_date.month, start_of_week_date.day,
53+
step_time.hour, step_time.min, step_time.sec
54+
], step_time
55+
)
56+
new_schedule = IceCube::Schedule.new(start_of_week_adjusted) do |s|
57+
s.add_recurrence_rule(IceCube::Rule.from_hash(rule.to_hash.except(:by_set_pos, :count, :until)))
58+
end
59+
60+
occurrences = new_schedule.occurrences_between(start_of_week, end_of_week)
61+
index = occurrences.index(step_time)
62+
if index.nil?
63+
1
64+
else
65+
positive_set_pos = index + 1
66+
negative_set_pos = index - occurrences.length
67+
68+
if @by_set_pos.include?(positive_set_pos) || @by_set_pos.include?(negative_set_pos)
69+
0
70+
else
71+
1
72+
end
73+
end
74+
end
75+
76+
def build_s(builder)
77+
builder.piece(:by_set_pos) << by_set_pos
78+
end
79+
80+
def build_hash(builder)
81+
builder[:by_set_pos] = by_set_pos
82+
end
83+
84+
def build_ical(builder)
85+
builder['BYSETPOS'] << by_set_pos
86+
end
87+
88+
nil
89+
end
90+
end
91+
end

spec/examples/by_set_pos_spec.rb

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,34 @@
11
require File.dirname(__FILE__) + '/../spec_helper'
22

33
module IceCube
4+
describe WeeklyRule, 'BYSETPOS' do
5+
it 'should behave correctly' do
6+
# Weekly on Monday, Wednesday, and Friday with the week starting on Wednesday, the last day of the set
7+
schedule = IceCube::Schedule.from_ical("RRULE:FREQ=WEEKLY;COUNT=4;WKST=WE;BYDAY=MO,WE,FR;BYSETPOS=-1")
8+
schedule.start_time = Time.new(2022, 12, 27, 12, 0, 0)
9+
expect(schedule.occurrences_between(Time.new(2022, 01, 01), Time.new(2024, 01, 01))).
10+
to eq([
11+
Time.new(2023,1,2,12,0,0),
12+
Time.new(2023,1,9,12,0,0),
13+
Time.new(2023,1,16,12,0,0),
14+
Time.new(2023,1,23,12,0,0)
15+
])
16+
end
17+
18+
it 'should work with intervals' do
19+
# Every 2 weeks on Monday, Wednesday, and Friday with the week starting on Wednesday, the last day of the set
20+
schedule = IceCube::Schedule.from_ical("RRULE:FREQ=WEEKLY;COUNT=4;INTERVAL=2;WKST=WE;BYDAY=MO,WE,FR;BYSETPOS=-1")
21+
schedule.start_time = Time.new(2022, 12, 27, 12, 0, 0)
22+
expect(schedule.occurrences_between(Time.new(2022, 01, 01), Time.new(2024, 01, 01))).
23+
to eq([
24+
Time.new(2023,1,9,12,0,0),
25+
Time.new(2023,1,23,12,0,0),
26+
Time.new(2023,2,6,12,0,0),
27+
Time.new(2023,2,20,12,0,0)
28+
])
29+
end
30+
end
31+
432
describe MonthlyRule, 'BYSETPOS' do
533
it 'should behave correctly' do
634
schedule = IceCube::Schedule.from_ical "RRULE:FREQ=MONTHLY;COUNT=4;BYDAY=WE;BYSETPOS=4"

0 commit comments

Comments
 (0)