ice_cube_conrad 0.8.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 +80 -0
- 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/deprecated.rb +28 -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/errors/zero_interval.rb +7 -0
- data/lib/ice_cube/rule.rb +182 -0
- data/lib/ice_cube/rules/daily_rule.rb +14 -0
- data/lib/ice_cube/rules/hourly_rule.rb +14 -0
- data/lib/ice_cube/rules/minutely_rule.rb +14 -0
- data/lib/ice_cube/rules/monthly_rule.rb +14 -0
- data/lib/ice_cube/rules/secondly_rule.rb +13 -0
- data/lib/ice_cube/rules/weekly_rule.rb +14 -0
- data/lib/ice_cube/rules/yearly_rule.rb +14 -0
- data/lib/ice_cube/schedule.rb +414 -0
- data/lib/ice_cube/single_occurrence_rule.rb +28 -0
- data/lib/ice_cube/time_util.rb +250 -0
- data/lib/ice_cube/validated_rule.rb +108 -0
- data/lib/ice_cube/validations/count.rb +56 -0
- data/lib/ice_cube/validations/daily_interval.rb +55 -0
- data/lib/ice_cube/validations/day.rb +65 -0
- data/lib/ice_cube/validations/day_of_month.rb +52 -0
- data/lib/ice_cube/validations/day_of_week.rb +70 -0
- data/lib/ice_cube/validations/day_of_year.rb +55 -0
- data/lib/ice_cube/validations/hour_of_day.rb +52 -0
- data/lib/ice_cube/validations/hourly_interval.rb +57 -0
- data/lib/ice_cube/validations/lock.rb +47 -0
- data/lib/ice_cube/validations/minute_of_hour.rb +51 -0
- data/lib/ice_cube/validations/minutely_interval.rb +57 -0
- data/lib/ice_cube/validations/month_of_year.rb +49 -0
- data/lib/ice_cube/validations/monthly_interval.rb +51 -0
- data/lib/ice_cube/validations/schedule_lock.rb +41 -0
- data/lib/ice_cube/validations/second_of_minute.rb +49 -0
- data/lib/ice_cube/validations/secondly_interval.rb +54 -0
- data/lib/ice_cube/validations/until.rb +51 -0
- data/lib/ice_cube/validations/weekly_interval.rb +60 -0
- data/lib/ice_cube/validations/yearly_interval.rb +49 -0
- data/lib/ice_cube/version.rb +5 -0
- data/spec/spec_helper.rb +11 -0
- metadata +120 -0
@@ -0,0 +1,65 @@
|
|
1
|
+
require 'date'
|
2
|
+
|
3
|
+
module IceCube
|
4
|
+
|
5
|
+
module Validations::Day
|
6
|
+
|
7
|
+
def day(*days)
|
8
|
+
days.each do |day|
|
9
|
+
day = TimeUtil.symbol_to_day(day.to_sym) if day.is_a?(Symbol) or day.is_a?(String)
|
10
|
+
validations_for(:day) << Validation.new(day)
|
11
|
+
end
|
12
|
+
clobber_base_validations(:wday, :day)
|
13
|
+
self
|
14
|
+
end
|
15
|
+
|
16
|
+
class Validation
|
17
|
+
|
18
|
+
include Validations::Lock
|
19
|
+
|
20
|
+
attr_reader :day
|
21
|
+
alias :value :day
|
22
|
+
|
23
|
+
def initialize(day)
|
24
|
+
@day = day
|
25
|
+
end
|
26
|
+
|
27
|
+
def build_s(builder)
|
28
|
+
builder.piece(:day) << day
|
29
|
+
end
|
30
|
+
|
31
|
+
def build_hash(builder)
|
32
|
+
builder.validations_array(:day) << day
|
33
|
+
end
|
34
|
+
|
35
|
+
def build_ical(builder)
|
36
|
+
ical_day = IcalBuilder.fixnum_to_ical_day(day)
|
37
|
+
# Only add if there aren't others from day_of_week that override
|
38
|
+
if builder['BYDAY'].none? { |b| b.end_with?(ical_day) }
|
39
|
+
builder['BYDAY'] << ical_day
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def type
|
44
|
+
:wday
|
45
|
+
end
|
46
|
+
|
47
|
+
StringBuilder.register_formatter(:day) do |validation_days|
|
48
|
+
# sort the days
|
49
|
+
validation_days.sort!
|
50
|
+
# pick the right shortening, if applicable
|
51
|
+
if validation_days == [0, 6]
|
52
|
+
'on Weekends'
|
53
|
+
elsif validation_days == (1..5).to_a
|
54
|
+
'on Weekdays'
|
55
|
+
else
|
56
|
+
segments = validation_days.map { |d| "#{Date::DAYNAMES[d]}s" }
|
57
|
+
"on #{StringBuilder.sentence(segments)}"
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
module IceCube
|
2
|
+
|
3
|
+
module Validations::DayOfMonth
|
4
|
+
|
5
|
+
include Validations::Lock
|
6
|
+
|
7
|
+
def day_of_month(*days)
|
8
|
+
days.each do |day|
|
9
|
+
validations_for(:day_of_month) << Validation.new(day)
|
10
|
+
end
|
11
|
+
clobber_base_validations(:day, :wday)
|
12
|
+
self
|
13
|
+
end
|
14
|
+
|
15
|
+
class Validation
|
16
|
+
|
17
|
+
include Validations::Lock
|
18
|
+
|
19
|
+
StringBuilder.register_formatter(:day_of_month) do |entries|
|
20
|
+
str = "on the #{StringBuilder.sentence(entries)} "
|
21
|
+
str << (entries.size == 1 ? 'day of the month' : 'days of the month')
|
22
|
+
str
|
23
|
+
end
|
24
|
+
|
25
|
+
attr_reader :day
|
26
|
+
alias :value :day
|
27
|
+
|
28
|
+
def initialize(day)
|
29
|
+
@day = day
|
30
|
+
end
|
31
|
+
|
32
|
+
def build_s(builder)
|
33
|
+
builder.piece(:day_of_month) << StringBuilder.nice_number(day)
|
34
|
+
end
|
35
|
+
|
36
|
+
def build_hash(builder)
|
37
|
+
builder.validations_array(:day_of_month) << day
|
38
|
+
end
|
39
|
+
|
40
|
+
def build_ical(builder)
|
41
|
+
builder['BYMONTHDAY'] << day
|
42
|
+
end
|
43
|
+
|
44
|
+
def type
|
45
|
+
:day
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
module IceCube
|
2
|
+
|
3
|
+
module Validations::DayOfWeek
|
4
|
+
|
5
|
+
def day_of_week(dows)
|
6
|
+
dows.each do |day, occs|
|
7
|
+
occs.each do |occ|
|
8
|
+
day = TimeUtil.symbol_to_day(day.to_sym) if day.is_a?(Symbol) or day.is_a?(String)
|
9
|
+
validations_for(:day_of_week) << Validation.new(day, occ)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
clobber_base_validations :day, :wday
|
13
|
+
self
|
14
|
+
end
|
15
|
+
|
16
|
+
class Validation
|
17
|
+
|
18
|
+
attr_reader :day, :occ
|
19
|
+
|
20
|
+
StringBuilder.register_formatter(:day_of_week) do |segments|
|
21
|
+
'on the ' + segments.join(' when it is the ')
|
22
|
+
end
|
23
|
+
|
24
|
+
def type
|
25
|
+
:day
|
26
|
+
end
|
27
|
+
|
28
|
+
def build_s(builder)
|
29
|
+
builder.piece(:day_of_week) << "#{StringBuilder.nice_number(occ)} #{Date::DAYNAMES[day]}"
|
30
|
+
end
|
31
|
+
|
32
|
+
def build_ical(builder)
|
33
|
+
ical_day = IcalBuilder.fixnum_to_ical_day(day)
|
34
|
+
# Delete any with this day and no occ first
|
35
|
+
builder['BYDAY'].delete_if { |d| d == ical_day }
|
36
|
+
builder['BYDAY'] << "#{occ}#{ical_day}"
|
37
|
+
end
|
38
|
+
|
39
|
+
def build_hash(builder)
|
40
|
+
builder.validations[:day_of_week] ||= {}
|
41
|
+
arr = (builder.validations[:day_of_week][day] ||= [])
|
42
|
+
arr << occ
|
43
|
+
end
|
44
|
+
|
45
|
+
def initialize(day, occ)
|
46
|
+
@day = day
|
47
|
+
@occ = occ
|
48
|
+
end
|
49
|
+
|
50
|
+
def validate(time, schedule)
|
51
|
+
# count the days to the weekday
|
52
|
+
sum = day >= time.wday ? day - time.wday : 7 - time.wday + day
|
53
|
+
wrapper = TimeUtil::TimeWrapper.new(time)
|
54
|
+
wrapper.add :day, sum
|
55
|
+
# and then count the week until a viable occ
|
56
|
+
loop do
|
57
|
+
which_occ, num_occ = TimeUtil.which_occurrence_in_month(wrapper.to_time, day)
|
58
|
+
this_occ = occ < 0 ? num_occ + occ + 1 : occ
|
59
|
+
break if which_occ == this_occ
|
60
|
+
sum += 7
|
61
|
+
wrapper.add :day, 7 # one week
|
62
|
+
end
|
63
|
+
sum
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
module IceCube
|
2
|
+
|
3
|
+
module Validations::DayOfYear
|
4
|
+
|
5
|
+
def day_of_year(*days)
|
6
|
+
days.each do |day|
|
7
|
+
validations_for(:day_of_year) << Validation.new(day)
|
8
|
+
end
|
9
|
+
clobber_base_validations(:month, :day, :wday)
|
10
|
+
self
|
11
|
+
end
|
12
|
+
|
13
|
+
class Validation
|
14
|
+
|
15
|
+
attr_reader :day
|
16
|
+
|
17
|
+
StringBuilder.register_formatter(:day_of_year) do |entries|
|
18
|
+
str = "on the #{StringBuilder.sentence(entries)} "
|
19
|
+
str << (entries.size == 1 ? 'day of the year' : 'days of the year')
|
20
|
+
str
|
21
|
+
end
|
22
|
+
|
23
|
+
def initialize(day)
|
24
|
+
@day = day
|
25
|
+
end
|
26
|
+
|
27
|
+
def type
|
28
|
+
:day
|
29
|
+
end
|
30
|
+
|
31
|
+
def build_s(builder)
|
32
|
+
builder.piece(:day_of_year) << StringBuilder.nice_number(day)
|
33
|
+
end
|
34
|
+
|
35
|
+
def build_hash(builder)
|
36
|
+
builder.validations_array(:day_of_year) << day
|
37
|
+
end
|
38
|
+
|
39
|
+
def build_ical(builder)
|
40
|
+
builder['BYYEARDAY'] << day
|
41
|
+
end
|
42
|
+
|
43
|
+
def validate(time, schedule)
|
44
|
+
days_in_year = TimeUtil.days_in_year(time)
|
45
|
+
the_day = day < 0 ? day + days_in_year : day
|
46
|
+
# compute the diff
|
47
|
+
diff = the_day - time.yday
|
48
|
+
diff >= 0 ? diff : diff + days_in_year
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
module IceCube
|
2
|
+
|
3
|
+
module Validations::HourOfDay
|
4
|
+
|
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)
|
11
|
+
end
|
12
|
+
clobber_base_validations(:hour)
|
13
|
+
self
|
14
|
+
end
|
15
|
+
|
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
|
47
|
+
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
@@ -0,0 +1,57 @@
|
|
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[:interval] = interval
|
25
|
+
end
|
26
|
+
|
27
|
+
def build_ical(builder)
|
28
|
+
builder['FREQ'] << 'HOURLY'
|
29
|
+
unless interval == 1
|
30
|
+
builder['INTERVAL'] << interval
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def initialize(interval)
|
35
|
+
@interval = interval
|
36
|
+
end
|
37
|
+
|
38
|
+
def dst_adjust?
|
39
|
+
false
|
40
|
+
end
|
41
|
+
|
42
|
+
def validate(time, schedule)
|
43
|
+
raise ZeroInterval if interval == 0
|
44
|
+
start_time = schedule.start_time
|
45
|
+
sec = (time.to_i - time.to_i % ONE_HOUR) -
|
46
|
+
(start_time.to_i - start_time.to_i % ONE_HOUR)
|
47
|
+
hours = sec / ONE_HOUR
|
48
|
+
unless hours % interval == 0
|
49
|
+
interval - (hours % interval)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
|
57
|
+
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
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module IceCube
|
2
|
+
|
3
|
+
module Validations::MinuteOfHour
|
4
|
+
|
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)
|
10
|
+
end
|
11
|
+
clobber_base_validations(:min)
|
12
|
+
self
|
13
|
+
end
|
14
|
+
|
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
|
+
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|