hiccup 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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