hiccup 0.3.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.mdown CHANGED
@@ -13,7 +13,7 @@ class Schedule
13
13
  hiccup :enumerable,
14
14
  :validatable,
15
15
  :humanizable,
16
- :inferrable,
16
+ :inferable,
17
17
  :serializable => [:ical]
18
18
 
19
19
  end
@@ -60,7 +60,7 @@ Mixes in ActiveModel validations for recurrence models
60
60
 
61
61
  Represents a recurring pattern in a human-readable string
62
62
 
63
- ### Inferrable
63
+ ### Inferable
64
64
 
65
65
  Infers a schedule from an array of dates
66
66
 
@@ -3,24 +3,6 @@ module Hiccup
3
3
  module Date
4
4
 
5
5
 
6
- def get_months_since(earlier_date)
7
- ((self.year - earlier_date.year) * 12) + (self.month - earlier_date.month).to_int
8
- end
9
-
10
- def get_years_since(earlier_date)
11
- (self.year - earlier_date.year)
12
- end
13
-
14
-
15
- def get_months_until(later_date)
16
- later_date.months_since(self)
17
- end
18
-
19
- def get_years_until(later_date)
20
- later_date.years_since(self)
21
- end
22
-
23
-
24
6
 
25
7
  def get_nth_wday_of_month
26
8
  (day - 1) / 7 + 1
@@ -0,0 +1,56 @@
1
+ require 'hiccup/enumerable/schedule_enumerator'
2
+
3
+ module Hiccup
4
+ module Enumerable
5
+ class AnnuallyEnumerator < ScheduleEnumerator
6
+
7
+
8
+ def initialize(*args)
9
+ super
10
+
11
+ # Use more efficient iterator methods unless
12
+ # we have to care about leap years
13
+
14
+ unless start_date.month == 2 && start_date.day == 29
15
+ def self.next_occurrence_after(date)
16
+ date.next_year(skip)
17
+ end
18
+
19
+ def self.next_occurrence_before(date)
20
+ date.prev_year(skip)
21
+ end
22
+ end
23
+ end
24
+
25
+
26
+ def first_occurrence_on_or_after(date)
27
+ year, month, day = date.year, start_date.month, start_date.day
28
+ day = -1 if month == 2 && day == 29
29
+
30
+ result = Date.new(year, month, day)
31
+ year += 1 if result < date
32
+
33
+ remainder = (year - start_date.year) % skip
34
+ year += (skip - remainder) if remainder > 0
35
+
36
+ Date.new(year, month, day)
37
+ end
38
+
39
+ def first_occurrence_on_or_before(date)
40
+ year, month, day = date.year, start_date.month, start_date.day
41
+ day = -1 if month == 2 && day == 29
42
+
43
+ result = Date.new(year, month, day)
44
+ year -= 1 if result > date
45
+
46
+ # what if year is before start_date.year?
47
+ remainder = (year - start_date.year) % skip
48
+ year -= remainder if remainder > 0
49
+
50
+ Date.new(year, month, day)
51
+ end
52
+
53
+
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,84 @@
1
+ require 'hiccup/enumerable/schedule_enumerator'
2
+
3
+ module Hiccup
4
+ module Enumerable
5
+ class MonthlyEnumerator < ScheduleEnumerator
6
+
7
+
8
+ def first_occurrence_on_or_after(date)
9
+ result = nil
10
+ monthly_pattern.each do |occurrence|
11
+ temp = nil
12
+ (0...30).each do |i| # If an occurrence doesn't occur this month, try up to 30 months in the future
13
+ temp = monthly_occurrence_to_date(occurrence, shift_date_by_months(date, i))
14
+ break if temp && (temp >= date)
15
+ end
16
+ next unless temp
17
+
18
+ remainder = months_between(temp, start_date) % skip
19
+ temp = monthly_occurrence_to_date(occurrence, shift_date_by_months(temp, skip - remainder)) if remainder > 0
20
+ next unless temp
21
+
22
+ result = temp if !result || (temp < result)
23
+ end
24
+ result
25
+ end
26
+
27
+ def first_occurrence_on_or_before(date)
28
+ result = nil
29
+ monthly_pattern.each do |occurrence|
30
+ temp = nil
31
+ (0...30).each do |i| # If an occurrence doesn't occur this month, try up to 30 months in the past
32
+ temp = monthly_occurrence_to_date(occurrence, shift_date_by_months(date, -i))
33
+ break if temp && (temp <= date)
34
+ end
35
+ next unless temp
36
+
37
+ remainder = months_between(temp, start_date) % skip
38
+ temp = monthly_occurrence_to_date(occurrence, shift_date_by_months(temp, -remainder)) if remainder > 0
39
+ next unless temp
40
+
41
+ result = temp if !result || (temp > result)
42
+ end
43
+ result
44
+ end
45
+
46
+
47
+ private
48
+
49
+
50
+ def shift_date_by_months(date, months)
51
+ date.next_month(months)
52
+ end
53
+
54
+
55
+ def monthly_occurrence_to_date(occurrence, date)
56
+ year, month = date.year, date.month
57
+
58
+ day = begin
59
+ if occurrence.is_a?(Array)
60
+ ordinal, weekday = occurrence
61
+ wday_of_first_of_month = Date.new(year, month, 1).wday
62
+ wday = Date::DAYNAMES.index(weekday)
63
+ day = wday
64
+ day = day + 7 if (wday < wday_of_first_of_month)
65
+ day = day - wday_of_first_of_month
66
+ day = day + (ordinal * 7) - 6
67
+ else
68
+ occurrence
69
+ end
70
+ end
71
+
72
+ last_day_of_month = Date.new(year, month, -1).day
73
+ (day > last_day_of_month) ? nil : Date.new(year, month, day)
74
+ end
75
+
76
+
77
+ def months_between(later_date, earlier_date)
78
+ ((later_date.year - earlier_date.year) * 12) + (later_date.month - earlier_date.month).to_int
79
+ end
80
+
81
+
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,19 @@
1
+ require 'hiccup/enumerable/schedule_enumerator'
2
+
3
+ module Hiccup
4
+ module Enumerable
5
+ class NeverEnumerator < ScheduleEnumerator
6
+
7
+
8
+ def first_occurrence_on_or_after(date)
9
+ date if date == start_date
10
+ end
11
+
12
+ def first_occurrence_on_or_before(date)
13
+ date
14
+ end
15
+
16
+
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,67 @@
1
+
2
+ module Hiccup
3
+ module Enumerable
4
+ class ScheduleEnumerator
5
+
6
+ def initialize(schedule, date)
7
+ @schedule = schedule
8
+ @date = date
9
+ @date = @date.to_date if @date.respond_to?(:to_date)
10
+ @current_date = nil
11
+ end
12
+
13
+ attr_reader :schedule
14
+ delegate :start_date, :weekly_pattern, :monthly_pattern, :ends?, :end_date, :skip, :to => :schedule
15
+
16
+
17
+
18
+ def next
19
+ @current_date = if @current_date
20
+ next_occurrence_after(@current_date)
21
+ else
22
+ first_occurrence_on_or_after(@date)
23
+ end
24
+ @current_date = nil if (ends? && @current_date && @current_date > end_date)
25
+ @current_date
26
+ end
27
+
28
+ def prev
29
+ @current_date = if @current_date
30
+ next_occurrence_before(@current_date)
31
+ else
32
+ first_occurrence_on_or_before(@date)
33
+ end
34
+ @current_date = nil if (@current_date && @current_date < start_date)
35
+ @current_date
36
+ end
37
+
38
+
39
+
40
+ # These two methods DO NOT assume that
41
+ # date is predicted by the given schedule
42
+
43
+ def first_occurrence_on_or_after(date)
44
+ raise NotImplementedError
45
+ end
46
+
47
+ def first_occurrence_on_or_before(date)
48
+ raise NotImplementedError
49
+ end
50
+
51
+
52
+ # These two methods DO assume that
53
+ # date is predicted by the given schedule
54
+
55
+ def next_occurrence_after(date)
56
+ first_occurrence_on_or_after(date + 1)
57
+ end
58
+
59
+ def next_occurrence_before(date)
60
+ first_occurrence_on_or_before(date - 1)
61
+ end
62
+
63
+
64
+
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,63 @@
1
+ require 'hiccup/enumerable/schedule_enumerator'
2
+
3
+ module Hiccup
4
+ module Enumerable
5
+ class WeeklyEnumerator < ScheduleEnumerator
6
+
7
+
8
+ def initialize(*args)
9
+ super
10
+
11
+ # Use more efficient iterator methods if
12
+ # weekly_pattern is simple enough
13
+
14
+ if weekly_pattern.length == 1
15
+ def self.next_occurrence_after(date)
16
+ date + skip * 7
17
+ end
18
+
19
+ def self.next_occurrence_before(date)
20
+ date - skip * 7
21
+ end
22
+ end
23
+ end
24
+
25
+
26
+ def first_occurrence_on_or_after(date)
27
+ result = nil
28
+ wday = date.wday
29
+ weekly_pattern.each do |weekday|
30
+ wd = Date::DAYNAMES.index(weekday)
31
+ wd = wd + 7 if wd < wday
32
+ days_in_the_future = wd - wday
33
+ temp = date + days_in_the_future
34
+
35
+ remainder = ((temp - start_date) / 7).to_i % skip
36
+ temp += (skip - remainder) * 7 if remainder > 0
37
+
38
+ result = temp if !result || (temp < result)
39
+ end
40
+ result
41
+ end
42
+
43
+ def first_occurrence_on_or_before(date)
44
+ result = nil
45
+ wday = date.wday
46
+ weekly_pattern.each do |weekday|
47
+ wd = Date::DAYNAMES.index(weekday)
48
+ wd = wd - 7 if wd > wday
49
+ days_in_the_past = wday - wd
50
+ temp = date - days_in_the_past
51
+
52
+ remainder = ((temp - start_date) / 7).to_i % skip
53
+ temp -= remainder * 7 if remainder > 0
54
+
55
+ result = temp if !result || (temp > result)
56
+ end
57
+ result
58
+ end
59
+
60
+
61
+ end
62
+ end
63
+ end
@@ -1,173 +1,65 @@
1
- require "hiccup/convenience"
2
1
  require "hiccup/core_ext/date"
3
- require "hiccup/core_ext/duration"
2
+ require "hiccup/enumerable/annually_enumerator"
3
+ require "hiccup/enumerable/monthly_enumerator"
4
+ require "hiccup/enumerable/never_enumerator"
5
+ require "hiccup/enumerable/weekly_enumerator"
4
6
 
5
7
 
6
8
  module Hiccup
7
9
  module Enumerable
8
- include Convenience
10
+
11
+
12
+
13
+ def enumerator
14
+ @enumerator ||= "Hiccup::Enumerable::#{kind.to_s.classify}Enumerator".constantize
15
+ end
16
+
17
+ def kind=(value)
18
+ super
19
+ @enumerator = nil
20
+ end
9
21
 
10
22
 
11
23
 
12
24
  def occurrences_during_month(year, month)
13
25
  date1 = Date.new(year, month, 1)
14
- date2 = date1.at_end_of_month
26
+ date2 = Date.new(year, month, -1)
15
27
  occurrences_between(date1, date2)
16
28
  end
17
29
 
18
30
 
19
31
 
20
32
  def occurrences_between(earlier_date, later_date)
21
- [].tap do |occurrences|
22
- if (!ends? || earlier_date <= end_date) && (later_date >= start_date)
23
- earlier_date = start_date if (earlier_date < start_date)
24
- later_date = end_date if ends? && (later_date > end_date)
25
- occurrence = first_occurrence_on_or_after(earlier_date)
26
- while occurrence && (occurrence <= later_date)
27
- occurrences << occurrence
28
- occurrence = next_occurrence_after(occurrence)
29
- end
30
- end
33
+ earlier_date = start_date if earlier_date < start_date
34
+
35
+ occurrences = []
36
+ enum = enumerator.new(self, earlier_date)
37
+ while (occurrence = enum.next) && (occurrence <= later_date)
38
+ occurrences << occurrence
31
39
  end
40
+ occurrences
32
41
  end
33
42
 
34
43
 
35
44
 
36
45
  def first_occurrence_on_or_after(date)
37
- date = date.to_date unless date.is_a?(Date) || !date.respond_to?(:to_date)
38
- date = self.start_date if (date < self.start_date)
39
-
40
- result = nil
41
- case kind
42
- when :never
43
- result = date if date == self.start_date # (date > self.start_date) ? nil : self.start_date
44
-
45
- when :weekly
46
- wday = date.wday
47
- weekly_pattern.each do |weekday|
48
- wd = Date::DAYNAMES.index(weekday)
49
- wd = wd + 7 if wd < wday
50
- days_in_the_future = wd - wday
51
- temp = days_in_the_future.days.after(date)
52
-
53
- remainder = ((temp - start_date) / 7).to_i % skip
54
- temp = (skip - remainder).weeks.after(temp) if (remainder > 0)
55
-
56
- result = temp if !result || (temp < result)
57
- end
58
-
59
- when :monthly
60
- monthly_pattern.each do |occurrence|
61
- temp = nil
62
- (0...30).each do |i| # If an occurrence doesn't occur this month, try up to 30 months in the future
63
- temp = monthly_occurrence_to_date(occurrence, i.months.after(date))
64
- break if temp && (temp >= date)
65
- end
66
- next unless temp
67
-
68
- remainder = months_between(temp, start_date) % skip
69
- temp = monthly_occurrence_to_date(occurrence, (skip - remainder).months.after(temp)) if (remainder > 0)
70
- next unless temp
71
-
72
- result = temp if !result || (temp < result)
73
- end
74
-
75
- when :annually
76
- result, try_to_use_2_29 = if((start_date.month == 2) && (start_date.day == 29))
77
- [Date.new(date.year, 2, 28), true]
78
- else
79
- [Date.new(date.year, start_date.month, start_date.day), false]
80
- end
81
- result = 1.year.after(result) if (result < date)
82
-
83
- remainder = years_between(result, start_date) % skip
84
- result = (skip - remainder).years.after(result) if (remainder > 0)
85
-
86
- if try_to_use_2_29
87
- begin
88
- date = Date.new(result.year, 2, 29)
89
- rescue
90
- end
91
- end
92
- end
93
-
94
- result = nil if (self.ends? && result && result > self.end_date)
95
- result
46
+ date = start_date if (date < start_date)
47
+ enumerator.new(self, date).next
96
48
  end
97
49
 
98
-
99
-
100
50
  def next_occurrence_after(date)
101
- first_occurrence_on_or_after(1.day.after(date))
51
+ first_occurrence_on_or_after(date + 1)
102
52
  end
103
53
 
104
54
 
105
55
 
106
56
  def first_occurrence_on_or_before(date)
107
- date = date.to_date unless date.is_a?(Date) || !date.respond_to?(:to_date)
108
- date = self.end_date if (self.ends? && date > self.end_date)
109
-
110
- result = nil
111
- case kind
112
- when :never
113
- result = date # (date > self.start_date) ? nil : self.start_date
114
-
115
- when :weekly
116
- wday = date.wday
117
- weekly_pattern.each do |weekday|
118
- wd = Date::DAYNAMES.index(weekday)
119
- wd = wd - 7 if wd > wday
120
- days_in_the_past = wday - wd
121
- temp = days_in_the_past.days.before(date)
122
-
123
- remainder = ((temp - start_date) / 7).to_i % skip
124
- temp = remainder.weeks.before(temp) if (remainder > 0)
125
-
126
- result = temp if !result || (temp > result)
127
- end
128
-
129
- when :monthly
130
- monthly_pattern.each do |occurrence|
131
- temp = nil
132
- (0...30).each do |i| # If an occurrence doesn't occur this month, try up to 30 months in the past
133
- temp = monthly_occurrence_to_date(occurrence, i.months.before(date))
134
- break if temp && (temp <= date)
135
- end
136
- next unless temp
137
-
138
- remainder = months_between(temp, start_date) % skip
139
- temp = monthly_occurrence_to_date(occurrence, remainder.months.before(temp)) if (remainder > 0)
140
- next unless temp
141
-
142
- result = temp if !result || (temp > result)
143
- end
144
-
145
- when :annually
146
- result, try_to_use_2_29 = if((start_date.month == 2) && (start_date.day == 29))
147
- [Date.new(date.year, 2, 28), true]
148
- else
149
- [Date.new(date.year, start_date.month, start_date.day), false]
150
- end
151
- result = 1.year.before(result) if (result < date)
152
-
153
- remainder = years_between(result, start_date) % skip
154
- result = remainder.years.before(result) if (remainder > 0)
155
- if try_to_use_2_29
156
- begin
157
- date = Date.new(result.year, 2, 29)
158
- rescue
159
- end
160
- end
161
- end
162
-
163
- result = nil if (result && result < self.start_date)
164
- result
57
+ date = end_date if (ends? && date > end_date)
58
+ enumerator.new(self, date).prev
165
59
  end
166
60
 
167
-
168
-
169
61
  def first_occurrence_before(date)
170
- first_occurrence_on_or_before(1.day.before(date))
62
+ first_occurrence_on_or_before(date - 1)
171
63
  end
172
64
 
173
65
 
@@ -181,68 +73,21 @@ module Hiccup
181
73
 
182
74
 
183
75
  def n_occurrences_before(limit, date)
184
- n_occurrences_on_or_before(limit, 1.day.before(date))
76
+ n_occurrences_on_or_before(limit, date - 1)
185
77
  end
186
78
 
187
79
  def n_occurrences_on_or_before(limit, date)
80
+ date = end_date if (ends? && date > end_date)
81
+
188
82
  occurrences = []
189
- occurrence = first_occurrence_on_or_before(date)
190
- while occurrence && occurrences.length < limit
83
+ enum = enumerator.new(self, date)
84
+ while (occurrence = enum.prev) && occurrences.length < limit
191
85
  occurrences << occurrence
192
- occurrence = first_occurrence_before(occurrence)
193
86
  end
194
87
  occurrences
195
88
  end
196
89
 
197
90
 
198
91
 
199
- def monthly_occurrence_to_date(occurrence, date)
200
- year, month = date.year, date.month
201
-
202
- day = begin
203
- if occurrence.is_a?(Array)
204
- ordinal, weekday = occurrence
205
- wday_of_first_of_month = Date.new(year, month, 1).wday
206
- wday = Date::DAYNAMES.index(weekday)
207
- day = wday
208
- day = day + 7 if (wday < wday_of_first_of_month)
209
- day = day - wday_of_first_of_month
210
- day = day + (ordinal * 7) - 6
211
- else
212
- occurrence
213
- end
214
- end
215
-
216
- last_day_of_month = Date.new(year, month, -1).day
217
- (day > last_day_of_month) ? nil : Date.new(year, month, day)
218
- end
219
-
220
-
221
-
222
- private
223
-
224
-
225
-
226
- def months_between(date1, date2)
227
- later_date, earlier_date = sort_dates(date1, date2)
228
- later_date.get_months_since(earlier_date)
229
- end
230
-
231
- def weeks_between(date1, date2)
232
- later_date, earlier_date = sort_dates(date1, date2)
233
- later_date.get_weeks_since(earlier_date)
234
- end
235
-
236
- def years_between(date1, date2)
237
- later_date, earlier_date = sort_dates(date1, date2)
238
- later_date.get_years_since(earlier_date)
239
- end
240
-
241
- def sort_dates(date1, date2)
242
- (date1 > date2) ? [date1, date2] : [date2, date1]
243
- end
244
-
245
-
246
-
247
92
  end
248
93
  end
@@ -2,29 +2,138 @@ require 'active_support/concern'
2
2
  require 'active_support/core_ext/date/conversions'
3
3
  require 'hiccup/core_ext/enumerable'
4
4
  require 'hiccup/core_ext/hash'
5
+ require "hiccup/core_ext/date"
5
6
 
6
7
 
7
8
  module Hiccup
8
- module Inferrable
9
+ module Inferable
9
10
  extend ActiveSupport::Concern
10
11
 
11
12
  module ClassMethods
12
13
 
14
+ def infer(dates, options={})
15
+ dates = extract_array_of_dates!(dates)
16
+
17
+ enumerator = DatesEnumerator.new(dates)
18
+ guesser = Guesser.new(self, options)
19
+ confidences = []
20
+ confidence_threshold = 0.6
21
+ rewind_by = 0
22
+
23
+ until enumerator.done?
24
+ date = enumerator.next
25
+ guesser << date
26
+ confidences << guesser.confidence.to_f
27
+
28
+ if guesser.predicted?(date)
29
+ rewind_by = 0
30
+ else
31
+ rewind_by += 1
32
+ end
33
+
34
+ # puts "date: #{date}, confidences: #{confidences}, rewind_by: #{rewind_by}" if options[:verbose]
35
+
36
+ # if the last two confidences are both below a certain
37
+ # threshhold and both declining, back up to where we
38
+ # started to go wrong and start a new schedule.
39
+
40
+ if confidences.length >= 3 &&
41
+ confidences[-1] < confidence_threshold &&
42
+ confidences[-2] < confidence_threshold &&
43
+ confidences[-1] < confidences[-2] &&
44
+ confidences[-2] < confidences[-3]
45
+
46
+ rewind_by -= 1 if rewind_by == guesser.count
47
+ enumerator.rewind_by(rewind_by)
48
+ guesser.restart!
49
+ confidences = []
50
+ rewind_by = 0
51
+ end
52
+ end
53
+
54
+ guesser.stop!
55
+
56
+ guesser.guesses
57
+ end
13
58
 
14
59
 
15
- def infer(dates, options={})
60
+
61
+ def extract_array_of_dates!(dates)
62
+ raise_invalid_dates_error! unless dates.respond_to?(:each)
63
+ dates.map { |date| assert_date!(date) }.sort
64
+ end
65
+
66
+ def assert_date!(date)
67
+ return date if date.is_a?(Date)
68
+ date.to_date rescue raise_invalid_dates_error!
69
+ end
70
+
71
+ def raise_invalid_dates_error!
72
+ raise ArgumentError.new("Inferable.infer expects to receive a collection of dates")
73
+ end
74
+
75
+
76
+
77
+ end
78
+
79
+
80
+
81
+ class Guesser
82
+
83
+ def initialize(klass, options={})
84
+ @klass = klass
16
85
  @verbose = options.fetch(:verbose, false)
17
- dates = extract_array_of_dates!(dates)
18
- guesses = generate_guesses(dates)
19
- guess, score = pick_best_guess(guesses, dates)
20
- guess
86
+ @guesses = []
87
+ start!
21
88
  end
22
89
 
90
+ attr_reader :guesses, :confidence
91
+
92
+ def start!
93
+ save_current_guess!
94
+ reset_growing_understanding!
95
+ end
96
+ alias :restart! :start!
23
97
 
98
+ def stop!
99
+ start!
100
+ end
101
+
102
+ def save_current_guess!
103
+ @guesses << @schedule if @schedule
104
+ end
105
+
106
+ def reset_growing_understanding!
107
+ @dates = []
108
+ @schedule = nil
109
+ @confidence = 0
110
+ end
111
+
112
+
113
+
114
+ def <<(date)
115
+ @dates << date
116
+ @schedule, @confidence = best_schedule_for(@dates)
117
+ end
118
+
119
+ def count
120
+ @dates.length
121
+ end
122
+
123
+ def predicted?(date)
124
+ @schedule && @schedule.contains?(date)
125
+ end
126
+
127
+
128
+
129
+ def best_schedule_for(dates)
130
+ guesses = generate_guesses(dates)
131
+ pick_best_guess(guesses, dates)
132
+ end
24
133
 
25
134
  def generate_guesses(dates)
26
- @start_date = dates.min
27
- @end_date = dates.max
135
+ @start_date = dates.first
136
+ @end_date = dates.last
28
137
  [].tap do |guesses|
29
138
  guesses.concat generate_yearly_guesses(dates)
30
139
  guesses.concat generate_monthly_guesses(dates)
@@ -43,7 +152,7 @@ module Hiccup
43
152
 
44
153
  [].tap do |guesses|
45
154
  (1...5).each do |skip|
46
- guesses << self.new.tap do |schedule|
155
+ guesses << @klass.new.tap do |schedule|
47
156
  schedule.kind = :annually
48
157
  schedule.start_date = start_date
49
158
  schedule.end_date = @end_date
@@ -75,7 +184,7 @@ module Hiccup
75
184
  [].tap do |guesses|
76
185
  (1...5).each do |skip|
77
186
  enumerate_by_popularity(days_by_popularity) do |days|
78
- guesses << self.new.tap do |schedule|
187
+ guesses << @klass.new.tap do |schedule|
79
188
  schedule.kind = :monthly
80
189
  schedule.start_date = @start_date
81
190
  schedule.end_date = @end_date
@@ -85,7 +194,7 @@ module Hiccup
85
194
  end
86
195
 
87
196
  enumerate_by_popularity(patterns_by_popularity) do |patterns|
88
- guesses << self.new.tap do |schedule|
197
+ guesses << @klass.new.tap do |schedule|
89
198
  schedule.kind = :monthly
90
199
  schedule.start_date = @start_date
91
200
  schedule.end_date = @end_date
@@ -114,7 +223,7 @@ module Hiccup
114
223
 
115
224
  (1...5).each do |skip|
116
225
  enumerate_by_popularity(wdays_by_popularity) do |wdays|
117
- guesses << self.new.tap do |schedule|
226
+ guesses << @klass.new.tap do |schedule|
118
227
  schedule.kind = :weekly
119
228
  schedule.start_date = @start_date
120
229
  schedule.end_date = @end_date
@@ -158,8 +267,7 @@ module Hiccup
158
267
  puts ""
159
268
  end
160
269
 
161
- best_guess = scored_guesses.reject { |(guess, score)| score.to_f < 0.333 }.first
162
- best_guess || Schedule.new(kind: :never)
270
+ scored_guesses.reject { |(guess, score)| score.to_f < 0.333 }.first
163
271
  end
164
272
 
165
273
  def score_guess(guess, input_dates)
@@ -194,22 +302,6 @@ module Hiccup
194
302
 
195
303
 
196
304
 
197
- def extract_array_of_dates!(dates)
198
- raise_invalid_dates_error! unless dates.respond_to?(:each)
199
- dates.map { |date| assert_date!(date) }.sort
200
- end
201
-
202
- def assert_date!(date)
203
- return date if date.is_a?(Date)
204
- date.to_date rescue raise_invalid_dates_error!
205
- end
206
-
207
- def raise_invalid_dates_error!
208
- raise ArgumentError.new("Inferrable.infer expects to receive a collection of dates")
209
- end
210
-
211
-
212
-
213
305
  class Score < Struct.new(:prediction_rate, :brick_rate, :complexity_rate)
214
306
 
215
307
  # as brick rate rises, our confidence in this guess drops
@@ -244,6 +336,37 @@ module Hiccup
244
336
  end
245
337
 
246
338
 
339
+
340
+ end
341
+
342
+
343
+
344
+ class DatesEnumerator
345
+
346
+ def initialize(dates)
347
+ @dates = dates
348
+ @last_index = @dates.length - 1
349
+ @index = -1
350
+ end
351
+
352
+ def done?
353
+ @index == @last_index
354
+ end
355
+
356
+ def next
357
+ @index += 1
358
+ raise OutOfRangeException if @index > @last_index
359
+ @dates[@index]
360
+ end
361
+
362
+ def rewind_by(n)
363
+ @index -= n
364
+ @index = -1 if @index < -1
365
+ end
366
+
247
367
  end
368
+
369
+
370
+
248
371
  end
249
372
  end
@@ -11,7 +11,7 @@ module Hiccup
11
11
  hiccup :enumerable,
12
12
  :validatable,
13
13
  :humanizable,
14
- :inferrable,
14
+ :inferable,
15
15
  :serializable => [:ical]
16
16
 
17
17
 
@@ -1,3 +1,3 @@
1
1
  module Hiccup
2
- VERSION = "0.3.0"
2
+ VERSION = "0.4.0"
3
3
  end
@@ -1,4 +1,5 @@
1
1
  require "test_helper"
2
+ require "hiccup/core_ext/duration"
2
3
 
3
4
 
4
5
  class DurationExtTest < ActiveSupport::TestCase
@@ -3,6 +3,7 @@ require "test_helper"
3
3
 
4
4
  class EnumerableTest < ActiveSupport::TestCase
5
5
  include Hiccup
6
+ PERFORMANCE_TEST = false
6
7
 
7
8
 
8
9
 
@@ -18,6 +19,18 @@ class EnumerableTest < ActiveSupport::TestCase
18
19
 
19
20
 
20
21
 
22
+ test "annual recurrence with a skip" do
23
+ schedule = Schedule.new({
24
+ :kind => :annually,
25
+ :skip => 2,
26
+ :start_date => Date.new(2009,3,4)})
27
+ expected_dates = %w{2009-03-04 2011-03-04 2013-03-04}
28
+ actual_dates = schedule.occurrences_between(Date.new(2009, 01, 01), Date.new(2013, 12, 31)).map(&:to_s)
29
+ assert_equal expected_dates, actual_dates
30
+ end
31
+
32
+
33
+
21
34
  def test_occurs_on_weekly
22
35
  schedule = Schedule.new({
23
36
  :kind => :weekly,
@@ -277,6 +290,7 @@ class EnumerableTest < ActiveSupport::TestCase
277
290
  });
278
291
 
279
292
  assert_equal [Date.new(2010, 2, 28)], schedule.occurrences_during_month(2010, 2)
293
+ assert_equal [Date.new(2012, 2, 29)], schedule.occurrences_during_month(2012, 2)
280
294
  end
281
295
 
282
296
 
@@ -286,7 +300,7 @@ class EnumerableTest < ActiveSupport::TestCase
286
300
  :kind => :monthly,
287
301
  :monthly_pattern => [31],
288
302
  :start_date => Date.new(2008, 2, 29)
289
- });
303
+ })
290
304
 
291
305
  assert_equal [Date.new(2010, 1, 31)], schedule.occurrences_during_month(2010, 1)
292
306
  assert_equal [], schedule.occurrences_during_month(2010, 2)
@@ -294,38 +308,69 @@ class EnumerableTest < ActiveSupport::TestCase
294
308
 
295
309
 
296
310
 
297
- # def test_monthly_occurrence_to_date
298
- # occurrence = MonthlyOccurrence.new(:ordinal => 3, :kind => "Thursday")
299
- # assert_equal 21, occurrence.to_date(2009, 5).day, "The third Thursday in May 2009 should be the 21st"
300
- # assert_equal 16, occurrence.to_date(2009, 4).day, "The third Thursday in April 2009 should be the 16th"
301
- # assert_equal 15, occurrence.to_date(2012, 11).day, "The third Thursday in November 2012 should be the 15th"
302
- #
303
- # occurrence = MonthlyOccurrence.new(:ordinal => 1, :kind => "Tuesday")
304
- # assert_equal 5, occurrence.to_date(2009, 5).day, "The first Tuesday in May 2009 should be the 5th"
305
- # assert_equal 2, occurrence.to_date(2008, 12).day, "The first Tuesday in December 2008 should be the 2nd"
306
- # assert_equal 1, occurrence.to_date(2009, 9).day, "The first Tuesday in September 2009 should be the 1st"
307
- #
308
- # occurrence = MonthlyOccurrence.new(:ordinal => 1, :kind => "Sunday")
309
- # assert_equal 3, occurrence.to_date(2009, 5).day, "The first Sunday in May 2009 should be the 3rd"
310
- # assert_equal 1, occurrence.to_date(2010, 8).day, "The first Sunday in August 2010 should be the 1st"
311
- # assert_equal 7, occurrence.to_date(2009, 6).day, "The first Sunday in June 2009 should be the 7th"
312
- #
313
- # occurrence = MonthlyOccurrence.new(:ordinal => 1, :kind => "day")
314
- # assert_equal 1, occurrence.to_date(2009, 5).day, "The first of May 2009 should be 1"
315
- #
316
- # occurrence = MonthlyOccurrence.new(:ordinal => 22, :kind => "day")
317
- # assert_equal 22, occurrence.to_date(2009, 5).day, "The twenty-second of May 2009 should be 22"
318
- #
319
- # #occurrence = MonthlyOccurrence.new(:ordinal => -1, :kind => "Sunday")
320
- # #assert_equal 31, occurrence.to_date(2009, 5), "The last Sunday in May 2009 should be the 31st"
321
- # end
322
- #
323
- #
324
- # test "to_date should return nil if there is no occurrence with the specified parameters" do
325
- # mo = MonthlyOccurrence.new(:ordinal => 5, :kind => "Monday")
326
- # assert_equal "fifth Monday", mo.to_s
327
- # assert_nil mo.to_date(2010, 6)
328
- # end
311
+ if PERFORMANCE_TEST
312
+ test "performance test" do
313
+ n = 100
314
+
315
+ # Each of these schedules should describe 52 events
316
+
317
+ Benchmark.bm(20) do |x|
318
+ x.report("weekly (simple):") do
319
+ n.times do
320
+ Schedule.new(
321
+ :kind => :weekly,
322
+ :weekly_pattern => ["Friday"],
323
+ :start_date => Date.new(2009, 1, 1)) \
324
+ .occurrences_between(Date.new(2009, 1, 1), Date.new(2009, 12, 31))
325
+ end
326
+ end
327
+ x.report("weekly (complex):") do
328
+ n.times do
329
+ Schedule.new(
330
+ :kind => :weekly,
331
+ :weekly_pattern => ["Monday", "Wednesday", "Friday"],
332
+ :start_date => Date.new(2009, 1, 1)) \
333
+ .occurrences_between(Date.new(2009, 1, 1), Date.new(2009, 5, 2))
334
+ end
335
+ end
336
+ x.report("monthly (simple):") do
337
+ n.times do
338
+ Schedule.new(
339
+ :kind => :monthly,
340
+ :monthly_pattern => [[2, "Monday"]],
341
+ :start_date => Date.new(2009, 1, 1)) \
342
+ .occurrences_between(Date.new(2009, 1, 1), Date.new(2013, 4, 30))
343
+ end
344
+ end
345
+ x.report("monthly (complex):") do
346
+ n.times do
347
+ Schedule.new(
348
+ :kind => :monthly,
349
+ :monthly_pattern => [[2, "Monday"], [4, "Monday"]],
350
+ :start_date => Date.new(2009, 1, 1)) \
351
+ .occurrences_between(Date.new(2009, 1, 1), Date.new(2011, 3, 1))
352
+ end
353
+ end
354
+ x.report("yearly:") do
355
+ n.times do
356
+ Schedule.new(
357
+ :kind => :annually,
358
+ :start_date => Date.new(1960, 3, 15)) \
359
+ .occurrences_between(Date.new(1960, 1, 1), Date.new(2011, 12, 31))
360
+ end
361
+ end
362
+ x.report("yearly (2/29):") do
363
+ n.times do
364
+ Schedule.new(
365
+ :kind => :annually,
366
+ :start_date => Date.new(1960, 2, 29)) \
367
+ .occurrences_between(Date.new(1960, 1, 1), Date.new(2011, 12, 31))
368
+ end
369
+ end
370
+ end
371
+
372
+ end
373
+ end
329
374
 
330
375
 
331
376
 
@@ -3,7 +3,7 @@ require "test_helper"
3
3
  # Ideas: see 3/4/10, 3/5/11, 3/4/12 as closer to
4
4
  # a pattern than 3/4/10, 9/15/11, 3/4/12.
5
5
 
6
- class InferrableTest < ActiveSupport::TestCase
6
+ class InferableTest < ActiveSupport::TestCase
7
7
  include Hiccup
8
8
 
9
9
 
@@ -46,7 +46,7 @@ class InferrableTest < ActiveSupport::TestCase
46
46
  # If bricks and fails were equal, the annual recurrence would be the
47
47
  # preferred guess, but the monthly one makes more sense.
48
48
  # It is better to brick than to fail.
49
- schedule = Schedule.infer(dates)
49
+ schedule = Schedule.infer(dates).first
50
50
  assert_equal :monthly, schedule.kind
51
51
  end
52
52
 
@@ -58,8 +58,8 @@ class InferrableTest < ActiveSupport::TestCase
58
58
 
59
59
  test "should infer an annual" do
60
60
  dates = %w{2010-3-4 2011-3-4 2012-3-4}
61
- schedule = Schedule.infer(dates)
62
- assert_equal "Every year on March 4", schedule.humanize
61
+ schedules = Schedule.infer(dates)
62
+ assert_equal ["Every year on March 4"], schedules.map(&:humanize)
63
63
  end
64
64
 
65
65
 
@@ -67,22 +67,22 @@ class InferrableTest < ActiveSupport::TestCase
67
67
 
68
68
  test "should infer a schedule that occurs every other year" do
69
69
  dates = %w{2010-3-4 2012-3-4 2014-3-4}
70
- schedule = Schedule.infer(dates)
71
- assert_equal "Every other year on March 4", schedule.humanize
70
+ schedules = Schedule.infer(dates)
71
+ assert_equal ["Every other year on March 4"], schedules.map(&:humanize)
72
72
  end
73
73
 
74
74
  # ... where some of the input is wrong
75
75
 
76
76
  test "should infer a yearly schedule when one of the dates was rescheduled" do
77
77
  dates = %w{2010-3-4 2011-9-15 2012-3-4 2013-3-4}
78
- schedule = Schedule.infer(dates)
79
- assert_equal "Every year on March 4", schedule.humanize
78
+ schedules = Schedule.infer(dates)
79
+ assert_equal ["Every year on March 4"], schedules.map(&:humanize)
80
80
  end
81
81
 
82
82
  test "should infer a yearly schedule when the first date was rescheduled" do
83
83
  dates = %w{2010-3-6 2011-3-4 2012-3-4 2013-3-4}
84
- schedule = Schedule.infer(dates)
85
- assert_equal "Every year on March 4", schedule.humanize
84
+ schedules = Schedule.infer(dates)
85
+ assert_equal ["Every year on March 4"], schedules.map(&:humanize)
86
86
  end
87
87
 
88
88
 
@@ -93,24 +93,24 @@ class InferrableTest < ActiveSupport::TestCase
93
93
 
94
94
  test "should infer a monthly schedule that occurs on a date" do
95
95
  dates = %w{2012-2-4 2012-3-4 2012-4-4}
96
- schedule = Schedule.infer(dates)
97
- assert_equal "The 4th of every month", schedule.humanize
96
+ schedules = Schedule.infer(dates)
97
+ assert_equal ["The 4th of every month"], schedules.map(&:humanize)
98
98
 
99
99
  dates = %w{2012-2-17 2012-3-17 2012-4-17}
100
- schedule = Schedule.infer(dates)
101
- assert_equal "The 17th of every month", schedule.humanize
100
+ schedules = Schedule.infer(dates)
101
+ assert_equal ["The 17th of every month"], schedules.map(&:humanize)
102
102
  end
103
103
 
104
104
  test "should infer a monthly schedule that occurs on a weekday" do
105
105
  dates = %w{2012-7-9 2012-8-13 2012-9-10}
106
- schedule = Schedule.infer(dates)
107
- assert_equal "The second Monday of every month", schedule.humanize
106
+ schedules = Schedule.infer(dates)
107
+ assert_equal ["The second Monday of every month"], schedules.map(&:humanize)
108
108
  end
109
109
 
110
110
  test "should infer a schedule that occurs several times a month" do
111
111
  dates = %w{2012-7-9 2012-7-23 2012-8-13 2012-8-27 2012-9-10 2012-9-24}
112
- schedule = Schedule.infer(dates)
113
- assert_equal "The second Monday and fourth Monday of every month", schedule.humanize
112
+ schedules = Schedule.infer(dates)
113
+ assert_equal ["The second Monday and fourth Monday of every month"], schedules.map(&:humanize)
114
114
  end
115
115
 
116
116
 
@@ -118,8 +118,8 @@ class InferrableTest < ActiveSupport::TestCase
118
118
 
119
119
  test "should infer a schedule that occurs every third month" do
120
120
  dates = %w{2012-2-4 2012-5-4 2012-8-4}
121
- schedule = Schedule.infer(dates)
122
- assert_equal "The 4th of every third month", schedule.humanize
121
+ schedules = Schedule.infer(dates)
122
+ assert_equal ["The 4th of every third month"], schedules.map(&:humanize)
123
123
  end
124
124
 
125
125
 
@@ -127,33 +127,33 @@ class InferrableTest < ActiveSupport::TestCase
127
127
 
128
128
  test "should infer a monthly (by day) schedule when one day was rescheduled" do
129
129
  dates = %w{2012-10-02 2012-11-02 2012-12-03}
130
- schedule = Schedule.infer(dates)
131
- assert_equal "The 2nd of every month", schedule.humanize
130
+ schedules = Schedule.infer(dates)
131
+ assert_equal ["The 2nd of every month"], schedules.map(&:humanize)
132
132
  end
133
133
 
134
134
  test "should infer a monthly (by day) schedule when the first day was rescheduled" do
135
135
  dates = %w{2012-10-03 2012-11-02 2012-12-02}
136
- schedule = Schedule.infer(dates)
137
- assert_equal "The 2nd of every month", schedule.humanize
136
+ schedules = Schedule.infer(dates)
137
+ assert_equal ["The 2nd of every month"], schedules.map(&:humanize)
138
138
  end
139
139
 
140
140
 
141
141
  test "should infer a monthly (by weekday) schedule when one day was rescheduled" do
142
142
  dates = %w{2012-10-02 2012-11-06 2012-12-05} # 1st Tuesday, 1st Tuesday, 1st Wednesday
143
- schedule = Schedule.infer(dates)
144
- assert_equal "The first Tuesday of every month", schedule.humanize
143
+ schedules = Schedule.infer(dates)
144
+ assert_equal ["The first Tuesday of every month"], schedules.map(&:humanize)
145
145
  end
146
146
 
147
147
  test "should infer a monthly (by weekday) schedule when the first day was rescheduled" do
148
148
  dates = %w{2012-10-03 2012-11-01 2012-12-06} # 1st Wednesday, 1st Thursday, 1st Thursday
149
- schedule = Schedule.infer(dates)
150
- assert_equal "The first Thursday of every month", schedule.humanize
149
+ schedules = Schedule.infer(dates)
150
+ assert_equal ["The first Thursday of every month"], schedules.map(&:humanize)
151
151
  end
152
152
 
153
153
  test "should infer a monthly (by weekday) schedule when the first day was rescheduled 2" do
154
154
  dates = %w{2012-10-11 2012-11-01 2012-12-06} # 2nd Thursday, 1st Thursday, 1st Thursday
155
- schedule = Schedule.infer(dates)
156
- assert_equal "The first Thursday of every month", schedule.humanize
155
+ schedules = Schedule.infer(dates)
156
+ assert_equal ["The first Thursday of every month"], schedules.map(&:humanize)
157
157
  end
158
158
 
159
159
 
@@ -164,14 +164,14 @@ class InferrableTest < ActiveSupport::TestCase
164
164
 
165
165
  test "should infer a weekly schedule" do
166
166
  dates = %w{2012-3-4 2012-3-11 2012-3-18}
167
- schedule = Schedule.infer(dates)
168
- assert_equal "Every Sunday", schedule.humanize
167
+ schedules = Schedule.infer(dates)
168
+ assert_equal ["Every Sunday"], schedules.map(&:humanize)
169
169
  end
170
170
 
171
171
  test "should infer a schedule that occurs several times a week" do
172
172
  dates = %w{2012-3-6 2012-3-8 2012-3-13 2012-3-15 2012-3-20 2012-3-22}
173
- schedule = Schedule.infer(dates)
174
- assert_equal "Every Tuesday and Thursday", schedule.humanize
173
+ schedules = Schedule.infer(dates)
174
+ assert_equal ["Every Tuesday and Thursday"], schedules.map(&:humanize)
175
175
  end
176
176
 
177
177
 
@@ -179,8 +179,8 @@ class InferrableTest < ActiveSupport::TestCase
179
179
 
180
180
  test "should infer weekly recurrence for something that occurs every other week" do
181
181
  dates = %w{2012-3-6 2012-3-8 2012-3-20 2012-3-22}
182
- schedule = Schedule.infer(dates)
183
- assert_equal "Tuesday and Thursday of every other week", schedule.humanize
182
+ schedules = Schedule.infer(dates)
183
+ assert_equal ["Tuesday and Thursday of every other week"], schedules.map(&:humanize)
184
184
  end
185
185
 
186
186
 
@@ -188,14 +188,14 @@ class InferrableTest < ActiveSupport::TestCase
188
188
 
189
189
  test "should infer a weekly schedule (missing dates)" do
190
190
  dates = %w{2012-3-4 2012-3-11 2012-3-25}
191
- schedule = Schedule.infer(dates)
192
- assert_equal "Every Sunday", schedule.humanize
191
+ schedules = Schedule.infer(dates)
192
+ assert_equal ["Every Sunday"], schedules.map(&:humanize)
193
193
  end
194
194
 
195
195
  test "should infer a schedule that occurs several times a week (missing dates)" do
196
196
  dates = %w{2012-3-6 2012-3-8 2012-3-15 2012-3-20 2012-3-27 2012-3-29}
197
- schedule = Schedule.infer(dates)
198
- assert_equal "Every Tuesday and Thursday", schedule.humanize
197
+ schedules = Schedule.infer(dates)
198
+ assert_equal ["Every Tuesday and Thursday"], schedules.map(&:humanize)
199
199
  end
200
200
 
201
201
 
@@ -203,14 +203,14 @@ class InferrableTest < ActiveSupport::TestCase
203
203
 
204
204
  test "should infer a weekly schedule when one day was rescheduled" do
205
205
  dates = %w{2012-10-02 2012-10-09 2012-10-15} # a Tuesday, a Tuesday, and a Monday
206
- schedule = Schedule.infer(dates)
207
- assert_equal "Every Tuesday", schedule.humanize
206
+ schedules = Schedule.infer(dates)
207
+ assert_equal ["Every Tuesday"], schedules.map(&:humanize)
208
208
  end
209
209
 
210
210
  test "should infer a weekly schedule when the first day was rescheduled" do
211
211
  dates = %w{2012-10-07 2012-10-10 2012-10-17} # a Sunday, a Wednesday, and a Wednesday
212
- schedule = Schedule.infer(dates)
213
- assert_equal "Every Wednesday", schedule.humanize
212
+ schedules = Schedule.infer(dates)
213
+ assert_equal ["Every Wednesday"], schedules.map(&:humanize)
214
214
  end
215
215
 
216
216
 
@@ -225,20 +225,28 @@ class InferrableTest < ActiveSupport::TestCase
225
225
  ]
226
226
 
227
227
  arbitrary_date_ranges.each do |dates|
228
- schedule = Schedule.infer(dates)
229
- fail "There should be no pattern to the dates #{dates}, but Hiccup guessed \"#{schedule.humanize}\"" unless schedule.never?
228
+ schedules = Schedule.infer(dates)
229
+ fail "There should be no pattern to the dates #{dates}, but Hiccup guessed \"#{schedules.map(&:humanize)}\"" if schedules.any?
230
230
  end
231
231
  end
232
232
 
233
233
 
234
234
 
235
+ test "should infer multiple schedules from mixed input" do
236
+ dates = %w{2012-11-05 2012-11-12 2012-11-19 2012-11-28 2012-12-05 2012-12-12} # three Mondays then three Wednesdays
237
+ schedules = Schedule.infer(dates)
238
+ assert_equal ["Every Monday", "Every Wednesday"],
239
+ schedules.map(&:humanize)
240
+ end
241
+
242
+
243
+
235
244
  test "should diabolically complex schedules" do
236
245
  dates = %w{2012-11-06 2012-11-08 2012-11-15 2012-11-20 2012-11-27 2012-11-29 2013-02-05 2013-02-14 2013-02-21 2013-02-19 2013-02-26 2013-05-07 2013-05-09 2013-05-16 2013-05-28 2013-05-21 2013-05-30}
237
- schedule = Schedule.infer(dates)
238
- assert_equal "The first Tuesday, second Thursday, third Thursday, third Tuesday, fourth Tuesday, and fifth Thursday of every third month", schedule.humanize
246
+ schedules = Schedule.infer(dates)
247
+ assert_equal ["The first Tuesday, second Thursday, third Thursday, third Tuesday, fourth Tuesday, and fifth Thursday of every third month"], schedules.map(&:humanize)
239
248
  end
240
249
 
241
250
 
242
251
 
243
-
244
252
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: hiccup
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.4.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-11-06 00:00:00.000000000 Z
12
+ date: 2012-11-12 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: activesupport
@@ -145,8 +145,13 @@ files:
145
145
  - lib/hiccup/core_ext/fixnum.rb
146
146
  - lib/hiccup/core_ext/hash.rb
147
147
  - lib/hiccup/enumerable.rb
148
+ - lib/hiccup/enumerable/annually_enumerator.rb
149
+ - lib/hiccup/enumerable/monthly_enumerator.rb
150
+ - lib/hiccup/enumerable/never_enumerator.rb
151
+ - lib/hiccup/enumerable/schedule_enumerator.rb
152
+ - lib/hiccup/enumerable/weekly_enumerator.rb
148
153
  - lib/hiccup/humanizable.rb
149
- - lib/hiccup/inferrable.rb
154
+ - lib/hiccup/inferable.rb
150
155
  - lib/hiccup/schedule.rb
151
156
  - lib/hiccup/serializable/ical.rb
152
157
  - lib/hiccup/serializers/ical.rb
@@ -174,7 +179,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
174
179
  version: '0'
175
180
  segments:
176
181
  - 0
177
- hash: -955647137280487206
182
+ hash: -1083227062532008741
178
183
  required_rubygems_version: !ruby/object:Gem::Requirement
179
184
  none: false
180
185
  requirements:
@@ -183,10 +188,10 @@ required_rubygems_version: !ruby/object:Gem::Requirement
183
188
  version: '0'
184
189
  segments:
185
190
  - 0
186
- hash: -955647137280487206
191
+ hash: -1083227062532008741
187
192
  requirements: []
188
193
  rubyforge_project: hiccup
189
- rubygems_version: 1.8.23
194
+ rubygems_version: 1.8.24
190
195
  signing_key:
191
196
  specification_version: 3
192
197
  summary: A library for working with things that recur