ice_cube 0.2.7 → 0.2.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|