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.
Files changed (43) hide show
  1. data/lib/ice_cube.rb +63 -37
  2. data/lib/ice_cube/builders/hash_builder.rb +27 -0
  3. data/lib/ice_cube/builders/ical_builder.rb +59 -0
  4. data/lib/ice_cube/builders/string_builder.rb +74 -0
  5. data/lib/ice_cube/errors/count_exceeded.rb +7 -0
  6. data/lib/ice_cube/errors/until_exceeded.rb +7 -0
  7. data/lib/ice_cube/rule.rb +85 -147
  8. data/lib/ice_cube/rules/daily_rule.rb +5 -27
  9. data/lib/ice_cube/rules/hourly_rule.rb +6 -26
  10. data/lib/ice_cube/rules/minutely_rule.rb +5 -25
  11. data/lib/ice_cube/rules/monthly_rule.rb +6 -30
  12. data/lib/ice_cube/rules/secondly_rule.rb +5 -26
  13. data/lib/ice_cube/rules/weekly_rule.rb +5 -36
  14. data/lib/ice_cube/rules/yearly_rule.rb +8 -34
  15. data/lib/ice_cube/schedule.rb +257 -229
  16. data/lib/ice_cube/single_occurrence_rule.rb +28 -0
  17. data/lib/ice_cube/time_util.rb +202 -76
  18. data/lib/ice_cube/validated_rule.rb +107 -0
  19. data/lib/ice_cube/validations/count.rb +56 -0
  20. data/lib/ice_cube/validations/daily_interval.rb +51 -0
  21. data/lib/ice_cube/validations/day.rb +45 -31
  22. data/lib/ice_cube/validations/day_of_month.rb +44 -44
  23. data/lib/ice_cube/validations/day_of_week.rb +60 -47
  24. data/lib/ice_cube/validations/day_of_year.rb +48 -44
  25. data/lib/ice_cube/validations/hour_of_day.rb +42 -30
  26. data/lib/ice_cube/validations/hourly_interval.rb +50 -0
  27. data/lib/ice_cube/validations/lock.rb +47 -0
  28. data/lib/ice_cube/validations/minute_of_hour.rb +42 -31
  29. data/lib/ice_cube/validations/minutely_interval.rb +50 -0
  30. data/lib/ice_cube/validations/month_of_year.rb +39 -30
  31. data/lib/ice_cube/validations/monthly_interval.rb +47 -0
  32. data/lib/ice_cube/validations/schedule_lock.rb +41 -0
  33. data/lib/ice_cube/validations/second_of_minute.rb +39 -30
  34. data/lib/ice_cube/validations/secondly_interval.rb +50 -0
  35. data/lib/ice_cube/validations/until.rb +49 -0
  36. data/lib/ice_cube/validations/weekly_interval.rb +50 -0
  37. data/lib/ice_cube/validations/yearly_interval.rb +45 -0
  38. data/lib/ice_cube/version.rb +2 -2
  39. data/spec/spec_helper.rb +13 -0
  40. metadata +50 -9
  41. data/lib/ice_cube/rule_occurrence.rb +0 -94
  42. data/lib/ice_cube/validation.rb +0 -44
  43. data/lib/ice_cube/validation_types.rb +0 -137
@@ -1,40 +1,52 @@
1
1
  module IceCube
2
2
 
3
- class HourOfDayValidation < Validation
3
+ module Validations::HourOfDay
4
4
 
5
- attr_reader :hours_of_day
6
-
7
- def initialize(rule)
8
- @hours_of_day = rule.validations[:hour_of_day]
9
- end
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
- hours.compact!
24
- # go to the closest distance away, the start of that hour
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
- def to_s
31
- 'on the ' << self.class.nice_numbers(@hours_of_day) << (@hours_of_day.size == 1 ? ' hour' : ' hours') << ' of the day' unless @hours_of_day.empty?
32
- end
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
- class MinuteOfHourValidation < Validation
3
+ module Validations::MinuteOfHour
4
4
 
5
- attr_reader :minutes_of_hour
6
-
7
- def initialize(rule)
8
- @minutes_of_hour = rule.validations[:minute_of_hour]
9
- end
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
- minutes.compact!
24
- # go to the closest distance away, the beginning of that minute
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
- def to_ical
35
- 'BYMINUTE=' << @minutes_of_hour.join(',') unless @minutes_of_hour.empty?
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
- class MonthOfYearValidation < Validation
3
+ module Validations::MonthOfYear
4
4
 
5
- attr_reader :months_of_year
6
-
7
- def initialize(rule)
8
- @months_of_year = rule.validations[:month_of_year]
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
- months.compact!
24
- # go to the closest distance away
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
- def to_s
31
- 'in ' << self.class.sentence(@months_of_year.map { |m| Date::MONTHNAMES[m] }) unless @months_of_year.empty?
32
- end
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