Skip to content

Commit

Permalink
Support BYSETPOS for MONTHLY AND YEARLY freq
Browse files Browse the repository at this point in the history
  • Loading branch information
Nicolas Marlier authored and davidstosik committed Feb 6, 2019
1 parent fb6c657 commit 04290ec
Show file tree
Hide file tree
Showing 11 changed files with 253 additions and 1 deletion.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,8 @@
/spec/reports/
/tmp/

# rubymine
.idea

# rspec failure tracking
.rspec_status
3 changes: 3 additions & 0 deletions lib/ice_cube.rb
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,9 @@ module Validations
autoload :YearlyInterval, 'ice_cube/validations/yearly_interval'
autoload :HourlyInterval, 'ice_cube/validations/hourly_interval'

autoload :MonthlyBySetPos, 'ice_cube/validations/monthly_by_set_pos'
autoload :YearlyBySetPos, 'ice_cube/validations/yearly_by_set_pos'

autoload :HourOfDay, 'ice_cube/validations/hour_of_day'
autoload :MonthOfYear, 'ice_cube/validations/month_of_year'
autoload :MinuteOfHour, 'ice_cube/validations/minute_of_hour'
Expand Down
1 change: 1 addition & 0 deletions lib/ice_cube/parsers/ical_parser.rb
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ def self.rule_from_ical(ical)
when 'BYYEARDAY'
validations[:day_of_year] = value.split(',').map(&:to_i)
when 'BYSETPOS'
params[:validations][:by_set_pos] = value.split(',').collect(&:to_i)
else
validations[name] = nil # invalid type
end
Expand Down
1 change: 1 addition & 0 deletions lib/ice_cube/rules/monthly_rule.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ class MonthlyRule < ValidatedRule
# include Validations::DayOfYear # n/a

include Validations::MonthlyInterval
include Validations::MonthlyBySetPos

def initialize(interval = 1)
super
Expand Down
1 change: 1 addition & 0 deletions lib/ice_cube/rules/yearly_rule.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ class YearlyRule < ValidatedRule
include Validations::DayOfYear

include Validations::YearlyInterval
include Validations::YearlyBySetPos

def initialize(interval = 1)
super
Expand Down
32 changes: 32 additions & 0 deletions lib/ice_cube/time_util.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
require 'date'
require 'time'
require 'active_support'
require 'active_support/core_ext'

module IceCube
module TimeUtil
Expand Down Expand Up @@ -198,6 +200,36 @@ def self.which_occurrence_in_month(time, wday)
[nth_occurrence_of_weekday, this_weekday_in_month_count]
end

# Use Activesupport CoreExt functions to manipulate time
def self.start_of_month time
time.beginning_of_month
end

# Use Activesupport CoreExt functions to manipulate time
def self.end_of_month time
time.end_of_month
end

# Use Activesupport CoreExt functions to manipulate time
def self.start_of_year time
time.beginning_of_year
end

# Use Activesupport CoreExt functions to manipulate time
def self.end_of_year time
time.end_of_year
end

# Use Activesupport CoreExt functions to manipulate time
def self.previous_month time
time - 1.month
end

# Use Activesupport CoreExt functions to manipulate time
def self.previous_year time
time - 1.year
end

# Get the days in the month for +time
def self.days_in_month(time)
date = Date.new(time.year, time.month, 1)
Expand Down
3 changes: 2 additions & 1 deletion lib/ice_cube/validated_rule.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ class ValidatedRule < Rule
:base_sec, :base_min, :base_day, :base_hour, :base_month, :base_wday,
:day_of_year, :second_of_minute, :minute_of_hour, :day_of_month,
:hour_of_day, :month_of_year, :day_of_week,
:interval
:interval,
:by_set_pos
]

attr_reader :validations
Expand Down
87 changes: 87 additions & 0 deletions lib/ice_cube/validations/monthly_by_set_pos.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
module IceCube

module Validations::MonthlyBySetPos

def by_set_pos(*by_set_pos)
return by_set_pos([by_set_pos]) if by_set_pos.is_a?(Fixnum)

unless by_set_pos.nil? || by_set_pos.is_a?(Array)
raise ArgumentError, "Expecting Array or nil value for count, got #{by_set_pos.inspect}"
end
by_set_pos.flatten!
by_set_pos.each do |set_pos|
unless (set_pos >= -366 && set_pos <= -1) ||
(set_pos <= 366 && set_pos >= 1)
raise ArgumentError, "Expecting number in [-366, -1] or [1, 366], got #{set_pos} (#{by_set_pos})"
end
end

@by_set_pos = by_set_pos
replace_validations_for(:by_set_pos, by_set_pos && [Validation.new(by_set_pos, self)])
self
end

class Validation

attr_reader :rule, :by_set_pos

def initialize(by_set_pos, rule)

@by_set_pos = by_set_pos
@rule = rule
end

def type
:day
end

def dst_adjust?
true
end

def validate(step_time, schedule)
start_of_month = TimeUtil.start_of_month step_time
end_of_month = TimeUtil.end_of_month step_time


new_schedule = IceCube::Schedule.new(TimeUtil.previous_month(step_time)) do |s|
s.add_recurrence_rule IceCube::Rule.from_hash(rule.to_hash.reject{|k, v| [:by_set_pos, :count, :until].include? k})
end

puts step_time
occurrences = new_schedule.occurrences_between(start_of_month, end_of_month)
p occurrences
index = occurrences.index(step_time)
if index == nil
1
else
positive_set_pos = index + 1
negative_set_pos = index - occurrences.length

if @by_set_pos.include?(positive_set_pos) || @by_set_pos.include?(negative_set_pos)
0
else
1
end
end
end


def build_s(builder)
builder.piece(:by_set_pos) << by_set_pos
end

def build_hash(builder)
builder[:by_set_pos] = by_set_pos
end

def build_ical(builder)
builder['BYSETPOS'] << by_set_pos
end

nil
end

end

end
89 changes: 89 additions & 0 deletions lib/ice_cube/validations/yearly_by_set_pos.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
module IceCube

module Validations::YearlyBySetPos

def by_set_pos(*by_set_pos)
return by_set_pos([by_set_pos]) if by_set_pos.is_a?(Fixnum)

unless by_set_pos.nil? || by_set_pos.is_a?(Array)
raise ArgumentError, "Expecting Array or nil value for count, got #{by_set_pos.inspect}"
end
by_set_pos.flatten!
by_set_pos.each do |set_pos|
unless (set_pos >= -366 && set_pos <= -1) ||
(set_pos <= 366 && set_pos >= 1)
raise ArgumentError, "Expecting number in [-366, -1] or [1, 366], got #{set_pos} (#{by_set_pos})"
end
end

@by_set_pos = by_set_pos
replace_validations_for(:by_set_pos, by_set_pos && [Validation.new(by_set_pos, self)])
self
end

class Validation

attr_reader :rule, :by_set_pos

def initialize(by_set_pos, rule)

@by_set_pos = by_set_pos
@rule = rule
end

def type
:day
end

def dst_adjust?
true
end

def validate(step_time, schedule)
start_of_year = TimeUtil.start_of_year step_time
end_of_year = TimeUtil.end_of_year step_time


new_schedule = IceCube::Schedule.new(TimeUtil.previous_year(step_time)) do |s|
s.add_recurrence_rule IceCube::Rule.from_hash(rule.to_hash.reject{|k, v| [:by_set_pos, :count, :until].include? k})
end

occurrences = new_schedule.occurrences_between(start_of_year, end_of_year)

index = occurrences.index(step_time)
if index == nil
1
else
positive_set_pos = index + 1
negative_set_pos = index - occurrences.length

if @by_set_pos.include?(positive_set_pos) || @by_set_pos.include?(negative_set_pos)
0
else
1
end
end



end


def build_s(builder)
builder.piece(:by_set_pos) << by_set_pos
end

def build_hash(builder)
builder[:by_set_pos] = by_set_pos
end

def build_ical(builder)
builder['BYSETPOS'] << by_set_pos
end

nil
end

end

end
29 changes: 29 additions & 0 deletions spec/examples/by_set_pos_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
require File.dirname(__FILE__) + '/../spec_helper'

module IceCube

describe MonthlyRule, 'BYSETPOS' do
it 'should behave correctly' do
schedule = IceCube::Schedule.from_ical "RRULE:FREQ=MONTHLY;COUNT=4;BYDAY=WE;BYSETPOS=4"
schedule.start_time = Time.new(2015, 5, 28, 12, 0, 0)
expect(schedule.occurrences_between(Time.new(2015, 01, 01), Time.new(2017, 01, 01))).to eq([
Time.new(2015,6,24,12,0,0),
Time.new(2015,7,22,12,0,0),
Time.new(2015,8,26,12,0,0),
Time.new(2015,9,23,12,0,0)
])
end

end

describe YearlyRule, 'BYSETPOS' do
it 'should behave correctly' do
schedule = IceCube::Schedule.from_ical "RRULE:FREQ=YEARLY;BYMONTH=7;BYDAY=SU,MO,TU,WE,TH,FR,SA;BYSETPOS=-1"
schedule.start_time = Time.new(1966,7,5)
expect(schedule.occurrences_between(Time.new(2015, 01, 01), Time.new(2017, 01, 01))).to eq([
Time.new(2015, 7, 31),
Time.new(2016, 7, 31)
])
end
end
end
5 changes: 5 additions & 0 deletions spec/examples/from_ical_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,11 @@ module IceCube
expect(rule).to eq(IceCube::Rule.weekly(2, :monday))
end

it 'should be able to parse by_set_pos start (BYSETPOS)' do
rule = IceCube::Rule.from_ical("FREQ=MONTHLY;BYDAY=MO,WE;BYSETPOS=-1,1")
rule.should == IceCube::Rule.monthly.day(:monday, :wednesday).by_set_pos([-1, 1])
end

it 'should return no occurrences after daily interval with count is over' do
schedule = IceCube::Schedule.new(Time.now)
schedule.add_recurrence_rule(IceCube::Rule.from_ical("FREQ=DAILY;COUNT=5"))
Expand Down

0 comments on commit 04290ec

Please sign in to comment.