ice_cube 0.2.7 → 0.2.8
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/ice_cube.rb +12 -9
- data/lib/ice_cube/rule.rb +26 -38
- data/lib/ice_cube/rules/daily_rule.rb +4 -0
- data/lib/ice_cube/rules/hourly_rule.rb +4 -0
- data/lib/ice_cube/rules/minutely_rule.rb +4 -0
- data/lib/ice_cube/rules/monthly_rule.rb +4 -0
- data/lib/ice_cube/rules/secondly_rule.rb +4 -0
- data/lib/ice_cube/rules/weekly_rule.rb +4 -0
- data/lib/ice_cube/rules/yearly_rule.rb +4 -0
- data/lib/ice_cube/validation.rb +30 -0
- data/lib/ice_cube/validation_types.rb +138 -0
- data/lib/ice_cube/validations/day.rb +31 -28
- data/lib/ice_cube/validations/day_of_month.rb +42 -43
- data/lib/ice_cube/validations/day_of_week.rb +40 -33
- data/lib/ice_cube/validations/day_of_year.rb +41 -43
- data/lib/ice_cube/validations/hour_of_day.rb +30 -27
- data/lib/ice_cube/validations/minute_of_hour.rb +30 -27
- data/lib/ice_cube/validations/month_of_year.rb +30 -31
- data/lib/ice_cube/validations/second_of_minute.rb +30 -27
- data/lib/ice_cube/version.rb +1 -1
- metadata +5 -3
data/lib/ice_cube.rb
CHANGED
@@ -1,17 +1,11 @@
|
|
1
1
|
require 'yaml.rb'
|
2
2
|
require 'set.rb'
|
3
|
+
require 'date'
|
3
4
|
|
4
5
|
require 'ice_cube/time_util'
|
5
6
|
|
6
|
-
require 'ice_cube/
|
7
|
-
require 'ice_cube/
|
8
|
-
require 'ice_cube/validations/day_of_month'
|
9
|
-
require 'ice_cube/validations/day_of_week'
|
10
|
-
require 'ice_cube/validations/day'
|
11
|
-
require 'ice_cube/validations/hour_of_day'
|
12
|
-
require 'ice_cube/validations/minute_of_hour'
|
13
|
-
require 'ice_cube/validations/second_of_minute'
|
14
|
-
|
7
|
+
require 'ice_cube/validation'
|
8
|
+
require 'ice_cube/validation_types'
|
15
9
|
require 'ice_cube/rule'
|
16
10
|
require 'ice_cube/schedule'
|
17
11
|
require 'ice_cube/rule_occurrence'
|
@@ -27,6 +21,15 @@ module IceCube
|
|
27
21
|
autoload :MinutelyRule, 'ice_cube/rules/minutely_rule'
|
28
22
|
autoload :SecondlyRule, 'ice_cube/rules/secondly_rule'
|
29
23
|
|
24
|
+
autoload :DayValidation, 'ice_cube/validations/day'
|
25
|
+
autoload :DayOfMonthValidation, 'ice_cube/validations/day_of_month'
|
26
|
+
autoload :DayOfWeekValidation, 'ice_cube/validations/day_of_week'
|
27
|
+
autoload :DayOfYearValidation, 'ice_cube/validations/day_of_year'
|
28
|
+
autoload :HourOfDayValidation, 'ice_cube/validations/hour_of_day'
|
29
|
+
autoload :MinuteOfHourValidation, 'ice_cube/validations/minute_of_hour'
|
30
|
+
autoload :MonthOfYearValidation, 'ice_cube/validations/month_of_year'
|
31
|
+
autoload :SecondOfMinuteValidation, 'ice_cube/validations/second_of_minute'
|
32
|
+
|
30
33
|
VERSION = '0.2.3'
|
31
34
|
|
32
35
|
IceCube::ONE_DAY = 24 * 60 * 60
|
data/lib/ice_cube/rule.rb
CHANGED
@@ -3,11 +3,9 @@ module IceCube
|
|
3
3
|
class Rule
|
4
4
|
|
5
5
|
attr_reader :occurrence_count, :until_date
|
6
|
-
|
7
|
-
SuggestionTypes = []
|
8
|
-
include MonthOfYearValidation, DayOfYearValidation, DayOfMonthValidation, DayOfWeekValidation, DayValidation
|
9
|
-
include HourOfDayValidation, MinuteOfHourValidation, SecondOfMinuteValidation
|
10
6
|
|
7
|
+
include ValidationTypes
|
8
|
+
|
11
9
|
def to_hash
|
12
10
|
hash = Hash.new
|
13
11
|
hash[:rule_type] = self.class.name
|
@@ -22,7 +20,9 @@ module IceCube
|
|
22
20
|
rule = hash[:rule_type].split('::').inject(Object) { |namespace, const_name| namespace.const_get(const_name) }.new(hash[:interval])
|
23
21
|
rule.count(hash[:count]) if hash[:count]
|
24
22
|
rule.until(hash[:until]) if hash[:until]
|
25
|
-
|
23
|
+
hash[:validations].each do |validation, data|
|
24
|
+
data.is_a?(Array) ? rule.send(validation, *data) : rule.send(validation, data)
|
25
|
+
end
|
26
26
|
rule
|
27
27
|
end
|
28
28
|
|
@@ -76,8 +76,8 @@ module IceCube
|
|
76
76
|
end
|
77
77
|
|
78
78
|
def validate_single_date(date)
|
79
|
-
|
80
|
-
response = send(
|
79
|
+
@validation_types.values.all? do |validation|
|
80
|
+
response = validation.send(:validate, date)
|
81
81
|
response.nil? || response
|
82
82
|
end
|
83
83
|
end
|
@@ -87,8 +87,9 @@ module IceCube
|
|
87
87
|
# by constantly moving the farthest back value forward
|
88
88
|
def next_suggestion(date)
|
89
89
|
# get the next date recommendation set
|
90
|
-
suggestions =
|
91
|
-
|
90
|
+
suggestions = {};
|
91
|
+
@validation_types.each { |k, validation| suggestions[k] = validation.send(:closest, date) }
|
92
|
+
compact_suggestions = suggestions.values.compact
|
92
93
|
# find the next date to go to
|
93
94
|
if compact_suggestions.empty?
|
94
95
|
next_date = date
|
@@ -99,23 +100,19 @@ module IceCube
|
|
99
100
|
end
|
100
101
|
else
|
101
102
|
loop do
|
102
|
-
compact_suggestions = suggestions.compact
|
103
|
+
compact_suggestions = suggestions.values.compact
|
103
104
|
min_suggestion = compact_suggestions.min
|
104
105
|
# validate all against the minimum
|
105
106
|
return min_suggestion if validate_single_date(min_suggestion)
|
106
107
|
# move anything that is the minimum to its next closest
|
107
|
-
|
108
|
-
suggestions[
|
108
|
+
@validation_types.each do |k, validation|
|
109
|
+
suggestions[k] = validation.send(:closest, min_suggestion) if min_suggestion == suggestions[k]
|
109
110
|
end
|
110
111
|
end
|
111
112
|
end
|
112
113
|
end
|
113
114
|
|
114
|
-
|
115
|
-
to_ical
|
116
|
-
end
|
117
|
-
|
118
|
-
attr_accessor :validations
|
115
|
+
attr_reader :validations
|
119
116
|
|
120
117
|
private
|
121
118
|
|
@@ -124,32 +121,22 @@ module IceCube
|
|
124
121
|
goal - goal.utc_offset + date.utc_offset
|
125
122
|
end
|
126
123
|
|
124
|
+
# get a very meaningful string representation of this rule
|
125
|
+
def to_s_base(singular, plural)
|
126
|
+
representation = ''
|
127
|
+
representation = 'Every ' << ((@interval == 1) ? singular : "#{@interval} #{plural}")
|
128
|
+
representation << @validation_types.values.map { |v| ' ' + v.send(:to_s) }.join()
|
129
|
+
representation
|
130
|
+
end
|
131
|
+
|
127
132
|
#TODO - until date formatting is not iCalendar here
|
128
133
|
#get the icalendar representation of this rule logic
|
129
134
|
def to_ical_base
|
130
135
|
representation = ''
|
131
136
|
representation << ";INTERVAL=#{@interval}" if @interval > 1
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
if @validations[:day] || @validations[:day_of_week]
|
136
|
-
representation << ';BYDAY='
|
137
|
-
days_dedup = @validations[:day].dup if @validations[:day]
|
138
|
-
#put days on the string, remove all occurrences in days from days_of_week
|
139
|
-
if days_dedup
|
140
|
-
@validations[:day_of_week].keys.each { |day| days_dedup.delete(day) } if @validations[:day_of_week]
|
141
|
-
representation << (days_dedup.map { |d| IceCube::ICAL_DAYS[d]} ).join(',')
|
142
|
-
end
|
143
|
-
representation << ',' if days_dedup && @validations[:day_of_week]
|
144
|
-
#put days_of_week on string representation
|
145
|
-
representation << @validations[:day_of_week].inject([]) do |day_rules, pair|
|
146
|
-
day, occ = *pair
|
147
|
-
day_rules.concat(occ.map {|v| v.to_s + IceCube::ICAL_DAYS[day]})
|
148
|
-
end.flatten.join(',') if @validations[:day_of_week]
|
149
|
-
end
|
150
|
-
representation << ';BYHOUR=' << @validations[:hour_of_day].join(',') if @validations[:hour_of_day]
|
151
|
-
representation << ';BYMINUTE=' << @validations[:minute_of_hour].join(',') if @validations[:minute_of_hour]
|
152
|
-
representation << ';BYSECOND=' << @validations[:second_of_minute].join(',') if @validations[:second_of_minute]
|
137
|
+
@validation_types.values.each do |v|
|
138
|
+
representation << ';' << v.send(:to_ical)
|
139
|
+
end
|
153
140
|
representation << ";COUNT=#{@occurrence_count}" if @occurrence_count
|
154
141
|
representation << ";UNTIL=#{@until_date}" if @until_date
|
155
142
|
representation
|
@@ -160,6 +147,7 @@ module IceCube
|
|
160
147
|
def initialize(interval = 1)
|
161
148
|
throw ArgumentError.new('Interval must be > 0') unless interval > 0
|
162
149
|
@validations = {}
|
150
|
+
@validation_types = {}
|
163
151
|
@interval = interval
|
164
152
|
end
|
165
153
|
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module IceCube
|
2
|
+
|
3
|
+
class Validation
|
4
|
+
|
5
|
+
NUMBER_SUFFIX = ['th', 'st', 'nd', 'rd', 'th', 'th', 'th', 'th', 'th', 'th']
|
6
|
+
|
7
|
+
def adjust(goal, date)
|
8
|
+
return goal if goal.utc_offset == date.utc_offset
|
9
|
+
goal - goal.utc_offset + date.utc_offset
|
10
|
+
end
|
11
|
+
|
12
|
+
def nice_numbers(array)
|
13
|
+
array.map { |d| nice_number(d) }.join(', ')
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def nice_number(number)
|
19
|
+
if number == -1
|
20
|
+
'last'
|
21
|
+
elsif number < -1
|
22
|
+
number.abs.to_s << NUMBER_SUFFIX[number.abs % 10] << ' to last'
|
23
|
+
else
|
24
|
+
number.to_s << NUMBER_SUFFIX[number % 10]
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
@@ -0,0 +1,138 @@
|
|
1
|
+
module ValidationTypes
|
2
|
+
|
3
|
+
def second_of_minute(*seconds)
|
4
|
+
@validations[:second_of_minute] ||= []
|
5
|
+
@validation_types[:second_of_minute] ||= SecondOfMinuteValidation.new(self)
|
6
|
+
seconds.each do |second|
|
7
|
+
raise ArgumentError.new('Argument must be a valid second') unless second < 60 && second >= 0
|
8
|
+
@validations[:second_of_minute] << second
|
9
|
+
end
|
10
|
+
# enforce uniqueness
|
11
|
+
@validations[:second_of_minute].uniq!
|
12
|
+
self
|
13
|
+
end
|
14
|
+
|
15
|
+
# Specify what days of the week this rule should occur on.
|
16
|
+
# ie: Schedule.weekly.day_of_week(:monday) would create a rule that
|
17
|
+
# occurs every monday.
|
18
|
+
def day(*days)
|
19
|
+
@validations[:day] ||= []
|
20
|
+
@validation_types[:day] ||= DayValidation.new(self)
|
21
|
+
days.each do |day|
|
22
|
+
if day.is_a?(Integer)
|
23
|
+
# integer type argument
|
24
|
+
raise ArgumentError.new('Argument must be a valid day of week (0-6)') unless day >= 0 && day <= 6
|
25
|
+
@validations[:day] << day
|
26
|
+
else
|
27
|
+
# symbol type argument
|
28
|
+
raise ArgumentError.new('Argument must be a valid day of the week') unless IceCube::DAYS.has_key?(day)
|
29
|
+
@validations[:day] << IceCube::DAYS[day]
|
30
|
+
end
|
31
|
+
end
|
32
|
+
# enforce uniqueness
|
33
|
+
@validations[:day].uniq!
|
34
|
+
self
|
35
|
+
end
|
36
|
+
|
37
|
+
# Specify what days of the year this rule applies to.
|
38
|
+
# ie: Schedule.yearly(2).days_of_year(17, -1) would create a
|
39
|
+
# rule which occurs every 17th and last day of every other year.
|
40
|
+
# Note: you cannot combine month_of_year and day_of_year in the same rule.
|
41
|
+
def day_of_year(*days)
|
42
|
+
@validations[:day_of_year] ||= []
|
43
|
+
@validation_types[:day_of_year] ||= DayOfYearValidation.new(self)
|
44
|
+
days.each do |day|
|
45
|
+
raise ArgumentError.new('Argument must be a valid day') if day.abs > 366
|
46
|
+
raise ArgumentError.new('Argument must be non-zero') if day == 0
|
47
|
+
@validations[:day_of_year] << day
|
48
|
+
end
|
49
|
+
# enforce uniqueness
|
50
|
+
@validations[:day_of_year].uniq!
|
51
|
+
self
|
52
|
+
end
|
53
|
+
|
54
|
+
# Specify what months of the year this rule applies to.
|
55
|
+
# ie: Schedule.yearly(2).month_of_year(:january, :march) would create a
|
56
|
+
# rule which occurs every january and march, every other year
|
57
|
+
# Note: you cannot combine day_of_year and month_of_year in the same rule.
|
58
|
+
def month_of_year(*months)
|
59
|
+
@validations[:month_of_year] ||= []
|
60
|
+
@validation_types[:month_of_year] ||= MonthOfYearValidation.new(self)
|
61
|
+
months.each do |month|
|
62
|
+
if month.is_a?(Integer)
|
63
|
+
# integer type argument
|
64
|
+
raise ArgumentError.new('Argument must be a valid month (1-12)') unless month >= 1 && month <= 12
|
65
|
+
@validations[:month_of_year] << month
|
66
|
+
else
|
67
|
+
#symbol type argument
|
68
|
+
raise ArgumentError.new('Argument must be a valid month') unless IceCube::MONTHS.has_key?(month)
|
69
|
+
@validations[:month_of_year] << IceCube::MONTHS[month]
|
70
|
+
end
|
71
|
+
end
|
72
|
+
# enforce uniqueness
|
73
|
+
@validations[:month_of_year].uniq!
|
74
|
+
self
|
75
|
+
end
|
76
|
+
|
77
|
+
# Specify the day(s) of the week that this rule should occur
|
78
|
+
# on. ie: rule.day_of_week(:monday => [1, -1]) would mean
|
79
|
+
# that this rule should occur on the first and last mondays of each month.
|
80
|
+
def day_of_week(days)
|
81
|
+
puts days[0].to_s
|
82
|
+
@validations[:day_of_week] ||= {}
|
83
|
+
@validation_types[:day_of_week] ||= DayOfWeekValidation.new(self)
|
84
|
+
days.each do |day, occurrences|
|
85
|
+
unless day.is_a?(Integer)
|
86
|
+
raise ArgumentError.new('Argument must be a valid day of week') unless IceCube::DAYS.has_key?(day)
|
87
|
+
day = IceCube::DAYS[day]
|
88
|
+
end
|
89
|
+
raise ArgumentError.new('Argument must be a valid day of week (0-6)') unless day >= 0 && day <= 6
|
90
|
+
# add the day
|
91
|
+
@validations[:day_of_week][day] ||= []
|
92
|
+
@validations[:day_of_week][day].concat(occurrences)
|
93
|
+
@validations[:day_of_week][day].uniq!
|
94
|
+
end
|
95
|
+
self
|
96
|
+
end
|
97
|
+
|
98
|
+
def hour_of_day(*hours)
|
99
|
+
@validations[:hour_of_day] ||= []
|
100
|
+
@validation_types[:hour_of_day] ||= HourOfDayValidation.new(self)
|
101
|
+
hours.each do |hour|
|
102
|
+
raise ArgumentError.new('Argument must be a valid hour') unless hour < 24 && hour >= 0
|
103
|
+
@validations[:hour_of_day] << hour
|
104
|
+
end
|
105
|
+
# enforce uniqueness
|
106
|
+
@validations[:hour_of_day].uniq!
|
107
|
+
self
|
108
|
+
end
|
109
|
+
|
110
|
+
# Specify the days of the month that this rule should
|
111
|
+
# occur on. ie: rule.day_of_month(1, -1) would mean that
|
112
|
+
# this rule should occur on the first and last day of every month.
|
113
|
+
def day_of_month(*days)
|
114
|
+
@validations[:day_of_month] ||= []
|
115
|
+
@validation_types[:day_of_month] ||= DayOfMonthValidation.new(self)
|
116
|
+
days.each do |day|
|
117
|
+
raise ArgumentError.new('Argument must be a valid date') if day.abs > 31
|
118
|
+
raise ArgumentError.new('Argument must be non-zero') if day == 0
|
119
|
+
@validations[:day_of_month] << day
|
120
|
+
end
|
121
|
+
# enforce uniqueness
|
122
|
+
@validations[:day_of_month].uniq!
|
123
|
+
self
|
124
|
+
end
|
125
|
+
|
126
|
+
def minute_of_hour(*minutes)
|
127
|
+
@validations[:minute_of_hour] ||= []
|
128
|
+
@validation_types[:minute_of_hour] ||= MinuteOfHourValidation.new(self)
|
129
|
+
minutes.each do |minute|
|
130
|
+
raise ArgumentError.new('Argument must be a valid minute') unless minute < 60 && minute >= 0
|
131
|
+
@validations[:minute_of_hour] << minute
|
132
|
+
end
|
133
|
+
# enforce uniqueness
|
134
|
+
@validations[:minute_of_hour].uniq!
|
135
|
+
self
|
136
|
+
end
|
137
|
+
|
138
|
+
end
|
@@ -1,36 +1,39 @@
|
|
1
|
-
module
|
1
|
+
module IceCube
|
2
|
+
|
3
|
+
class DayValidation < Validation
|
2
4
|
|
3
|
-
|
4
|
-
|
5
|
-
|
5
|
+
def initialize(rule)
|
6
|
+
@days = rule.validations[:day]
|
7
|
+
@rule = rule
|
8
|
+
end
|
6
9
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
def day(*days)
|
11
|
-
@validations[:day] ||= []
|
12
|
-
days.each do |day|
|
13
|
-
raise ArgumentError.new('Argument must be a valid day of the week') unless IceCube::DAYS.has_key?(day)
|
14
|
-
@validations[:day] << IceCube::DAYS[day]
|
10
|
+
def validate(date)
|
11
|
+
return true if !@days || @days.empty?
|
12
|
+
@days.include?(date.wday)
|
15
13
|
end
|
16
|
-
self
|
17
|
-
end
|
18
14
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
15
|
+
def closest(date)
|
16
|
+
return nil if !@days || @days.empty?
|
17
|
+
# turn days into distances
|
18
|
+
days = @days.map do |d|
|
19
|
+
d > date.wday ? (d - date.wday) : (7 - date.wday + d)
|
20
|
+
end
|
21
|
+
days.compact!
|
22
|
+
# go to the closest distance away, the start of that day
|
23
|
+
goal = date + days.min * IceCube::ONE_DAY
|
24
|
+
adjust(goal, date)
|
25
|
+
end
|
23
26
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
27
|
+
def to_s
|
28
|
+
days_dup = (@days - @rule.validations[:day_of_week].keys if @rule.validations[:day_of_week]) || @days # don't list twice
|
29
|
+
'on every ' << days_dup.map { |d| Date::DAYNAMES[d] }.join(', ') unless days_dup.empty?
|
30
|
+
end
|
31
|
+
|
32
|
+
def to_ical
|
33
|
+
days_dup = (@days - @rule.validations[:day_of_week].keys if @rule.validations[:day_of_week]) || @days # don't list twice
|
34
|
+
'BYDAY=' << days_dup.map { |d| IceCube::ICAL_DAYS[d] }.join(',') unless days_dup.empty?
|
29
35
|
end
|
30
|
-
days.compact!
|
31
|
-
# go to the closest distance away, the start of that day
|
32
|
-
goal = date + days.min * IceCube::ONE_DAY
|
33
|
-
adjust(goal, date)
|
34
|
-
end
|
35
36
|
|
37
|
+
end
|
38
|
+
|
36
39
|
end
|
@@ -1,51 +1,50 @@
|
|
1
|
-
module
|
1
|
+
module IceCube
|
2
|
+
|
3
|
+
class DayOfMonthValidation < Validation
|
2
4
|
|
3
|
-
|
4
|
-
|
5
|
-
end
|
6
|
-
|
7
|
-
# Specify the days of the month that this rule should
|
8
|
-
# occur on. ie: rule.day_of_month(1, -1) would mean that
|
9
|
-
# this rule should occur on the first and last day of every month.
|
10
|
-
def day_of_month(*days)
|
11
|
-
@validations[:day_of_month] ||= []
|
12
|
-
days.each do |day|
|
13
|
-
raise ArgumentError.new('Argument must be a valid date') if day.abs > 31
|
14
|
-
raise ArgumentError.new('Argument must be non-zero') if day == 0
|
15
|
-
@validations[:day_of_month] << day
|
5
|
+
def initialize(rule)
|
6
|
+
@days_of_month = rule.validations[:day_of_month]
|
16
7
|
end
|
17
|
-
self
|
18
|
-
end
|
19
8
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
9
|
+
def validate(date)
|
10
|
+
return true if !@days_of_month || @days_of_month.empty?
|
11
|
+
@days_of_month.include?(date.mday) || @days_of_month.include?(date.mday - TimeUtil.days_in_month(date) - 1)
|
12
|
+
end
|
24
13
|
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
14
|
+
def closest(date)
|
15
|
+
return nil if !@days_of_month || @days_of_month.empty?
|
16
|
+
#get some variables we need
|
17
|
+
days_in_month = TimeUtil.days_in_month(date)
|
18
|
+
days_left_in_this_month = days_in_month - date.mday
|
19
|
+
next_month, next_year = date.month == 12 ? [1, date.year + 1] : [date.month + 1, date.year] #clean way to wrap over years
|
20
|
+
days_in_next_month = TimeUtil.days_in_month(Time.utc(next_year, next_month, 1))
|
21
|
+
# create a list of distances
|
22
|
+
distances = []
|
23
|
+
@days_of_month.each do |d|
|
24
|
+
if d > 0
|
25
|
+
distances << d - date.mday #today is 1, we want 20 (19)
|
26
|
+
distances << days_left_in_this_month + d #(364 + 20)
|
27
|
+
elsif d < 0
|
28
|
+
distances << (days_in_month + d + 1) - date.mday #today is 30, we want -1
|
29
|
+
distances << (days_in_next_month + d + 1) + days_left_in_this_month #today is 300, we want -70
|
30
|
+
end
|
41
31
|
end
|
32
|
+
#return the lowest distance
|
33
|
+
distances = distances.select { |d| d > 0 }
|
34
|
+
return nil if distances.empty?
|
35
|
+
# return the start of the proper day
|
36
|
+
goal = date + distances.min * IceCube::ONE_DAY
|
37
|
+
adjust(goal, date)
|
42
38
|
end
|
43
|
-
#return the lowest distance
|
44
|
-
distances = distances.select { |d| d > 0 }
|
45
|
-
return nil if distances.empty?
|
46
|
-
# return the start of the proper day
|
47
|
-
goal = date + distances.min * IceCube::ONE_DAY
|
48
|
-
adjust(goal, date)
|
49
|
-
end
|
50
39
|
|
40
|
+
def to_s
|
41
|
+
'on the ' << nice_numbers(@days_of_month.sort) << (@days_of_month.count == 1 ? ' day' : ' days') << ' of the month' unless @days_of_month.empty?
|
42
|
+
end
|
43
|
+
|
44
|
+
def to_ical
|
45
|
+
'BYMONTHDAY=' << @days_of_month.join(',') unless @days_of_month.empty?
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
|
51
50
|
end
|
@@ -1,41 +1,48 @@
|
|
1
|
-
module
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
1
|
+
module IceCube
|
2
|
+
|
3
|
+
class DayOfWeekValidation < Validation
|
4
|
+
|
5
|
+
def initialize(rule)
|
6
|
+
@days_of_week = rule.validations[:day_of_week]
|
7
|
+
@rule = rule
|
8
|
+
end
|
6
9
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
@
|
10
|
+
def validate(date)
|
11
|
+
# is it even one of the valid days?
|
12
|
+
return true if !@days_of_week || @days_of_week.empty?
|
13
|
+
return false unless @days_of_week.has_key?(date.wday) #shortcut
|
14
|
+
# does this fall on one of the occurrences?
|
15
|
+
first_occurrence = ((7 - Time.utc(date.year, date.month, 1).wday) + date.wday) % 7 + 1 #day of first occurrence of a wday in a month
|
16
|
+
this_weekday_in_month_count = ((TimeUtil.days_in_month(date) - first_occurrence + 1) / 7.0).ceil #how many of these in the month
|
17
|
+
nth_occurrence_of_weekday = (date.mday - first_occurrence) / 7 + 1 #what occurrence of the weekday is +date+
|
18
|
+
@days_of_week[date.wday].include?(nth_occurrence_of_weekday) || @days_of_week[date.wday].include?(nth_occurrence_of_weekday - this_weekday_in_month_count - 1)
|
16
19
|
end
|
17
|
-
self
|
18
|
-
end
|
19
20
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
21
|
+
#note - temporary implementation
|
22
|
+
def closest(date)
|
23
|
+
return nil if !@days_of_week || @days_of_week.empty?
|
24
|
+
while date += IceCube::ONE_DAY
|
25
|
+
return date if validate(date)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def to_s
|
30
|
+
representation = ''
|
31
|
+
representation << 'on the '
|
32
|
+
representation << @days_of_week.map do |day, occ|
|
33
|
+
nice_numbers(occ) << ' ' << Date::DAYNAMES[day] << (occ.count != 1 ? 's' : '') unless @days_of_week.empty?
|
34
|
+
end.join(' and the ')
|
35
|
+
representation
|
36
|
+
end
|
30
37
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
return test if validate_day_of_week(test)
|
38
|
+
def to_ical
|
39
|
+
representation = 'BYDAY='
|
40
|
+
@days_of_week.each do |day, occ|
|
41
|
+
representation << occ.map { |o| o.to_s + IceCube::ICAL_DAYS[day] }.join(',')
|
42
|
+
end
|
43
|
+
representation
|
38
44
|
end
|
45
|
+
|
39
46
|
end
|
40
47
|
|
41
48
|
end
|
@@ -1,51 +1,49 @@
|
|
1
|
-
module
|
1
|
+
module IceCube
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
# Specify what days of the year this rule applies to.
|
8
|
-
# ie: Schedule.yearly(2).days_of_year(17, -1) would create a
|
9
|
-
# rule which occurs every 17th and last day of every other year.
|
10
|
-
# Note: you cannot combine month_of_year and day_of_year in the same rule.
|
11
|
-
def day_of_year(*days)
|
12
|
-
@validations[:day_of_year] ||= []
|
13
|
-
days.each do |day|
|
14
|
-
raise ArgumentError.new('Argument must be a valid day') if day.abs > 366
|
15
|
-
raise ArgumentError.new('Argument must be non-zero') if day == 0
|
16
|
-
@validations[:day_of_year] << day
|
3
|
+
class DayOfYearValidation < Validation
|
4
|
+
|
5
|
+
def initialize(rule)
|
6
|
+
@days_of_year = rule.validations[:day_of_year]
|
17
7
|
end
|
18
|
-
self
|
19
|
-
end
|
20
8
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
9
|
+
def validate(date)
|
10
|
+
return true if !@days_of_year || @days_of_year.empty?
|
11
|
+
@days_of_year.include?(date.yday) || @days_of_year.include?(date.yday - TimeUtil.days_in_year(date) - 1)
|
12
|
+
end
|
25
13
|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
14
|
+
def closest(date)
|
15
|
+
return nil if !@days_of_year || @days_of_year.empty?
|
16
|
+
#get some variables we need
|
17
|
+
days_in_year = TimeUtil.days_in_year(date)
|
18
|
+
days_left_in_this_year = days_in_year - date.yday
|
19
|
+
days_in_next_year = TimeUtil.days_in_year(Time.utc(date.year + 1, 1, 1))
|
20
|
+
# create a list of distances
|
21
|
+
distances = []
|
22
|
+
@days_of_year.each do |d|
|
23
|
+
if d > 0
|
24
|
+
distances << d - date.yday #today is 1, we want 20 (19)
|
25
|
+
distances << days_left_in_this_year + d #(364 + 20)
|
26
|
+
elsif d < 0
|
27
|
+
distances << (days_in_year + d + 1) - date.yday #today is 300, we want -1
|
28
|
+
distances << (days_in_next_year + d + 1) + days_left_in_this_year #today is 300, we want -70
|
29
|
+
end
|
41
30
|
end
|
31
|
+
#return the lowest distance
|
32
|
+
distances = distances.select { |d| d > 0 }
|
33
|
+
return nil if distances.empty?
|
34
|
+
# return the start of the proper day
|
35
|
+
goal = date + distances.min * IceCube::ONE_DAY
|
36
|
+
adjust(goal, date)
|
42
37
|
end
|
43
|
-
#return the lowest distance
|
44
|
-
distances = distances.select { |d| d > 0 }
|
45
|
-
return nil if distances.empty?
|
46
|
-
# return the start of the proper day
|
47
|
-
goal = date + distances.min * IceCube::ONE_DAY
|
48
|
-
adjust(goal, date)
|
49
|
-
end
|
50
38
|
|
39
|
+
def to_s
|
40
|
+
'on the ' << nice_numbers(@days_of_year.sort) << (@days_of_year.count == 1 ? ' day' : ' days') << ' of the year' unless @days_of_year.empty?
|
41
|
+
end
|
42
|
+
|
43
|
+
def to_ical
|
44
|
+
'BYYEARDAY=' << @days_of_year.join(',') unless @days_of_year.empty?
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
|
51
49
|
end
|
@@ -1,35 +1,38 @@
|
|
1
|
-
module
|
1
|
+
module IceCube
|
2
|
+
|
3
|
+
class HourOfDayValidation < Validation
|
2
4
|
|
3
|
-
|
4
|
-
|
5
|
-
end
|
6
|
-
|
7
|
-
def hour_of_day(*hours)
|
8
|
-
@validations[:hour_of_day] ||= []
|
9
|
-
hours.each do |hour|
|
10
|
-
raise ArgumentError.new('Argument must be a valid hour') unless hour < 24 && hour >= 0
|
11
|
-
@validations[:hour_of_day] << hour
|
5
|
+
def initialize(rule)
|
6
|
+
@hours_of_day = rule.validations[:hour_of_day]
|
12
7
|
end
|
13
|
-
self
|
14
|
-
end
|
15
8
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
9
|
+
def validate(date)
|
10
|
+
return true if !@hours_of_day || @hours_of_day.empty?
|
11
|
+
@hours_of_day.include?(date.hour)
|
12
|
+
end
|
20
13
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
14
|
+
def closest(date)
|
15
|
+
return nil if !@hours_of_day || @hours_of_day.empty?
|
16
|
+
# turn hours into hour of day
|
17
|
+
# hour >= 24 should fall into the next day
|
18
|
+
hours = @hours_of_day.map do |h|
|
19
|
+
h > date.hour ? h - date.hour : 24 - date.hour + h
|
20
|
+
end
|
21
|
+
hours.compact!
|
22
|
+
# go to the closest distance away, the start of that hour
|
23
|
+
closest_hour = hours.min
|
24
|
+
goal = date + IceCube::ONE_HOUR * closest_hour
|
25
|
+
adjust(goal, date)
|
26
|
+
end
|
27
|
+
|
28
|
+
def to_s
|
29
|
+
'on the ' << nice_numbers(@hours_of_day.sort) << (@hours_of_day.count == 1 ? ' hour' : ' hours') << ' of the day' unless @hours_of_day.empty?
|
27
30
|
end
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
31
|
+
|
32
|
+
def to_ical
|
33
|
+
'BYHOUR=' << @hours_of_day.join(',') unless @hours_of_day.empty?
|
34
|
+
end
|
35
|
+
|
33
36
|
end
|
34
37
|
|
35
38
|
end
|
@@ -1,35 +1,38 @@
|
|
1
|
-
module
|
1
|
+
module IceCube
|
2
|
+
|
3
|
+
class MinuteOfHourValidation < Validation
|
2
4
|
|
3
|
-
|
4
|
-
|
5
|
-
end
|
6
|
-
|
7
|
-
def minute_of_hour(*minutes)
|
8
|
-
@validations[:minute_of_hour] ||= []
|
9
|
-
minutes.each do |minute|
|
10
|
-
raise ArgumentError.new('Argument must be a valid minute') unless minute < 60 && minute >= 0
|
11
|
-
@validations[:minute_of_hour] << minute
|
5
|
+
def initialize(rule)
|
6
|
+
@minutes_of_hour = rule.validations[:minute_of_hour]
|
12
7
|
end
|
13
|
-
self
|
14
|
-
end
|
15
8
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
9
|
+
def validate(date)
|
10
|
+
return true if !@minutes_of_hour || @minutes_of_hour.empty?
|
11
|
+
@minutes_of_hour.include?(date.min)
|
12
|
+
end
|
20
13
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
14
|
+
def closest(date)
|
15
|
+
return nil if !@minutes_of_hour || @minutes_of_hour.empty?
|
16
|
+
# turn minutes into minutes of hour
|
17
|
+
# minute >= 60 should fall into the next hour
|
18
|
+
minutes = @minutes_of_hour.map do |m|
|
19
|
+
m > date.min ? m - date.min : 60 - date.min + m
|
20
|
+
end
|
21
|
+
minutes.compact!
|
22
|
+
# go to the closest distance away, the beginning of that minute
|
23
|
+
closest_minute = minutes.min
|
24
|
+
goal = date + closest_minute * IceCube::ONE_MINUTE
|
25
|
+
adjust(goal, date)
|
26
|
+
end
|
27
|
+
|
28
|
+
def to_s
|
29
|
+
'on the ' << nice_numbers(@minutes_of_hour.sort) << (@minutes_of_hour.count == 1 ? ' minute' : ' minutes') << ' of the hour' unless @minutes_of_hour.empty?
|
30
|
+
end
|
31
|
+
|
32
|
+
def to_ical
|
33
|
+
'BYMINUTE=' << @minutes_of_hour.join(',') unless @minutes_of_hour.empty?
|
27
34
|
end
|
28
|
-
|
29
|
-
# go to the closest distance away, the beginning of that minute
|
30
|
-
closest_minute = minutes.min
|
31
|
-
goal = date + closest_minute * IceCube::ONE_MINUTE
|
32
|
-
adjust(goal, date)
|
35
|
+
|
33
36
|
end
|
34
37
|
|
35
38
|
end
|
@@ -1,39 +1,38 @@
|
|
1
|
-
module
|
1
|
+
module IceCube
|
2
|
+
|
3
|
+
class MonthOfYearValidation < Validation
|
2
4
|
|
3
|
-
|
4
|
-
|
5
|
-
end
|
6
|
-
|
7
|
-
# Specify what months of the year this rule applies to.
|
8
|
-
# ie: Schedule.yearly(2).month_of_year(:january, :march) would create a
|
9
|
-
# rule which occurs every january and march, every other year
|
10
|
-
# Note: you cannot combine day_of_year and month_of_year in the same rule.
|
11
|
-
def month_of_year(*months)
|
12
|
-
@validations[:month_of_year] ||= []
|
13
|
-
months.each do |month|
|
14
|
-
raise ArgumentError.new('Argument must be a valid month') unless IceCube::MONTHS.has_key?(month)
|
15
|
-
@validations[:month_of_year] << IceCube::MONTHS[month]
|
5
|
+
def initialize(rule)
|
6
|
+
@months_of_year = rule.validations[:month_of_year]
|
16
7
|
end
|
17
|
-
self
|
18
|
-
end
|
19
8
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
9
|
+
def validate(date)
|
10
|
+
return true if !@months_of_year || @months_of_year.empty?
|
11
|
+
@months_of_year.include?(date.month)
|
12
|
+
end
|
24
13
|
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
14
|
+
def closest(date)
|
15
|
+
return nil if !@months_of_year || @months_of_year.empty?
|
16
|
+
# turn months into month of year
|
17
|
+
# month > 12 should fall into the next year
|
18
|
+
months = @months_of_year.map do |m|
|
19
|
+
m > date.month ? m - date.month : 12 - date.month + m
|
20
|
+
end
|
21
|
+
months.compact!
|
22
|
+
# go to the closest distance away
|
23
|
+
goal = date
|
24
|
+
months.min.times { goal += TimeUtil.days_in_month(goal) * IceCube::ONE_DAY }
|
25
|
+
adjust(goal, date)
|
26
|
+
end
|
27
|
+
|
28
|
+
def to_s
|
29
|
+
'in ' << @months_of_year.map { |m| Date::MONTHNAMES[m] }.join(', ') unless @months_of_year.empty?
|
30
|
+
end
|
31
|
+
|
32
|
+
def to_ical
|
33
|
+
'BYMONTH=' << @months_of_year.join(',') unless @months_of_year.empty?
|
31
34
|
end
|
32
|
-
|
33
|
-
# go to the closest distance away
|
34
|
-
goal = date
|
35
|
-
months.min.times { goal += TimeUtil.days_in_month(goal) * IceCube::ONE_DAY }
|
36
|
-
adjust(goal, date)
|
35
|
+
|
37
36
|
end
|
38
37
|
|
39
38
|
end
|
@@ -1,35 +1,38 @@
|
|
1
|
-
module
|
1
|
+
module IceCube
|
2
|
+
|
3
|
+
class SecondOfMinuteValidation < Validation
|
2
4
|
|
3
|
-
|
4
|
-
|
5
|
-
end
|
6
|
-
|
7
|
-
def second_of_minute(*seconds)
|
8
|
-
@validations[:second_of_minute] ||= []
|
9
|
-
seconds.each do |second|
|
10
|
-
raise ArgumentError.new('Argument must be a valid second') unless second < 60 && second >= 0
|
11
|
-
@validations[:second_of_minute] << second
|
5
|
+
def initialize(rule)
|
6
|
+
@seconds_of_minute = rule.validations[:second_of_minute]
|
12
7
|
end
|
13
|
-
self
|
14
|
-
end
|
15
8
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
9
|
+
def validate(date)
|
10
|
+
return true if !@seconds_of_minute || @seconds_of_minute.empty?
|
11
|
+
@seconds_of_minute.include?(date.sec)
|
12
|
+
end
|
20
13
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
14
|
+
def closest(date)
|
15
|
+
return nil if !@seconds_of_minute || @seconds_of_minute.empty?
|
16
|
+
# turn seconds into seconds of minute
|
17
|
+
# second >= 60 should fall into the next minute
|
18
|
+
seconds = @seconds_of_minute.map do |s|
|
19
|
+
s > date.sec ? s - date.sec : 60 - date.sec + s
|
20
|
+
end
|
21
|
+
seconds.compact!
|
22
|
+
# go to the closest distance away
|
23
|
+
closest_second = seconds.min
|
24
|
+
goal = date + closest_second
|
25
|
+
adjust(goal, date)
|
26
|
+
end
|
27
|
+
|
28
|
+
def to_s
|
29
|
+
'on the ' << nice_numbers(@seconds_of_minute.sort) << (@seconds_of_minute.count == 1 ? ' second' : ' seconds') << ' of the minute' unless @seconds_of_minute.empty?
|
27
30
|
end
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
31
|
+
|
32
|
+
def to_ical
|
33
|
+
'BYSECOND=' << @seconds_of_minute.join(',') unless @seconds_of_minute.empty?
|
34
|
+
end
|
35
|
+
|
33
36
|
end
|
34
37
|
|
35
38
|
end
|
data/lib/ice_cube/version.rb
CHANGED
metadata
CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
|
|
5
5
|
segments:
|
6
6
|
- 0
|
7
7
|
- 2
|
8
|
-
-
|
9
|
-
version: 0.2.
|
8
|
+
- 8
|
9
|
+
version: 0.2.8
|
10
10
|
platform: ruby
|
11
11
|
authors:
|
12
12
|
- John Crepezzi
|
@@ -14,7 +14,7 @@ autorequire:
|
|
14
14
|
bindir: bin
|
15
15
|
cert_chain: []
|
16
16
|
|
17
|
-
date: 2010-04-
|
17
|
+
date: 2010-04-08 00:00:00 -04:00
|
18
18
|
default_executable:
|
19
19
|
dependencies:
|
20
20
|
- !ruby/object:Gem::Dependency
|
@@ -49,6 +49,8 @@ files:
|
|
49
49
|
- lib/ice_cube/rules/yearly_rule.rb
|
50
50
|
- lib/ice_cube/schedule.rb
|
51
51
|
- lib/ice_cube/time_util.rb
|
52
|
+
- lib/ice_cube/validation.rb
|
53
|
+
- lib/ice_cube/validation_types.rb
|
52
54
|
- lib/ice_cube/validations/day.rb
|
53
55
|
- lib/ice_cube/validations/day_of_month.rb
|
54
56
|
- lib/ice_cube/validations/day_of_week.rb
|