ice_cube 0.2.7 → 0.2.8

Sign up to get free protection for your applications and to get access to all the features.
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