Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update to BYSETPOS support #449

Open
wants to merge 11 commits into
base: master
Choose a base branch
from
4 changes: 4 additions & 0 deletions lib/ice_cube.rb
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,10 @@ module Validations
autoload :YearlyInterval, "ice_cube/validations/yearly_interval"
autoload :HourlyInterval, "ice_cube/validations/hourly_interval"

autoload :WeeklyBySetPos, 'ice_cube/validations/weekly_by_set_pos'
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
13 changes: 12 additions & 1 deletion lib/ice_cube/parsers/ical_parser.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,18 @@ module IceCube
class IcalParser
def self.schedule_from_ical(ical_string, options = {})
data = {}

# First join lines that are wrapped
lines = []
ical_string.each_line do |line|
if lines[-1] && line =~ /\A[ \t].+/
lines[-1] = lines[-1].strip + line.sub(/\A[ \t]+/, "")
else
lines << line
end
end

lines.each do |line|
(property, value) = line.split(":")
(property, _tzid) = property.split(";")
case property
Expand Down Expand Up @@ -75,7 +86,7 @@ def self.rule_from_ical(ical)
when "BYYEARDAY"
validations[:day_of_year] = value.split(",").map(&:to_i)
when "BYSETPOS"
# noop
params[:validations][:by_set_pos] = value.split(',').collect(&:to_i)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps use the style validations[:by_set_pos] to match everything else in this case statement.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, existing code uses double quotes instead of single quotes for string in split and collect instead of map. Picky but just for consistency.

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 @@ -10,6 +10,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/weekly_rule.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ class WeeklyRule < ValidatedRule
# include Validations::DayOfYear # n/a

include Validations::WeeklyInterval
include Validations::WeeklyBySetPos

attr_reader :week_start

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 @@ -10,6 +10,7 @@ class YearlyRule < ValidatedRule
include Validations::DayOfYear

include Validations::YearlyInterval
include Validations::YearlyBySetPos

def initialize(interval = 1)
super
Expand Down
4 changes: 2 additions & 2 deletions lib/ice_cube/time_util.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
require "date"
require "time"
require 'date'
require 'time'
Comment on lines +1 to +2
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changing the type of quotes seems unnecessary. Double quotes are consistent with the existing style.


module IceCube
module TimeUtil
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 @@ -18,7 +18,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
77 changes: 77 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,77 @@
module IceCube

module Validations::MonthlyBySetPos
def by_set_pos(*by_set_pos)
by_set_pos.flatten!
by_set_pos.each do |set_pos|
unless (-366..366).include?(set_pos) && set_pos != 0
raise ArgumentError, "Expecting number in [-366, -1] or [1, 366], got #{set_pos} (#{by_set_pos})"
nehresma marked this conversation as resolved.
Show resolved Hide resolved
end
end

@by_set_pos = by_set_pos
replace_validations_for(: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, start_time)
start_of_month = TimeUtil.build_in_zone([step_time.year, step_time.month, 1, 0, 0, 0], step_time)
eom_date = Date.new(step_time.year, step_time.month, -1)
end_of_month = TimeUtil.build_in_zone([eom_date.year, eom_date.month, eom_date.day, 23, 59, 59], step_time)

# Needs to start on the first day of the month
new_schedule = IceCube::Schedule.new(IceCube::TimeUtil.build_in_zone([start_of_month.year, start_of_month.month, start_of_month.day, step_time.hour, step_time.min, step_time.sec], start_of_month)) do |s|
s.add_recurrence_rule(IceCube::Rule.from_hash(rule.to_hash.except(:by_set_pos, :count, :until)))
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto my comment about except.

end

occurrences = new_schedule.occurrences_between(start_of_month, end_of_month)
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
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hello @nehresma

Looks like this should be improved:

> IceCube::Rule.monthly(2).day(1,2,3,4,5).by_set_pos(2).to_s
"Every 2 months on Weekdays [2]"

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
91 changes: 91 additions & 0 deletions lib/ice_cube/validations/weekly_by_set_pos.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
module IceCube
module Validations::WeeklyBySetPos
def by_set_pos(*by_set_pos)
by_set_pos.flatten!
by_set_pos.each do |set_pos|
unless (-366..366).include?(set_pos) && set_pos != 0
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, [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, start_time)
# Use vanilla Ruby Date objects so we can add and subtract dates across DST changes
step_time_date = step_time.to_date
start_day_of_week = TimeUtil.sym_to_wday(rule.week_start)
step_time_day_of_week = step_time_date.wday
days_delta = step_time_day_of_week - start_day_of_week
days_to_start = days_delta >= 0 ? days_delta : 7 + days_delta
start_of_week_date = step_time_date - days_to_start
end_of_week_date = start_of_week_date + 6
start_of_week = IceCube::TimeUtil.build_in_zone(
[start_of_week_date.year, start_of_week_date.month, start_of_week_date.day, 0, 0, 0], step_time
)
end_of_week = IceCube::TimeUtil.build_in_zone(
[end_of_week_date.year, end_of_week_date.month, end_of_week_date.day, 23, 59, 59], step_time
)

# Needs to start on the first day of the week at the step_time's hour, min, sec
start_of_week_adjusted = IceCube::TimeUtil.build_in_zone(
[
start_of_week_date.year, start_of_week_date.month, start_of_week_date.day,
step_time.hour, step_time.min, step_time.sec
], step_time
)
new_schedule = IceCube::Schedule.new(start_of_week_adjusted) do |s|
s.add_recurrence_rule(IceCube::Rule.from_hash(rule.to_hash.except(:by_set_pos, :count, :until)))
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

end

occurrences = new_schedule.occurrences_between(start_of_week, end_of_week)
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
77 changes: 77 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,77 @@
module IceCube

module Validations::YearlyBySetPos
nehresma marked this conversation as resolved.
Show resolved Hide resolved

def by_set_pos(*by_set_pos)
by_set_pos.flatten!
by_set_pos.each do |set_pos|
unless (-366..366).include?(set_pos) && set_pos != 0
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, [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, start_time)
start_of_year = TimeUtil.build_in_zone([step_time.year, 1, 1, 0, 0, 0], step_time)
end_of_year = TimeUtil.build_in_zone([step_time.year, 12, 31, 23, 59, 59], step_time)

# Needs to start on the first day of the year
new_schedule = IceCube::Schedule.new(IceCube::TimeUtil.build_in_zone([start_of_year.year, start_of_year.month, start_of_year.day, step_time.hour, step_time.min, step_time.sec], start_of_year)) do |s|
s.add_recurrence_rule(IceCube::Rule.from_hash(rule.to_hash.except(:by_set_pos, :count, :until)))
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto my comment about except.

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
Loading