ice_cube 0.6.14 → 0.7.0
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 +63 -37
- data/lib/ice_cube/builders/hash_builder.rb +27 -0
- data/lib/ice_cube/builders/ical_builder.rb +59 -0
- data/lib/ice_cube/builders/string_builder.rb +74 -0
- data/lib/ice_cube/errors/count_exceeded.rb +7 -0
- data/lib/ice_cube/errors/until_exceeded.rb +7 -0
- data/lib/ice_cube/rule.rb +85 -147
- data/lib/ice_cube/rules/daily_rule.rb +5 -27
- data/lib/ice_cube/rules/hourly_rule.rb +6 -26
- data/lib/ice_cube/rules/minutely_rule.rb +5 -25
- data/lib/ice_cube/rules/monthly_rule.rb +6 -30
- data/lib/ice_cube/rules/secondly_rule.rb +5 -26
- data/lib/ice_cube/rules/weekly_rule.rb +5 -36
- data/lib/ice_cube/rules/yearly_rule.rb +8 -34
- data/lib/ice_cube/schedule.rb +257 -229
- data/lib/ice_cube/single_occurrence_rule.rb +28 -0
- data/lib/ice_cube/time_util.rb +202 -76
- data/lib/ice_cube/validated_rule.rb +107 -0
- data/lib/ice_cube/validations/count.rb +56 -0
- data/lib/ice_cube/validations/daily_interval.rb +51 -0
- data/lib/ice_cube/validations/day.rb +45 -31
- data/lib/ice_cube/validations/day_of_month.rb +44 -44
- data/lib/ice_cube/validations/day_of_week.rb +60 -47
- data/lib/ice_cube/validations/day_of_year.rb +48 -44
- data/lib/ice_cube/validations/hour_of_day.rb +42 -30
- data/lib/ice_cube/validations/hourly_interval.rb +50 -0
- data/lib/ice_cube/validations/lock.rb +47 -0
- data/lib/ice_cube/validations/minute_of_hour.rb +42 -31
- data/lib/ice_cube/validations/minutely_interval.rb +50 -0
- data/lib/ice_cube/validations/month_of_year.rb +39 -30
- data/lib/ice_cube/validations/monthly_interval.rb +47 -0
- data/lib/ice_cube/validations/schedule_lock.rb +41 -0
- data/lib/ice_cube/validations/second_of_minute.rb +39 -30
- data/lib/ice_cube/validations/secondly_interval.rb +50 -0
- data/lib/ice_cube/validations/until.rb +49 -0
- data/lib/ice_cube/validations/weekly_interval.rb +50 -0
- data/lib/ice_cube/validations/yearly_interval.rb +45 -0
- data/lib/ice_cube/version.rb +2 -2
- data/spec/spec_helper.rb +13 -0
- metadata +50 -9
- data/lib/ice_cube/rule_occurrence.rb +0 -94
- data/lib/ice_cube/validation.rb +0 -44
- data/lib/ice_cube/validation_types.rb +0 -137
@@ -1,40 +1,52 @@
|
|
1
1
|
module IceCube
|
2
2
|
|
3
|
-
|
3
|
+
module Validations::HourOfDay
|
4
4
|
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
def validate(date)
|
12
|
-
return true if !@hours_of_day || @hours_of_day.empty?
|
13
|
-
@hours_of_day.include?(date.hour)
|
14
|
-
end
|
15
|
-
|
16
|
-
def closest(date)
|
17
|
-
return nil if !@hours_of_day || @hours_of_day.empty?
|
18
|
-
# turn hours into hour of day
|
19
|
-
# hour >= 24 should fall into the next day
|
20
|
-
hours = @hours_of_day.map do |h|
|
21
|
-
h > date.hour ? h - date.hour : 24 - date.hour + h
|
5
|
+
include Validations::Lock
|
6
|
+
|
7
|
+
# Add hour of day validations
|
8
|
+
def hour_of_day(*hours)
|
9
|
+
hours.each do |hour|
|
10
|
+
validations_for(:hour_of_day) << Validation.new(hour)
|
22
11
|
end
|
23
|
-
|
24
|
-
|
25
|
-
closest_hour = hours.min
|
26
|
-
goal = date + IceCube::ONE_HOUR * closest_hour
|
27
|
-
self.class.adjust(goal, date)
|
12
|
+
clobber_base_validations(:hour)
|
13
|
+
self
|
28
14
|
end
|
29
15
|
|
30
|
-
|
31
|
-
|
32
|
-
|
16
|
+
class Validation
|
17
|
+
|
18
|
+
include Validations::Lock
|
19
|
+
|
20
|
+
StringBuilder.register_formatter(:hour_of_day) do |segments|
|
21
|
+
str = "on the #{StringBuilder.sentence(segments)} "
|
22
|
+
str << (segments.size == 1 ? 'hour of the day' : 'hours of the day')
|
23
|
+
end
|
24
|
+
|
25
|
+
attr_reader :hour
|
26
|
+
alias :value :hour
|
27
|
+
|
28
|
+
def initialize(hour)
|
29
|
+
@hour = hour
|
30
|
+
end
|
31
|
+
|
32
|
+
def build_s(builder)
|
33
|
+
builder.piece(:hour_of_day) << StringBuilder.nice_number(hour)
|
34
|
+
end
|
35
|
+
|
36
|
+
def type
|
37
|
+
:hour
|
38
|
+
end
|
39
|
+
|
40
|
+
def build_hash(builder)
|
41
|
+
builder.validations_array(:hour_of_day) << hour
|
42
|
+
end
|
43
|
+
|
44
|
+
def build_ical(builder)
|
45
|
+
builder['BYHOUR'] << hour
|
46
|
+
end
|
33
47
|
|
34
|
-
def to_ical
|
35
|
-
'BYHOUR=' << @hours_of_day.join(',') unless @hours_of_day.empty?
|
36
48
|
end
|
37
|
-
|
49
|
+
|
38
50
|
end
|
39
|
-
|
51
|
+
|
40
52
|
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module IceCube
|
2
|
+
|
3
|
+
module Validations::HourlyInterval
|
4
|
+
|
5
|
+
def interval(interval)
|
6
|
+
validations_for(:interval) << Validation.new(interval)
|
7
|
+
clobber_base_validations(:hour)
|
8
|
+
self
|
9
|
+
end
|
10
|
+
|
11
|
+
class Validation
|
12
|
+
|
13
|
+
attr_reader :interval
|
14
|
+
|
15
|
+
def type
|
16
|
+
:hour
|
17
|
+
end
|
18
|
+
|
19
|
+
def build_s(builder)
|
20
|
+
builder.base = interval == 1 ? 'Hourly' : "Every #{interval} hours"
|
21
|
+
end
|
22
|
+
|
23
|
+
def build_hash(builder)
|
24
|
+
builder.validations[:interval] = interval
|
25
|
+
end
|
26
|
+
|
27
|
+
def build_ical(builder)
|
28
|
+
builder['FREQ'] << 'HOURLY'
|
29
|
+
end
|
30
|
+
|
31
|
+
def initialize(interval)
|
32
|
+
@interval = interval
|
33
|
+
end
|
34
|
+
|
35
|
+
def dst_adjust?
|
36
|
+
false
|
37
|
+
end
|
38
|
+
|
39
|
+
def validate(time, schedule)
|
40
|
+
hours = (time.to_i - schedule.start_time.to_i) / IceCube::ONE_HOUR
|
41
|
+
unless hours % interval == 0
|
42
|
+
interval - (hours % interval)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module IceCube
|
2
|
+
|
3
|
+
# A validation mixin that will lock the +type field to
|
4
|
+
# +value or +schedule.start_time.send(type) if value is nil
|
5
|
+
|
6
|
+
module Validations::Lock
|
7
|
+
|
8
|
+
INTERVALS = { :hour => 24, :min => 60, :sec => 60, :month => 12, :wday => 7 }
|
9
|
+
def validate(time, schedule)
|
10
|
+
return send(:"validate_#{type}_lock", time, schedule) unless INTERVALS[type]
|
11
|
+
start = value || schedule.start_time.send(type)
|
12
|
+
start = INTERVALS[type] + start if start < 0 # handle negative values
|
13
|
+
start >= time.send(type) ? start - time.send(type) : INTERVALS[type] - time.send(type) + start
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
# Needs to be custom since we don't know the days in the month
|
19
|
+
# (meaning, its not a fixed interval)
|
20
|
+
def validate_day_lock(time, schedule)
|
21
|
+
start = value || schedule.start_time.day
|
22
|
+
days_in_this_month = TimeUtil.days_in_month(time)
|
23
|
+
# If this number is positive, then follow our normal procedure
|
24
|
+
if start > 0
|
25
|
+
return start >= time.day ? start - time.day : days_in_this_month - time.day + start
|
26
|
+
end
|
27
|
+
# If the number is negative, and it resolved against the current month
|
28
|
+
# puts it in the future, just return the difference
|
29
|
+
days_in_this_month = TimeUtil.days_in_month(time)
|
30
|
+
start_one = days_in_this_month + start + 1
|
31
|
+
if start_one >= time.day
|
32
|
+
return start_one - time.day
|
33
|
+
end
|
34
|
+
# Otherwise, we need to figure out the meaning of the value
|
35
|
+
# in the next month, and then figure out how to get there
|
36
|
+
days_in_next_month = TimeUtil.days_in_next_month(time)
|
37
|
+
start_two = days_in_next_month + start + 1
|
38
|
+
if start_two >= time.day
|
39
|
+
days_in_this_month + start_two - time.day
|
40
|
+
else
|
41
|
+
days_in_next_month + start_two - time.day
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
@@ -1,40 +1,51 @@
|
|
1
1
|
module IceCube
|
2
2
|
|
3
|
-
|
3
|
+
module Validations::MinuteOfHour
|
4
4
|
|
5
|
-
|
6
|
-
|
7
|
-
def
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
def validate(date)
|
12
|
-
return true if !@minutes_of_hour || @minutes_of_hour.empty?
|
13
|
-
@minutes_of_hour.include?(date.min)
|
14
|
-
end
|
15
|
-
|
16
|
-
def closest(date)
|
17
|
-
return nil if !@minutes_of_hour || @minutes_of_hour.empty?
|
18
|
-
# turn minutes into minutes of hour
|
19
|
-
# minute >= 60 should fall into the next hour
|
20
|
-
minutes = @minutes_of_hour.map do |m|
|
21
|
-
m > date.min ? m - date.min : 60 - date.min + m
|
5
|
+
include Validations::Lock
|
6
|
+
|
7
|
+
def minute_of_hour(*minutes)
|
8
|
+
minutes.each do |minute|
|
9
|
+
validations_for(:minute_of_hour) << Validation.new(minute)
|
22
10
|
end
|
23
|
-
|
24
|
-
|
25
|
-
closest_minute = minutes.min
|
26
|
-
goal = date + closest_minute * IceCube::ONE_MINUTE
|
27
|
-
self.class.adjust(goal, date)
|
28
|
-
end
|
29
|
-
|
30
|
-
def to_s
|
31
|
-
'on the ' << self.class.nice_numbers(@minutes_of_hour) << (@minutes_of_hour.size == 1 ? ' minute' : ' minutes') << ' of the hour' unless @minutes_of_hour.empty?
|
11
|
+
clobber_base_validations(:min)
|
12
|
+
self
|
32
13
|
end
|
33
14
|
|
34
|
-
|
35
|
-
|
15
|
+
class Validation
|
16
|
+
|
17
|
+
include Validations::Lock
|
18
|
+
|
19
|
+
StringBuilder.register_formatter(:minute_of_hour) do |segments|
|
20
|
+
str = "on the #{StringBuilder.sentence(segments)} "
|
21
|
+
str << (segments.size == 1 ? 'minute of the hour' : 'minutes of the hour')
|
22
|
+
end
|
23
|
+
|
24
|
+
attr_reader :minute
|
25
|
+
alias :value :minute
|
26
|
+
|
27
|
+
def initialize(minute)
|
28
|
+
@minute = minute
|
29
|
+
end
|
30
|
+
|
31
|
+
def build_s(builder)
|
32
|
+
builder.piece(:minute_of_hour) << StringBuilder.nice_number(minute)
|
33
|
+
end
|
34
|
+
|
35
|
+
def type
|
36
|
+
:min
|
37
|
+
end
|
38
|
+
|
39
|
+
def build_hash(builder)
|
40
|
+
builder.validations_array(:minute_of_hour) << minute
|
41
|
+
end
|
42
|
+
|
43
|
+
def build_ical(builder)
|
44
|
+
builder['BYMINUTE'] << minute
|
45
|
+
end
|
46
|
+
|
36
47
|
end
|
37
|
-
|
48
|
+
|
38
49
|
end
|
39
|
-
|
50
|
+
|
40
51
|
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module IceCube
|
2
|
+
|
3
|
+
module Validations::MinutelyInterval
|
4
|
+
|
5
|
+
def interval(interval)
|
6
|
+
validations_for(:interval) << Validation.new(interval)
|
7
|
+
clobber_base_validations(:min)
|
8
|
+
self
|
9
|
+
end
|
10
|
+
|
11
|
+
class Validation
|
12
|
+
|
13
|
+
attr_reader :interval
|
14
|
+
|
15
|
+
def type
|
16
|
+
:min
|
17
|
+
end
|
18
|
+
|
19
|
+
def dst_adjust?
|
20
|
+
false
|
21
|
+
end
|
22
|
+
|
23
|
+
def build_s(builder)
|
24
|
+
builder.base = interval == 1 ? 'Minutely' : "Every #{interval} minutes"
|
25
|
+
end
|
26
|
+
|
27
|
+
def build_ical(builder)
|
28
|
+
builder['FREQ'] << 'MINUTELY'
|
29
|
+
end
|
30
|
+
|
31
|
+
def build_hash(builder)
|
32
|
+
builder.validations[:interval] = interval
|
33
|
+
end
|
34
|
+
|
35
|
+
def initialize(interval)
|
36
|
+
@interval = interval
|
37
|
+
end
|
38
|
+
|
39
|
+
def validate(time, schedule)
|
40
|
+
minutes = (time.to_i - schedule.start_time.to_i) / IceCube::ONE_MINUTE
|
41
|
+
unless minutes % interval == 0
|
42
|
+
interval - (minutes % interval)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
@@ -1,40 +1,49 @@
|
|
1
1
|
module IceCube
|
2
2
|
|
3
|
-
|
3
|
+
module Validations::MonthOfYear
|
4
4
|
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
end
|
10
|
-
|
11
|
-
def validate(date)
|
12
|
-
return true if !@months_of_year || @months_of_year.empty?
|
13
|
-
@months_of_year.include?(date.month)
|
14
|
-
end
|
15
|
-
|
16
|
-
def closest(date)
|
17
|
-
return nil if !@months_of_year || @months_of_year.empty?
|
18
|
-
# turn months into month of year
|
19
|
-
# month > 12 should fall into the next year
|
20
|
-
months = @months_of_year.map do |m|
|
21
|
-
m > date.month ? m - date.month : 12 - date.month + m
|
5
|
+
def month_of_year(*months)
|
6
|
+
months.each do |month|
|
7
|
+
month = TimeUtil.symbol_to_month(month) if month.is_a?(Symbol)
|
8
|
+
validations_for(:month_of_year) << Validation.new(month)
|
22
9
|
end
|
23
|
-
|
24
|
-
|
25
|
-
goal = date
|
26
|
-
months.min.times { goal += TimeUtil.days_in_month(goal) * IceCube::ONE_DAY }
|
27
|
-
self.class.adjust(goal, date)
|
10
|
+
clobber_base_validations :month
|
11
|
+
self
|
28
12
|
end
|
29
13
|
|
30
|
-
|
31
|
-
|
32
|
-
|
14
|
+
class Validation
|
15
|
+
|
16
|
+
include Validations::Lock
|
17
|
+
|
18
|
+
attr_reader :month
|
19
|
+
alias :value :month
|
20
|
+
|
21
|
+
def initialize(month)
|
22
|
+
@month = month
|
23
|
+
end
|
24
|
+
|
25
|
+
def build_s(builder)
|
26
|
+
builder.piece(:month_of_year) << Date::MONTHNAMES[month]
|
27
|
+
end
|
28
|
+
|
29
|
+
def build_hash(builder)
|
30
|
+
builder.validations_array(:month_of_year) << month
|
31
|
+
end
|
32
|
+
|
33
|
+
def build_ical(builder)
|
34
|
+
builder['BYMONTH'] << month
|
35
|
+
end
|
36
|
+
|
37
|
+
def type
|
38
|
+
:month
|
39
|
+
end
|
40
|
+
|
41
|
+
StringBuilder.register_formatter(:month_of_year) do |segments|
|
42
|
+
"in #{StringBuilder.sentence(segments)}"
|
43
|
+
end
|
33
44
|
|
34
|
-
def to_ical
|
35
|
-
'BYMONTH=' << @months_of_year.join(',') unless @months_of_year.empty?
|
36
45
|
end
|
37
|
-
|
46
|
+
|
38
47
|
end
|
39
|
-
|
48
|
+
|
40
49
|
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module IceCube
|
2
|
+
|
3
|
+
module Validations::MonthlyInterval
|
4
|
+
|
5
|
+
def interval(interval = 1)
|
6
|
+
validations_for(:interval) << Validation.new(interval)
|
7
|
+
clobber_base_validations(:month)
|
8
|
+
self
|
9
|
+
end
|
10
|
+
|
11
|
+
class Validation
|
12
|
+
|
13
|
+
attr_reader :interval
|
14
|
+
|
15
|
+
def type
|
16
|
+
:month
|
17
|
+
end
|
18
|
+
|
19
|
+
def build_s(builder)
|
20
|
+
builder.base = interval == 1 ? 'Monthly' : "Every #{interval} months"
|
21
|
+
end
|
22
|
+
|
23
|
+
def build_ical(builder)
|
24
|
+
builder['FREQ'] << 'MONTHLY'
|
25
|
+
end
|
26
|
+
|
27
|
+
def build_hash(builder)
|
28
|
+
builder.validations[:interval] = interval
|
29
|
+
end
|
30
|
+
|
31
|
+
def initialize(interval)
|
32
|
+
@interval = interval
|
33
|
+
end
|
34
|
+
|
35
|
+
def validate(time, schedule)
|
36
|
+
start_time = schedule.start_time
|
37
|
+
months_to_start = (time.month - start_time.month) + (time.year - start_time.year) * 12
|
38
|
+
unless months_to_start % interval == 0
|
39
|
+
interval - (months_to_start % interval)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module IceCube
|
2
|
+
|
3
|
+
module Validations::ScheduleLock
|
4
|
+
|
5
|
+
# Lock the given times down the schedule's start_time for that position
|
6
|
+
# These locks are all clobberable by other rules of the same #type
|
7
|
+
# using clobber_base_validation
|
8
|
+
def schedule_lock(*types)
|
9
|
+
types.each do |type|
|
10
|
+
validations_for(:"base_#{type}") << Validation.new(type)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
# A validation used for locking time into a certain value
|
15
|
+
class Validation
|
16
|
+
|
17
|
+
include Validations::Lock
|
18
|
+
|
19
|
+
attr_reader :type, :value
|
20
|
+
|
21
|
+
def initialize(type)
|
22
|
+
@type = type
|
23
|
+
end
|
24
|
+
|
25
|
+
# no -op
|
26
|
+
def build_s(builder)
|
27
|
+
end
|
28
|
+
|
29
|
+
# no -op
|
30
|
+
def build_ical(builder)
|
31
|
+
end
|
32
|
+
|
33
|
+
# no -op
|
34
|
+
def build_hash(builder)
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|