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.
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