ice_cube 0.2.7 → 0.2.8

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 CHANGED
@@ -1,17 +1,11 @@
1
1
  require 'yaml.rb'
2
2
  require 'set.rb'
3
+ require 'date'
3
4
 
4
5
  require 'ice_cube/time_util'
5
6
 
6
- require 'ice_cube/validations/month_of_year'
7
- require 'ice_cube/validations/day_of_year'
8
- require 'ice_cube/validations/day_of_month'
9
- require 'ice_cube/validations/day_of_week'
10
- require 'ice_cube/validations/day'
11
- require 'ice_cube/validations/hour_of_day'
12
- require 'ice_cube/validations/minute_of_hour'
13
- require 'ice_cube/validations/second_of_minute'
14
-
7
+ require 'ice_cube/validation'
8
+ require 'ice_cube/validation_types'
15
9
  require 'ice_cube/rule'
16
10
  require 'ice_cube/schedule'
17
11
  require 'ice_cube/rule_occurrence'
@@ -27,6 +21,15 @@ module IceCube
27
21
  autoload :MinutelyRule, 'ice_cube/rules/minutely_rule'
28
22
  autoload :SecondlyRule, 'ice_cube/rules/secondly_rule'
29
23
 
24
+ autoload :DayValidation, 'ice_cube/validations/day'
25
+ autoload :DayOfMonthValidation, 'ice_cube/validations/day_of_month'
26
+ autoload :DayOfWeekValidation, 'ice_cube/validations/day_of_week'
27
+ autoload :DayOfYearValidation, 'ice_cube/validations/day_of_year'
28
+ autoload :HourOfDayValidation, 'ice_cube/validations/hour_of_day'
29
+ autoload :MinuteOfHourValidation, 'ice_cube/validations/minute_of_hour'
30
+ autoload :MonthOfYearValidation, 'ice_cube/validations/month_of_year'
31
+ autoload :SecondOfMinuteValidation, 'ice_cube/validations/second_of_minute'
32
+
30
33
  VERSION = '0.2.3'
31
34
 
32
35
  IceCube::ONE_DAY = 24 * 60 * 60
data/lib/ice_cube/rule.rb CHANGED
@@ -3,11 +3,9 @@ module IceCube
3
3
  class Rule
4
4
 
5
5
  attr_reader :occurrence_count, :until_date
6
-
7
- SuggestionTypes = []
8
- include MonthOfYearValidation, DayOfYearValidation, DayOfMonthValidation, DayOfWeekValidation, DayValidation
9
- include HourOfDayValidation, MinuteOfHourValidation, SecondOfMinuteValidation
10
6
 
7
+ include ValidationTypes
8
+
11
9
  def to_hash
12
10
  hash = Hash.new
13
11
  hash[:rule_type] = self.class.name
@@ -22,7 +20,9 @@ module IceCube
22
20
  rule = hash[:rule_type].split('::').inject(Object) { |namespace, const_name| namespace.const_get(const_name) }.new(hash[:interval])
23
21
  rule.count(hash[:count]) if hash[:count]
24
22
  rule.until(hash[:until]) if hash[:until]
25
- rule.validations = hash[:validations]
23
+ hash[:validations].each do |validation, data|
24
+ data.is_a?(Array) ? rule.send(validation, *data) : rule.send(validation, data)
25
+ end
26
26
  rule
27
27
  end
28
28
 
@@ -76,8 +76,8 @@ module IceCube
76
76
  end
77
77
 
78
78
  def validate_single_date(date)
79
- SuggestionTypes.all? do |s|
80
- response = send("validate_#{s}", date)
79
+ @validation_types.values.all? do |validation|
80
+ response = validation.send(:validate, date)
81
81
  response.nil? || response
82
82
  end
83
83
  end
@@ -87,8 +87,9 @@ module IceCube
87
87
  # by constantly moving the farthest back value forward
88
88
  def next_suggestion(date)
89
89
  # get the next date recommendation set
90
- suggestions = SuggestionTypes.map { |r| send("closest_#{r}", date) }
91
- compact_suggestions = suggestions.compact
90
+ suggestions = {};
91
+ @validation_types.each { |k, validation| suggestions[k] = validation.send(:closest, date) }
92
+ compact_suggestions = suggestions.values.compact
92
93
  # find the next date to go to
93
94
  if compact_suggestions.empty?
94
95
  next_date = date
@@ -99,23 +100,19 @@ module IceCube
99
100
  end
100
101
  else
101
102
  loop do
102
- compact_suggestions = suggestions.compact
103
+ compact_suggestions = suggestions.values.compact
103
104
  min_suggestion = compact_suggestions.min
104
105
  # validate all against the minimum
105
106
  return min_suggestion if validate_single_date(min_suggestion)
106
107
  # move anything that is the minimum to its next closest
107
- SuggestionTypes.each_with_index do |r, index|
108
- suggestions[index] = send("closest_#{r}", min_suggestion) if min_suggestion == suggestions[index]
108
+ @validation_types.each do |k, validation|
109
+ suggestions[k] = validation.send(:closest, min_suggestion) if min_suggestion == suggestions[k]
109
110
  end
110
111
  end
111
112
  end
112
113
  end
113
114
 
114
- def to_s
115
- to_ical
116
- end
117
-
118
- attr_accessor :validations
115
+ attr_reader :validations
119
116
 
120
117
  private
121
118
 
@@ -124,32 +121,22 @@ module IceCube
124
121
  goal - goal.utc_offset + date.utc_offset
125
122
  end
126
123
 
124
+ # get a very meaningful string representation of this rule
125
+ def to_s_base(singular, plural)
126
+ representation = ''
127
+ representation = 'Every ' << ((@interval == 1) ? singular : "#{@interval} #{plural}")
128
+ representation << @validation_types.values.map { |v| ' ' + v.send(:to_s) }.join()
129
+ representation
130
+ end
131
+
127
132
  #TODO - until date formatting is not iCalendar here
128
133
  #get the icalendar representation of this rule logic
129
134
  def to_ical_base
130
135
  representation = ''
131
136
  representation << ";INTERVAL=#{@interval}" if @interval > 1
132
- representation << ';BYMONTH=' << @validations[:month_of_year].join(',') if @validations[:month_of_year]
133
- representation << ';BYYEARDAY=' << @validations[:day_of_year].join(',') if @validations[:day_of_year]
134
- representation << ';BYMONTHDAY=' << @validations[:day_of_month].join(',') if @validations[:day_of_month]
135
- if @validations[:day] || @validations[:day_of_week]
136
- representation << ';BYDAY='
137
- days_dedup = @validations[:day].dup if @validations[:day]
138
- #put days on the string, remove all occurrences in days from days_of_week
139
- if days_dedup
140
- @validations[:day_of_week].keys.each { |day| days_dedup.delete(day) } if @validations[:day_of_week]
141
- representation << (days_dedup.map { |d| IceCube::ICAL_DAYS[d]} ).join(',')
142
- end
143
- representation << ',' if days_dedup && @validations[:day_of_week]
144
- #put days_of_week on string representation
145
- representation << @validations[:day_of_week].inject([]) do |day_rules, pair|
146
- day, occ = *pair
147
- day_rules.concat(occ.map {|v| v.to_s + IceCube::ICAL_DAYS[day]})
148
- end.flatten.join(',') if @validations[:day_of_week]
149
- end
150
- representation << ';BYHOUR=' << @validations[:hour_of_day].join(',') if @validations[:hour_of_day]
151
- representation << ';BYMINUTE=' << @validations[:minute_of_hour].join(',') if @validations[:minute_of_hour]
152
- representation << ';BYSECOND=' << @validations[:second_of_minute].join(',') if @validations[:second_of_minute]
137
+ @validation_types.values.each do |v|
138
+ representation << ';' << v.send(:to_ical)
139
+ end
153
140
  representation << ";COUNT=#{@occurrence_count}" if @occurrence_count
154
141
  representation << ";UNTIL=#{@until_date}" if @until_date
155
142
  representation
@@ -160,6 +147,7 @@ module IceCube
160
147
  def initialize(interval = 1)
161
148
  throw ArgumentError.new('Interval must be > 0') unless interval > 0
162
149
  @validations = {}
150
+ @validation_types = {}
163
151
  @interval = interval
164
152
  end
165
153
 
@@ -12,6 +12,10 @@ module IceCube
12
12
  def to_ical
13
13
  'FREQ=DAILY' << to_ical_base
14
14
  end
15
+
16
+ def to_s
17
+ to_s_base 'day', 'days'
18
+ end
15
19
 
16
20
  protected
17
21
 
@@ -13,6 +13,10 @@ module IceCube
13
13
  'FREQ=HOURLY' << to_ical_base
14
14
  end
15
15
 
16
+ def to_s
17
+ to_s_base 'hour', 'hours'
18
+ end
19
+
16
20
  protected
17
21
 
18
22
  def default_jump(date)
@@ -13,6 +13,10 @@ module IceCube
13
13
  'FREQ=MINUTELY' << to_ical_base
14
14
  end
15
15
 
16
+ def to_s
17
+ to_s_base 'minute', 'minutes'
18
+ end
19
+
16
20
  protected
17
21
 
18
22
  def default_jump(date)
@@ -17,6 +17,10 @@ module IceCube
17
17
  'FREQ=MONTHLY' << to_ical_base
18
18
  end
19
19
 
20
+ def to_s
21
+ to_s_base 'month', 'months'
22
+ end
23
+
20
24
  protected
21
25
 
22
26
  def default_jump(date)
@@ -13,6 +13,10 @@ module IceCube
13
13
  'FREQ=SECONDLY' << to_ical_base
14
14
  end
15
15
 
16
+ def to_s
17
+ to_s_base 'second', 'seconds'
18
+ end
19
+
16
20
  protected
17
21
 
18
22
  def default_jump(date)
@@ -15,6 +15,10 @@ module IceCube
15
15
  'FREQ=WEEKLY' << to_ical_base
16
16
  end
17
17
 
18
+ def to_s
19
+ to_s_base 'week', 'weeks'
20
+ end
21
+
18
22
  protected
19
23
 
20
24
  def default_jump(date)
@@ -17,6 +17,10 @@ module IceCube
17
17
  'FREQ=YEARLY' << to_ical_base
18
18
  end
19
19
 
20
+ def to_s
21
+ to_s_base 'year', 'years'
22
+ end
23
+
20
24
  protected
21
25
 
22
26
  # one year from now, the same month and day of the year
@@ -0,0 +1,30 @@
1
+ module IceCube
2
+
3
+ class Validation
4
+
5
+ NUMBER_SUFFIX = ['th', 'st', 'nd', 'rd', 'th', 'th', 'th', 'th', 'th', 'th']
6
+
7
+ def adjust(goal, date)
8
+ return goal if goal.utc_offset == date.utc_offset
9
+ goal - goal.utc_offset + date.utc_offset
10
+ end
11
+
12
+ def nice_numbers(array)
13
+ array.map { |d| nice_number(d) }.join(', ')
14
+ end
15
+
16
+ private
17
+
18
+ def nice_number(number)
19
+ if number == -1
20
+ 'last'
21
+ elsif number < -1
22
+ number.abs.to_s << NUMBER_SUFFIX[number.abs % 10] << ' to last'
23
+ else
24
+ number.to_s << NUMBER_SUFFIX[number % 10]
25
+ end
26
+ end
27
+
28
+ end
29
+
30
+ end
@@ -0,0 +1,138 @@
1
+ module ValidationTypes
2
+
3
+ def second_of_minute(*seconds)
4
+ @validations[:second_of_minute] ||= []
5
+ @validation_types[:second_of_minute] ||= SecondOfMinuteValidation.new(self)
6
+ seconds.each do |second|
7
+ raise ArgumentError.new('Argument must be a valid second') unless second < 60 && second >= 0
8
+ @validations[:second_of_minute] << second
9
+ end
10
+ # enforce uniqueness
11
+ @validations[:second_of_minute].uniq!
12
+ self
13
+ end
14
+
15
+ # Specify what days of the week this rule should occur on.
16
+ # ie: Schedule.weekly.day_of_week(:monday) would create a rule that
17
+ # occurs every monday.
18
+ def day(*days)
19
+ @validations[:day] ||= []
20
+ @validation_types[:day] ||= DayValidation.new(self)
21
+ days.each do |day|
22
+ if day.is_a?(Integer)
23
+ # integer type argument
24
+ raise ArgumentError.new('Argument must be a valid day of week (0-6)') unless day >= 0 && day <= 6
25
+ @validations[:day] << day
26
+ else
27
+ # symbol type argument
28
+ raise ArgumentError.new('Argument must be a valid day of the week') unless IceCube::DAYS.has_key?(day)
29
+ @validations[:day] << IceCube::DAYS[day]
30
+ end
31
+ end
32
+ # enforce uniqueness
33
+ @validations[:day].uniq!
34
+ self
35
+ end
36
+
37
+ # Specify what days of the year this rule applies to.
38
+ # ie: Schedule.yearly(2).days_of_year(17, -1) would create a
39
+ # rule which occurs every 17th and last day of every other year.
40
+ # Note: you cannot combine month_of_year and day_of_year in the same rule.
41
+ def day_of_year(*days)
42
+ @validations[:day_of_year] ||= []
43
+ @validation_types[:day_of_year] ||= DayOfYearValidation.new(self)
44
+ days.each do |day|
45
+ raise ArgumentError.new('Argument must be a valid day') if day.abs > 366
46
+ raise ArgumentError.new('Argument must be non-zero') if day == 0
47
+ @validations[:day_of_year] << day
48
+ end
49
+ # enforce uniqueness
50
+ @validations[:day_of_year].uniq!
51
+ self
52
+ end
53
+
54
+ # Specify what months of the year this rule applies to.
55
+ # ie: Schedule.yearly(2).month_of_year(:january, :march) would create a
56
+ # rule which occurs every january and march, every other year
57
+ # Note: you cannot combine day_of_year and month_of_year in the same rule.
58
+ def month_of_year(*months)
59
+ @validations[:month_of_year] ||= []
60
+ @validation_types[:month_of_year] ||= MonthOfYearValidation.new(self)
61
+ months.each do |month|
62
+ if month.is_a?(Integer)
63
+ # integer type argument
64
+ raise ArgumentError.new('Argument must be a valid month (1-12)') unless month >= 1 && month <= 12
65
+ @validations[:month_of_year] << month
66
+ else
67
+ #symbol type argument
68
+ raise ArgumentError.new('Argument must be a valid month') unless IceCube::MONTHS.has_key?(month)
69
+ @validations[:month_of_year] << IceCube::MONTHS[month]
70
+ end
71
+ end
72
+ # enforce uniqueness
73
+ @validations[:month_of_year].uniq!
74
+ self
75
+ end
76
+
77
+ # Specify the day(s) of the week that this rule should occur
78
+ # on. ie: rule.day_of_week(:monday => [1, -1]) would mean
79
+ # that this rule should occur on the first and last mondays of each month.
80
+ def day_of_week(days)
81
+ puts days[0].to_s
82
+ @validations[:day_of_week] ||= {}
83
+ @validation_types[:day_of_week] ||= DayOfWeekValidation.new(self)
84
+ days.each do |day, occurrences|
85
+ unless day.is_a?(Integer)
86
+ raise ArgumentError.new('Argument must be a valid day of week') unless IceCube::DAYS.has_key?(day)
87
+ day = IceCube::DAYS[day]
88
+ end
89
+ raise ArgumentError.new('Argument must be a valid day of week (0-6)') unless day >= 0 && day <= 6
90
+ # add the day
91
+ @validations[:day_of_week][day] ||= []
92
+ @validations[:day_of_week][day].concat(occurrences)
93
+ @validations[:day_of_week][day].uniq!
94
+ end
95
+ self
96
+ end
97
+
98
+ def hour_of_day(*hours)
99
+ @validations[:hour_of_day] ||= []
100
+ @validation_types[:hour_of_day] ||= HourOfDayValidation.new(self)
101
+ hours.each do |hour|
102
+ raise ArgumentError.new('Argument must be a valid hour') unless hour < 24 && hour >= 0
103
+ @validations[:hour_of_day] << hour
104
+ end
105
+ # enforce uniqueness
106
+ @validations[:hour_of_day].uniq!
107
+ self
108
+ end
109
+
110
+ # Specify the days of the month that this rule should
111
+ # occur on. ie: rule.day_of_month(1, -1) would mean that
112
+ # this rule should occur on the first and last day of every month.
113
+ def day_of_month(*days)
114
+ @validations[:day_of_month] ||= []
115
+ @validation_types[:day_of_month] ||= DayOfMonthValidation.new(self)
116
+ days.each do |day|
117
+ raise ArgumentError.new('Argument must be a valid date') if day.abs > 31
118
+ raise ArgumentError.new('Argument must be non-zero') if day == 0
119
+ @validations[:day_of_month] << day
120
+ end
121
+ # enforce uniqueness
122
+ @validations[:day_of_month].uniq!
123
+ self
124
+ end
125
+
126
+ def minute_of_hour(*minutes)
127
+ @validations[:minute_of_hour] ||= []
128
+ @validation_types[:minute_of_hour] ||= MinuteOfHourValidation.new(self)
129
+ minutes.each do |minute|
130
+ raise ArgumentError.new('Argument must be a valid minute') unless minute < 60 && minute >= 0
131
+ @validations[:minute_of_hour] << minute
132
+ end
133
+ # enforce uniqueness
134
+ @validations[:minute_of_hour].uniq!
135
+ self
136
+ end
137
+
138
+ end
@@ -1,36 +1,39 @@
1
- module DayValidation
1
+ module IceCube
2
+
3
+ class DayValidation < Validation
2
4
 
3
- def self.included(base)
4
- base::SuggestionTypes << :day
5
- end
5
+ def initialize(rule)
6
+ @days = rule.validations[:day]
7
+ @rule = rule
8
+ end
6
9
 
7
- # Specify what days of the week this rule should occur on.
8
- # ie: Schedule.weekly.day_of_week(:monday) would create a rule that
9
- # occurs every monday.
10
- def day(*days)
11
- @validations[:day] ||= []
12
- days.each do |day|
13
- raise ArgumentError.new('Argument must be a valid day of the week') unless IceCube::DAYS.has_key?(day)
14
- @validations[:day] << IceCube::DAYS[day]
10
+ def validate(date)
11
+ return true if !@days || @days.empty?
12
+ @days.include?(date.wday)
15
13
  end
16
- self
17
- end
18
14
 
19
- def validate_day(date)
20
- return true if !@validations[:day] || @validations[:day].empty?
21
- @validations[:day].include?(date.wday)
22
- end
15
+ def closest(date)
16
+ return nil if !@days || @days.empty?
17
+ # turn days into distances
18
+ days = @days.map do |d|
19
+ d > date.wday ? (d - date.wday) : (7 - date.wday + d)
20
+ end
21
+ days.compact!
22
+ # go to the closest distance away, the start of that day
23
+ goal = date + days.min * IceCube::ONE_DAY
24
+ adjust(goal, date)
25
+ end
23
26
 
24
- def closest_day(date)
25
- return nil if !@validations[:day] || @validations[:day].empty?
26
- # turn days into distances
27
- days = @validations[:day].map do |d|
28
- d > date.wday ? (d - date.wday) : (7 - date.wday + d)
27
+ def to_s
28
+ days_dup = (@days - @rule.validations[:day_of_week].keys if @rule.validations[:day_of_week]) || @days # don't list twice
29
+ 'on every ' << days_dup.map { |d| Date::DAYNAMES[d] }.join(', ') unless days_dup.empty?
30
+ end
31
+
32
+ def to_ical
33
+ days_dup = (@days - @rule.validations[:day_of_week].keys if @rule.validations[:day_of_week]) || @days # don't list twice
34
+ 'BYDAY=' << days_dup.map { |d| IceCube::ICAL_DAYS[d] }.join(',') unless days_dup.empty?
29
35
  end
30
- days.compact!
31
- # go to the closest distance away, the start of that day
32
- goal = date + days.min * IceCube::ONE_DAY
33
- adjust(goal, date)
34
- end
35
36
 
37
+ end
38
+
36
39
  end
@@ -1,51 +1,50 @@
1
- module DayOfMonthValidation
1
+ module IceCube
2
+
3
+ class DayOfMonthValidation < Validation
2
4
 
3
- def self.included(base)
4
- base::SuggestionTypes << :day_of_month
5
- end
6
-
7
- # Specify the days of the month that this rule should
8
- # occur on. ie: rule.day_of_month(1, -1) would mean that
9
- # this rule should occur on the first and last day of every month.
10
- def day_of_month(*days)
11
- @validations[:day_of_month] ||= []
12
- days.each do |day|
13
- raise ArgumentError.new('Argument must be a valid date') if day.abs > 31
14
- raise ArgumentError.new('Argument must be non-zero') if day == 0
15
- @validations[:day_of_month] << day
5
+ def initialize(rule)
6
+ @days_of_month = rule.validations[:day_of_month]
16
7
  end
17
- self
18
- end
19
8
 
20
- def validate_day_of_month(date)
21
- return true if !@validations[:day_of_month] || @validations[:day_of_month].empty?
22
- @validations[:day_of_month].include?(date.mday) || @validations[:day_of_month].include?(date.mday - TimeUtil.days_in_month(date) - 1)
23
- end
9
+ def validate(date)
10
+ return true if !@days_of_month || @days_of_month.empty?
11
+ @days_of_month.include?(date.mday) || @days_of_month.include?(date.mday - TimeUtil.days_in_month(date) - 1)
12
+ end
24
13
 
25
- def closest_day_of_month(date)
26
- return nil if !@validations[:day_of_month] || @validations[:day_of_month].empty?
27
- #get some variables we need
28
- days_in_month = TimeUtil.days_in_month(date)
29
- days_left_in_this_month = days_in_month - date.mday
30
- next_month, next_year = date.month == 12 ? [1, date.year + 1] : [date.month + 1, date.year] #clean way to wrap over years
31
- days_in_next_month = TimeUtil.days_in_month(Time.utc(next_year, next_month, 1))
32
- # create a list of distances
33
- distances = []
34
- @validations[:day_of_month].each do |d|
35
- if d > 0
36
- distances << d - date.mday #today is 1, we want 20 (19)
37
- distances << days_left_in_this_month + d #(364 + 20)
38
- elsif d < 0
39
- distances << (days_in_month + d + 1) - date.mday #today is 30, we want -1
40
- distances << (days_in_next_month + d + 1) + days_left_in_this_month #today is 300, we want -70
14
+ def closest(date)
15
+ return nil if !@days_of_month || @days_of_month.empty?
16
+ #get some variables we need
17
+ days_in_month = TimeUtil.days_in_month(date)
18
+ days_left_in_this_month = days_in_month - date.mday
19
+ next_month, next_year = date.month == 12 ? [1, date.year + 1] : [date.month + 1, date.year] #clean way to wrap over years
20
+ days_in_next_month = TimeUtil.days_in_month(Time.utc(next_year, next_month, 1))
21
+ # create a list of distances
22
+ distances = []
23
+ @days_of_month.each do |d|
24
+ if d > 0
25
+ distances << d - date.mday #today is 1, we want 20 (19)
26
+ distances << days_left_in_this_month + d #(364 + 20)
27
+ elsif d < 0
28
+ distances << (days_in_month + d + 1) - date.mday #today is 30, we want -1
29
+ distances << (days_in_next_month + d + 1) + days_left_in_this_month #today is 300, we want -70
30
+ end
41
31
  end
32
+ #return the lowest distance
33
+ distances = distances.select { |d| d > 0 }
34
+ return nil if distances.empty?
35
+ # return the start of the proper day
36
+ goal = date + distances.min * IceCube::ONE_DAY
37
+ adjust(goal, date)
42
38
  end
43
- #return the lowest distance
44
- distances = distances.select { |d| d > 0 }
45
- return nil if distances.empty?
46
- # return the start of the proper day
47
- goal = date + distances.min * IceCube::ONE_DAY
48
- adjust(goal, date)
49
- end
50
39
 
40
+ def to_s
41
+ 'on the ' << nice_numbers(@days_of_month.sort) << (@days_of_month.count == 1 ? ' day' : ' days') << ' of the month' unless @days_of_month.empty?
42
+ end
43
+
44
+ def to_ical
45
+ 'BYMONTHDAY=' << @days_of_month.join(',') unless @days_of_month.empty?
46
+ end
47
+
48
+ end
49
+
51
50
  end
@@ -1,41 +1,48 @@
1
- module DayOfWeekValidation
2
-
3
- def self.included(base)
4
- base::SuggestionTypes << :day_of_week
5
- end
1
+ module IceCube
2
+
3
+ class DayOfWeekValidation < Validation
4
+
5
+ def initialize(rule)
6
+ @days_of_week = rule.validations[:day_of_week]
7
+ @rule = rule
8
+ end
6
9
 
7
- # Specify the day(s) of the week that this rule should occur
8
- # on. ie: rule.day_of_week(:monday => [1, -1]) would mean
9
- # that this rule should occur on the first and last mondays of each month.
10
- def day_of_week(days)
11
- @validations[:day_of_week] ||= {}
12
- days.each do |day, occurrences|
13
- raise ArgumentError.new('Argument must be a valid day') unless IceCube::DAYS.has_key?(day)
14
- @validations[:day_of_week][IceCube::DAYS[day]] ||= []
15
- @validations[:day_of_week][IceCube::DAYS[day]].concat(occurrences)
10
+ def validate(date)
11
+ # is it even one of the valid days?
12
+ return true if !@days_of_week || @days_of_week.empty?
13
+ return false unless @days_of_week.has_key?(date.wday) #shortcut
14
+ # does this fall on one of the occurrences?
15
+ 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
16
+ this_weekday_in_month_count = ((TimeUtil.days_in_month(date) - first_occurrence + 1) / 7.0).ceil #how many of these in the month
17
+ nth_occurrence_of_weekday = (date.mday - first_occurrence) / 7 + 1 #what occurrence of the weekday is +date+
18
+ @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)
16
19
  end
17
- self
18
- end
19
20
 
20
- def validate_day_of_week(date)
21
- # is it even one of the valid days?
22
- return true if !@validations[:day_of_week] || @validations[:day_of_week].empty?
23
- return false unless @validations[:day_of_week].has_key?(date.wday) #shortcut
24
- # does this fall on one of the occurrences?
25
- 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
26
- this_weekday_in_month_count = ((TimeUtil.days_in_month(date) - first_occurrence + 1) / 7.0).ceil #how many of these in the month
27
- nth_occurrence_of_weekday = (date.mday - first_occurrence) / 7 + 1 #what occurrence of the weekday is +date+
28
- @validations[:day_of_week][date.wday].include?(nth_occurrence_of_weekday) || @validations[:day_of_week][date.wday].include?(nth_occurrence_of_weekday - this_weekday_in_month_count - 1)
29
- end
21
+ #note - temporary implementation
22
+ def closest(date)
23
+ return nil if !@days_of_week || @days_of_week.empty?
24
+ while date += IceCube::ONE_DAY
25
+ return date if validate(date)
26
+ end
27
+ end
28
+
29
+ def to_s
30
+ representation = ''
31
+ representation << 'on the '
32
+ representation << @days_of_week.map do |day, occ|
33
+ nice_numbers(occ) << ' ' << Date::DAYNAMES[day] << (occ.count != 1 ? 's' : '') unless @days_of_week.empty?
34
+ end.join(' and the ')
35
+ representation
36
+ end
30
37
 
31
- #note - temporary implementation
32
- def closest_day_of_week(date)
33
- return nil if !@validations[:day_of_week] || @validations[:day_of_week].empty?
34
- goal = date
35
- while goal += IceCube::ONE_DAY
36
- test = adjust(goal, date)
37
- return test if validate_day_of_week(test)
38
+ def to_ical
39
+ representation = 'BYDAY='
40
+ @days_of_week.each do |day, occ|
41
+ representation << occ.map { |o| o.to_s + IceCube::ICAL_DAYS[day] }.join(',')
42
+ end
43
+ representation
38
44
  end
45
+
39
46
  end
40
47
 
41
48
  end
@@ -1,51 +1,49 @@
1
- module DayOfYearValidation
1
+ module IceCube
2
2
 
3
- def self.included(base)
4
- base::SuggestionTypes << :day_of_year
5
- end
6
-
7
- # Specify what days of the year this rule applies to.
8
- # ie: Schedule.yearly(2).days_of_year(17, -1) would create a
9
- # rule which occurs every 17th and last day of every other year.
10
- # Note: you cannot combine month_of_year and day_of_year in the same rule.
11
- def day_of_year(*days)
12
- @validations[:day_of_year] ||= []
13
- days.each do |day|
14
- raise ArgumentError.new('Argument must be a valid day') if day.abs > 366
15
- raise ArgumentError.new('Argument must be non-zero') if day == 0
16
- @validations[:day_of_year] << day
3
+ class DayOfYearValidation < Validation
4
+
5
+ def initialize(rule)
6
+ @days_of_year = rule.validations[:day_of_year]
17
7
  end
18
- self
19
- end
20
8
 
21
- def validate_day_of_year(date)
22
- return true if !@validations[:day_of_year] || @validations[:day_of_year].empty?
23
- @validations[:day_of_year].include?(date.yday) || @validations[:day_of_year].include?(date.yday - TimeUtil.days_in_year(date) - 1)
24
- end
9
+ def validate(date)
10
+ return true if !@days_of_year || @days_of_year.empty?
11
+ @days_of_year.include?(date.yday) || @days_of_year.include?(date.yday - TimeUtil.days_in_year(date) - 1)
12
+ end
25
13
 
26
- def closest_day_of_year(date)
27
- return nil if !@validations[:day_of_year] || @validations[:day_of_year].empty?
28
- #get some variables we need
29
- days_in_year = TimeUtil.days_in_year(date)
30
- days_left_in_this_year = days_in_year - date.yday
31
- days_in_next_year = TimeUtil.days_in_year(Time.utc(date.year + 1, 1, 1))
32
- # create a list of distances
33
- distances = []
34
- @validations[:day_of_year].each do |d|
35
- if d > 0
36
- distances << d - date.yday #today is 1, we want 20 (19)
37
- distances << days_left_in_this_year + d #(364 + 20)
38
- elsif d < 0
39
- distances << (days_in_year + d + 1) - date.yday #today is 300, we want -1
40
- distances << (days_in_next_year + d + 1) + days_left_in_this_year #today is 300, we want -70
14
+ def closest(date)
15
+ return nil if !@days_of_year || @days_of_year.empty?
16
+ #get some variables we need
17
+ days_in_year = TimeUtil.days_in_year(date)
18
+ days_left_in_this_year = days_in_year - date.yday
19
+ days_in_next_year = TimeUtil.days_in_year(Time.utc(date.year + 1, 1, 1))
20
+ # create a list of distances
21
+ distances = []
22
+ @days_of_year.each do |d|
23
+ if d > 0
24
+ distances << d - date.yday #today is 1, we want 20 (19)
25
+ distances << days_left_in_this_year + d #(364 + 20)
26
+ elsif d < 0
27
+ distances << (days_in_year + d + 1) - date.yday #today is 300, we want -1
28
+ distances << (days_in_next_year + d + 1) + days_left_in_this_year #today is 300, we want -70
29
+ end
41
30
  end
31
+ #return the lowest distance
32
+ distances = distances.select { |d| d > 0 }
33
+ return nil if distances.empty?
34
+ # return the start of the proper day
35
+ goal = date + distances.min * IceCube::ONE_DAY
36
+ adjust(goal, date)
42
37
  end
43
- #return the lowest distance
44
- distances = distances.select { |d| d > 0 }
45
- return nil if distances.empty?
46
- # return the start of the proper day
47
- goal = date + distances.min * IceCube::ONE_DAY
48
- adjust(goal, date)
49
- end
50
38
 
39
+ def to_s
40
+ 'on the ' << nice_numbers(@days_of_year.sort) << (@days_of_year.count == 1 ? ' day' : ' days') << ' of the year' unless @days_of_year.empty?
41
+ end
42
+
43
+ def to_ical
44
+ 'BYYEARDAY=' << @days_of_year.join(',') unless @days_of_year.empty?
45
+ end
46
+
47
+ end
48
+
51
49
  end
@@ -1,35 +1,38 @@
1
- module HourOfDayValidation
1
+ module IceCube
2
+
3
+ class HourOfDayValidation < Validation
2
4
 
3
- def self.included(base)
4
- base::SuggestionTypes << :hour_of_day
5
- end
6
-
7
- def hour_of_day(*hours)
8
- @validations[:hour_of_day] ||= []
9
- hours.each do |hour|
10
- raise ArgumentError.new('Argument must be a valid hour') unless hour < 24 && hour >= 0
11
- @validations[:hour_of_day] << hour
5
+ def initialize(rule)
6
+ @hours_of_day = rule.validations[:hour_of_day]
12
7
  end
13
- self
14
- end
15
8
 
16
- def validate_hour_of_day(date)
17
- return true if !@validations[:hour_of_day] || @validations[:hour_of_day].empty?
18
- @validations[:hour_of_day].include?(date.hour)
19
- end
9
+ def validate(date)
10
+ return true if !@hours_of_day || @hours_of_day.empty?
11
+ @hours_of_day.include?(date.hour)
12
+ end
20
13
 
21
- def closest_hour_of_day(date)
22
- return nil if !@validations[:hour_of_day] || @validations[:hour_of_day].empty?
23
- # turn hours into hour of day
24
- # hour >= 24 should fall into the next day
25
- hours = @validations[:hour_of_day].map do |h|
26
- h > date.hour ? h - date.hour : 24 - date.hour + h
14
+ def closest(date)
15
+ return nil if !@hours_of_day || @hours_of_day.empty?
16
+ # turn hours into hour of day
17
+ # hour >= 24 should fall into the next day
18
+ hours = @hours_of_day.map do |h|
19
+ h > date.hour ? h - date.hour : 24 - date.hour + h
20
+ end
21
+ hours.compact!
22
+ # go to the closest distance away, the start of that hour
23
+ closest_hour = hours.min
24
+ goal = date + IceCube::ONE_HOUR * closest_hour
25
+ adjust(goal, date)
26
+ end
27
+
28
+ def to_s
29
+ 'on the ' << nice_numbers(@hours_of_day.sort) << (@hours_of_day.count == 1 ? ' hour' : ' hours') << ' of the day' unless @hours_of_day.empty?
27
30
  end
28
- hours.compact!
29
- # go to the closest distance away, the start of that hour
30
- closest_hour = hours.min
31
- goal = date + IceCube::ONE_HOUR * closest_hour
32
- adjust(goal, date)
31
+
32
+ def to_ical
33
+ 'BYHOUR=' << @hours_of_day.join(',') unless @hours_of_day.empty?
34
+ end
35
+
33
36
  end
34
37
 
35
38
  end
@@ -1,35 +1,38 @@
1
- module MinuteOfHourValidation
1
+ module IceCube
2
+
3
+ class MinuteOfHourValidation < Validation
2
4
 
3
- def self.included(base)
4
- base::SuggestionTypes << :minute_of_hour
5
- end
6
-
7
- def minute_of_hour(*minutes)
8
- @validations[:minute_of_hour] ||= []
9
- minutes.each do |minute|
10
- raise ArgumentError.new('Argument must be a valid minute') unless minute < 60 && minute >= 0
11
- @validations[:minute_of_hour] << minute
5
+ def initialize(rule)
6
+ @minutes_of_hour = rule.validations[:minute_of_hour]
12
7
  end
13
- self
14
- end
15
8
 
16
- def validate_minute_of_hour(date)
17
- return true if !@validations[:minute_of_hour] || @validations[:minute_of_hour].empty?
18
- @validations[:minute_of_hour].include?(date.min)
19
- end
9
+ def validate(date)
10
+ return true if !@minutes_of_hour || @minutes_of_hour.empty?
11
+ @minutes_of_hour.include?(date.min)
12
+ end
20
13
 
21
- def closest_minute_of_hour(date)
22
- return nil if !@validations[:minute_of_hour] || @validations[:minute_of_hour].empty?
23
- # turn minutes into minutes of hour
24
- # minute >= 60 should fall into the next hour
25
- minutes = @validations[:minute_of_hour].map do |m|
26
- m > date.min ? m - date.min : 60 - date.min + m
14
+ def closest(date)
15
+ return nil if !@minutes_of_hour || @minutes_of_hour.empty?
16
+ # turn minutes into minutes of hour
17
+ # minute >= 60 should fall into the next hour
18
+ minutes = @minutes_of_hour.map do |m|
19
+ m > date.min ? m - date.min : 60 - date.min + m
20
+ end
21
+ minutes.compact!
22
+ # go to the closest distance away, the beginning of that minute
23
+ closest_minute = minutes.min
24
+ goal = date + closest_minute * IceCube::ONE_MINUTE
25
+ adjust(goal, date)
26
+ end
27
+
28
+ def to_s
29
+ 'on the ' << nice_numbers(@minutes_of_hour.sort) << (@minutes_of_hour.count == 1 ? ' minute' : ' minutes') << ' of the hour' unless @minutes_of_hour.empty?
30
+ end
31
+
32
+ def to_ical
33
+ 'BYMINUTE=' << @minutes_of_hour.join(',') unless @minutes_of_hour.empty?
27
34
  end
28
- minutes.compact!
29
- # go to the closest distance away, the beginning of that minute
30
- closest_minute = minutes.min
31
- goal = date + closest_minute * IceCube::ONE_MINUTE
32
- adjust(goal, date)
35
+
33
36
  end
34
37
 
35
38
  end
@@ -1,39 +1,38 @@
1
- module MonthOfYearValidation
1
+ module IceCube
2
+
3
+ class MonthOfYearValidation < Validation
2
4
 
3
- def self.included(base)
4
- base::SuggestionTypes << :month_of_year
5
- end
6
-
7
- # Specify what months of the year this rule applies to.
8
- # ie: Schedule.yearly(2).month_of_year(:january, :march) would create a
9
- # rule which occurs every january and march, every other year
10
- # Note: you cannot combine day_of_year and month_of_year in the same rule.
11
- def month_of_year(*months)
12
- @validations[:month_of_year] ||= []
13
- months.each do |month|
14
- raise ArgumentError.new('Argument must be a valid month') unless IceCube::MONTHS.has_key?(month)
15
- @validations[:month_of_year] << IceCube::MONTHS[month]
5
+ def initialize(rule)
6
+ @months_of_year = rule.validations[:month_of_year]
16
7
  end
17
- self
18
- end
19
8
 
20
- def validate_month_of_year(date)
21
- return true if !@validations[:month_of_year] || @validations[:month_of_year].empty?
22
- @validations[:month_of_year].include?(date.month)
23
- end
9
+ def validate(date)
10
+ return true if !@months_of_year || @months_of_year.empty?
11
+ @months_of_year.include?(date.month)
12
+ end
24
13
 
25
- def closest_month_of_year(date)
26
- return nil if !@validations[:month_of_year] || @validations[:month_of_year].empty?
27
- # turn months into month of year
28
- # month > 12 should fall into the next year
29
- months = @validations[:month_of_year].map do |m|
30
- m > date.month ? m - date.month : 12 - date.month + m
14
+ def closest(date)
15
+ return nil if !@months_of_year || @months_of_year.empty?
16
+ # turn months into month of year
17
+ # month > 12 should fall into the next year
18
+ months = @months_of_year.map do |m|
19
+ m > date.month ? m - date.month : 12 - date.month + m
20
+ end
21
+ months.compact!
22
+ # go to the closest distance away
23
+ goal = date
24
+ months.min.times { goal += TimeUtil.days_in_month(goal) * IceCube::ONE_DAY }
25
+ adjust(goal, date)
26
+ end
27
+
28
+ def to_s
29
+ 'in ' << @months_of_year.map { |m| Date::MONTHNAMES[m] }.join(', ') unless @months_of_year.empty?
30
+ end
31
+
32
+ def to_ical
33
+ 'BYMONTH=' << @months_of_year.join(',') unless @months_of_year.empty?
31
34
  end
32
- months.compact!
33
- # go to the closest distance away
34
- goal = date
35
- months.min.times { goal += TimeUtil.days_in_month(goal) * IceCube::ONE_DAY }
36
- adjust(goal, date)
35
+
37
36
  end
38
37
 
39
38
  end
@@ -1,35 +1,38 @@
1
- module SecondOfMinuteValidation
1
+ module IceCube
2
+
3
+ class SecondOfMinuteValidation < Validation
2
4
 
3
- def self.included(base)
4
- base::SuggestionTypes << :second_of_minute
5
- end
6
-
7
- def second_of_minute(*seconds)
8
- @validations[:second_of_minute] ||= []
9
- seconds.each do |second|
10
- raise ArgumentError.new('Argument must be a valid second') unless second < 60 && second >= 0
11
- @validations[:second_of_minute] << second
5
+ def initialize(rule)
6
+ @seconds_of_minute = rule.validations[:second_of_minute]
12
7
  end
13
- self
14
- end
15
8
 
16
- def validate_second_of_minute(date)
17
- return true if !@validations[:second_of_minute] || @validations[:second_of_minute].empty?
18
- @validations[:second_of_minute].include?(date.sec)
19
- end
9
+ def validate(date)
10
+ return true if !@seconds_of_minute || @seconds_of_minute.empty?
11
+ @seconds_of_minute.include?(date.sec)
12
+ end
20
13
 
21
- def closest_second_of_minute(date)
22
- return nil if !@validations[:second_of_minute] || @validations[:second_of_minute].empty?
23
- # turn seconds into seconds of minute
24
- # second >= 60 should fall into the next minute
25
- seconds = @validations[:second_of_minute].map do |s|
26
- s > date.sec ? s - date.sec : 60 - date.sec + s
14
+ def closest(date)
15
+ return nil if !@seconds_of_minute || @seconds_of_minute.empty?
16
+ # turn seconds into seconds of minute
17
+ # second >= 60 should fall into the next minute
18
+ seconds = @seconds_of_minute.map do |s|
19
+ s > date.sec ? s - date.sec : 60 - date.sec + s
20
+ end
21
+ seconds.compact!
22
+ # go to the closest distance away
23
+ closest_second = seconds.min
24
+ goal = date + closest_second
25
+ adjust(goal, date)
26
+ end
27
+
28
+ def to_s
29
+ 'on the ' << nice_numbers(@seconds_of_minute.sort) << (@seconds_of_minute.count == 1 ? ' second' : ' seconds') << ' of the minute' unless @seconds_of_minute.empty?
27
30
  end
28
- seconds.compact!
29
- # go to the closest distance away
30
- closest_second = seconds.min
31
- goal = date + closest_second
32
- adjust(goal, date)
31
+
32
+ def to_ical
33
+ 'BYSECOND=' << @seconds_of_minute.join(',') unless @seconds_of_minute.empty?
34
+ end
35
+
33
36
  end
34
37
 
35
38
  end
@@ -1,5 +1,5 @@
1
1
  module IceCube
2
2
 
3
- VERSION = "0.2.7"
3
+ VERSION = "0.2.8"
4
4
 
5
5
  end
metadata CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
5
5
  segments:
6
6
  - 0
7
7
  - 2
8
- - 7
9
- version: 0.2.7
8
+ - 8
9
+ version: 0.2.8
10
10
  platform: ruby
11
11
  authors:
12
12
  - John Crepezzi
@@ -14,7 +14,7 @@ autorequire:
14
14
  bindir: bin
15
15
  cert_chain: []
16
16
 
17
- date: 2010-04-07 00:00:00 -04:00
17
+ date: 2010-04-08 00:00:00 -04:00
18
18
  default_executable:
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency
@@ -49,6 +49,8 @@ files:
49
49
  - lib/ice_cube/rules/yearly_rule.rb
50
50
  - lib/ice_cube/schedule.rb
51
51
  - lib/ice_cube/time_util.rb
52
+ - lib/ice_cube/validation.rb
53
+ - lib/ice_cube/validation_types.rb
52
54
  - lib/ice_cube/validations/day.rb
53
55
  - lib/ice_cube/validations/day_of_month.rb
54
56
  - lib/ice_cube/validations/day_of_week.rb