ice_cube 0.6.14 → 0.7.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
@@ -0,0 +1,51 @@
|
|
1
|
+
module IceCube
|
2
|
+
|
3
|
+
module Validations::DailyInterval
|
4
|
+
|
5
|
+
# Add a new interval validation
|
6
|
+
def interval(interval)
|
7
|
+
validations_for(:interval) << Validation.new(interval)
|
8
|
+
clobber_base_validations(:wday, :day)
|
9
|
+
self
|
10
|
+
end
|
11
|
+
|
12
|
+
# A validation for checking to make sure that a time
|
13
|
+
# is inside of a certain DailyInterval
|
14
|
+
class Validation
|
15
|
+
|
16
|
+
attr_reader :interval
|
17
|
+
|
18
|
+
def initialize(interval)
|
19
|
+
@interval = interval
|
20
|
+
end
|
21
|
+
|
22
|
+
def build_s(builder)
|
23
|
+
builder.base = interval == 1 ? 'Daily' : "Every #{interval} days"
|
24
|
+
end
|
25
|
+
|
26
|
+
def build_hash(builder)
|
27
|
+
builder.validations[:interval] = interval
|
28
|
+
end
|
29
|
+
|
30
|
+
def build_ical(builder)
|
31
|
+
builder['FREQ'] << 'DAILY'
|
32
|
+
end
|
33
|
+
|
34
|
+
def type
|
35
|
+
:day
|
36
|
+
end
|
37
|
+
|
38
|
+
def validate(time, schedule)
|
39
|
+
time_date = Date.new(time.year, time.month, time.day)
|
40
|
+
start_date = Date.new(schedule.start_time.year, schedule.start_time.month, schedule.start_time.day)
|
41
|
+
days = time_date - start_date
|
42
|
+
unless days % interval === 0
|
43
|
+
interval - (days % interval)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
@@ -1,41 +1,55 @@
|
|
1
|
-
|
1
|
+
require 'date'
|
2
2
|
|
3
|
-
|
3
|
+
module IceCube
|
4
4
|
|
5
|
-
|
6
|
-
|
7
|
-
def initialize(rule)
|
8
|
-
@days = rule.validations[:day]
|
9
|
-
@rule = rule
|
10
|
-
end
|
5
|
+
module Validations::Day
|
11
6
|
|
12
|
-
def
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
def closest(date)
|
18
|
-
return nil if !@days || @days.empty?
|
19
|
-
# turn days into distances
|
20
|
-
days = @days.map do |d|
|
21
|
-
d > date.wday ? (d - date.wday) : (7 - date.wday + d)
|
7
|
+
def day(*days)
|
8
|
+
days.each do |day|
|
9
|
+
day = TimeUtil.symbol_to_day(day) if day.is_a?(Symbol)
|
10
|
+
validations_for(:day) << Validation.new(day)
|
22
11
|
end
|
23
|
-
|
24
|
-
|
25
|
-
goal = date + days.min * IceCube::ONE_DAY
|
26
|
-
self.class.adjust(goal, date)
|
27
|
-
end
|
28
|
-
|
29
|
-
def to_s
|
30
|
-
days_dup = (@days - @rule.validations[:day_of_week].keys if @rule.validations[:day_of_week]) || @days # don't list twice
|
31
|
-
'on ' << self.class.sentence(days_dup.map { |d| Date::DAYNAMES[d] + 's' }) unless days_dup.empty?
|
12
|
+
clobber_base_validations(:wday, :day)
|
13
|
+
self
|
32
14
|
end
|
33
15
|
|
34
|
-
|
35
|
-
|
36
|
-
|
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) << "#{Date::DAYNAMES[day]}s"
|
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 |segments|
|
48
|
+
"on #{StringBuilder.sentence(segments)}"
|
49
|
+
end
|
50
|
+
|
37
51
|
end
|
38
|
-
|
52
|
+
|
39
53
|
end
|
40
54
|
|
41
55
|
end
|
@@ -1,52 +1,52 @@
|
|
1
1
|
module IceCube
|
2
2
|
|
3
|
-
|
3
|
+
module Validations::DayOfMonth
|
4
4
|
|
5
|
-
|
6
|
-
|
7
|
-
def
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
def validate(date)
|
12
|
-
return true if !@days_of_month || @days_of_month.empty?
|
13
|
-
@days_of_month.include?(date.mday) || @days_of_month.include?(date.mday - TimeUtil.days_in_month(date) - 1)
|
14
|
-
end
|
15
|
-
|
16
|
-
def closest(date)
|
17
|
-
return nil if !@days_of_month || @days_of_month.empty?
|
18
|
-
#get some variables we need
|
19
|
-
days_in_month = TimeUtil.days_in_month(date)
|
20
|
-
days_left_in_this_month = days_in_month - date.mday
|
21
|
-
next_month, next_year = date.month == 12 ? [1, date.year + 1] : [date.month + 1, date.year] #clean way to wrap over years
|
22
|
-
days_in_next_month = TimeUtil.days_in_month(Time.utc(next_year, next_month, 1))
|
23
|
-
# create a list of distances
|
24
|
-
distances = []
|
25
|
-
@days_of_month.each do |d|
|
26
|
-
if d > 0
|
27
|
-
distances << d - date.mday #today is 1, we want 20 (19)
|
28
|
-
distances << days_left_in_this_month + d #(364 + 20)
|
29
|
-
elsif d < 0
|
30
|
-
distances << (days_in_month + d + 1) - date.mday #today is 30, we want -1
|
31
|
-
distances << (days_in_next_month + d + 1) + days_left_in_this_month #today is 300, we want -70
|
32
|
-
end
|
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)
|
33
10
|
end
|
34
|
-
|
35
|
-
|
36
|
-
return nil if distances.empty?
|
37
|
-
# return the start of the proper day
|
38
|
-
goal = date + distances.min * IceCube::ONE_DAY
|
39
|
-
self.class.adjust(goal, date)
|
11
|
+
clobber_base_validations(:day, :wday)
|
12
|
+
self
|
40
13
|
end
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
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
48
|
end
|
49
|
-
|
49
|
+
|
50
50
|
end
|
51
|
-
|
51
|
+
|
52
52
|
end
|
@@ -1,57 +1,70 @@
|
|
1
1
|
module IceCube
|
2
2
|
|
3
|
-
|
3
|
+
module Validations::DayOfWeek
|
4
4
|
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
def validate(date)
|
13
|
-
# is it even one of the valid days?
|
14
|
-
return true if !@days_of_week || @days_of_week.empty?
|
15
|
-
return false unless @days_of_week.has_key?(date.wday) #shortcut
|
16
|
-
# does this fall on one of the occurrences?
|
17
|
-
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
|
18
|
-
this_weekday_in_month_count = ((TimeUtil.days_in_month(date) - first_occurrence + 1) / 7.0).ceil #how many of these in the month
|
19
|
-
nth_occurrence_of_weekday = (date.mday - first_occurrence) / 7 + 1 #what occurrence of the weekday is +date+
|
20
|
-
@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)
|
21
|
-
end
|
22
|
-
|
23
|
-
|
24
|
-
# note - temporary implementation
|
25
|
-
# instead - once we know what weekday starts the month, we should be able to figure out
|
26
|
-
# the rest with basic math
|
27
|
-
def closest(date)
|
28
|
-
return nil if !@days_of_week || @days_of_week.empty?
|
29
|
-
goal = date
|
30
|
-
while (next_date = goal + IceCube::ONE_DAY)
|
31
|
-
# DST hack. If our day starts at midnight, when DST ends it will be pushed to 11 PM on the previous day.
|
32
|
-
check_date = next_date.day == goal.day ? next_date + IceCube::ONE_HOUR : next_date
|
33
|
-
return self.class.adjust(next_date, date) if validate(check_date)
|
34
|
-
goal = next_date
|
5
|
+
def day_of_week(dows)
|
6
|
+
dows.each do |day, occs|
|
7
|
+
occs.each do |occ|
|
8
|
+
day = TimeUtil.symbol_to_day(day) if day.is_a?(Symbol)
|
9
|
+
validations_for(:day_of_week) << Validation.new(day, occ)
|
10
|
+
end
|
35
11
|
end
|
12
|
+
clobber_base_validations :day, :wday
|
13
|
+
self
|
36
14
|
end
|
37
15
|
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
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
|
+
|
53
66
|
end
|
54
67
|
|
55
68
|
end
|
56
|
-
|
69
|
+
|
57
70
|
end
|
@@ -1,51 +1,55 @@
|
|
1
1
|
module IceCube
|
2
|
-
|
3
|
-
class DayOfYearValidation < Validation
|
4
2
|
|
5
|
-
|
6
|
-
|
7
|
-
def
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
@days_of_year.include?(date.yday) || @days_of_year.include?(date.yday - TimeUtil.days_in_year(date) - 1)
|
14
|
-
end
|
15
|
-
|
16
|
-
def closest(date)
|
17
|
-
return nil if !@days_of_year || @days_of_year.empty?
|
18
|
-
#get some variables we need
|
19
|
-
days_in_year = TimeUtil.days_in_year(date)
|
20
|
-
days_left_in_this_year = days_in_year - date.yday
|
21
|
-
days_in_next_year = TimeUtil.days_in_year(Time.utc(date.year + 1, 1, 1))
|
22
|
-
# create a list of distances
|
23
|
-
distances = []
|
24
|
-
@days_of_year.each do |d|
|
25
|
-
if d > 0
|
26
|
-
distances << d - date.yday #today is 1, we want 20 (19)
|
27
|
-
distances << days_left_in_this_year + d #(364 + 20)
|
28
|
-
elsif d < 0
|
29
|
-
distances << (days_in_year + d + 1) - date.yday #today is 300, we want -1
|
30
|
-
distances << (days_in_next_year + d + 1) + days_left_in_this_year #today is 300, we want -70
|
31
|
-
end
|
32
|
-
end
|
33
|
-
#return the lowest distance
|
34
|
-
distances = distances.select { |d| d > 0 }
|
35
|
-
return nil if distances.empty?
|
36
|
-
# return the start of the proper day
|
37
|
-
goal = date + distances.min * IceCube::ONE_DAY
|
38
|
-
self.class.adjust(goal, date)
|
39
|
-
end
|
40
|
-
|
41
|
-
def to_s
|
42
|
-
'on the ' << self.class.nice_numbers(@days_of_year) << (@days_of_year.size == 1 ? ' day' : ' days') << ' of the year' unless @days_of_year.empty?
|
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
|
43
11
|
end
|
44
|
-
|
45
|
-
|
46
|
-
|
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
|
+
|
47
51
|
end
|
48
|
-
|
52
|
+
|
49
53
|
end
|
50
54
|
|
51
55
|
end
|