hiccup 0.4.0 → 0.4.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -11,17 +11,13 @@ module Hiccup
11
11
 
12
12
 
13
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
14
+ "Hiccup::Enumerable::#{kind.to_s.classify}Enumerator".constantize
20
15
  end
21
16
 
22
17
 
23
18
 
24
19
  def occurrences_during_month(year, month)
20
+ puts "DEPRECATED: `occurrences_during_month` will be removed in 0.5.0. Use `occurrences_between` instead"
25
21
  date1 = Date.new(year, month, 1)
26
22
  date2 = Date.new(year, month, -1)
27
23
  occurrences_between(date1, date2)
@@ -30,8 +26,6 @@ module Hiccup
30
26
 
31
27
 
32
28
  def occurrences_between(earlier_date, later_date)
33
- earlier_date = start_date if earlier_date < start_date
34
-
35
29
  occurrences = []
36
30
  enum = enumerator.new(self, earlier_date)
37
31
  while (occurrence = enum.next) && (occurrence <= later_date)
@@ -43,42 +37,41 @@ module Hiccup
43
37
 
44
38
 
45
39
  def first_occurrence_on_or_after(date)
46
- date = start_date if (date < start_date)
40
+ return nil if ends? && date > end_date
47
41
  enumerator.new(self, date).next
48
42
  end
49
43
 
50
- def next_occurrence_after(date)
51
- first_occurrence_on_or_after(date + 1)
44
+ def first_occurrence_after(date)
45
+ first_occurrence_on_or_after(date.to_date + 1)
52
46
  end
47
+ alias :next_occurrence_after :first_occurrence_after
53
48
 
54
49
 
55
50
 
56
51
  def first_occurrence_on_or_before(date)
57
- date = end_date if (ends? && date > end_date)
52
+ return nil if date < start_date
58
53
  enumerator.new(self, date).prev
59
54
  end
60
55
 
61
56
  def first_occurrence_before(date)
62
- first_occurrence_on_or_before(date - 1)
57
+ first_occurrence_on_or_before(date.to_date - 1)
63
58
  end
64
59
 
65
60
 
66
61
 
67
62
  def occurs_on(date)
68
63
  date = date.to_date
69
- first_occurrence_on_or_after(date).eql?(date)
64
+ date == first_occurrence_on_or_after(date)
70
65
  end
71
66
  alias :contains? :occurs_on
72
67
 
73
68
 
74
69
 
75
70
  def n_occurrences_before(limit, date)
76
- n_occurrences_on_or_before(limit, date - 1)
71
+ n_occurrences_on_or_before(limit, date.to_date - 1)
77
72
  end
78
73
 
79
74
  def n_occurrences_on_or_before(limit, date)
80
- date = end_date if (ends? && date > end_date)
81
-
82
75
  occurrences = []
83
76
  enum = enumerator.new(self, date)
84
77
  while (occurrence = enum.prev) && occurrences.length < limit
@@ -7,6 +7,8 @@ module Hiccup
7
7
  @schedule = schedule
8
8
  @date = date
9
9
  @date = @date.to_date if @date.respond_to?(:to_date)
10
+ @date = start_date if (@date < start_date)
11
+ @date = end_date if (ends? && @date > end_date)
10
12
  @current_date = nil
11
13
  end
12
14
 
@@ -23,7 +23,7 @@ module Hiccup
23
23
  end
24
24
 
25
25
 
26
- def first_occurrence_on_or_after(date)
26
+ def first_occurrence_on_or_after(date)
27
27
  result = nil
28
28
  wday = date.wday
29
29
  weekly_pattern.each do |weekday|
@@ -10,7 +10,7 @@ module Hiccup
10
10
 
11
11
  def humanize
12
12
  case kind
13
- when :never; ""
13
+ when :never; start_date.to_s
14
14
  when :weekly; weekly_humanize
15
15
  when :monthly; monthly_humanize
16
16
  when :annually; yearly_humanize
@@ -12,48 +12,89 @@ module Hiccup
12
12
  module ClassMethods
13
13
 
14
14
  def infer(dates, options={})
15
- dates = extract_array_of_dates!(dates)
15
+ allow_null_schedules = options.fetch(:allow_null_schedules, false)
16
+ verbosity = options.fetch(:verbosity, (options[:verbose] ? 1 : 0)) # 0, 1, or 2
16
17
 
18
+ dates = extract_array_of_dates!(dates)
17
19
  enumerator = DatesEnumerator.new(dates)
18
- guesser = Guesser.new(self, options)
20
+ guesser = Guesser.new(self, {verbose: verbosity >= 2})
21
+ schedules = []
22
+
19
23
  confidences = []
20
- confidence_threshold = 0.6
21
- rewind_by = 0
24
+ high_confidence_threshold = 0.6
25
+ min_confidence_threshold = 0.35
26
+
27
+ last_confident_schedule = nil
28
+ iterations_since_last_confident_schedule = 0
22
29
 
23
30
  until enumerator.done?
24
31
  date = enumerator.next
25
32
  guesser << date
26
- confidences << guesser.confidence.to_f
33
+ confidence = guesser.confidence.to_f
34
+ confidences << confidence
35
+ predicted = guesser.predicted?(date)
36
+
37
+ # if the last two confidences are both below a certain
38
+ # threshhold and both declining, back up to where we
39
+ # started to go wrong and start a new schedule.
40
+
41
+ confident = !(confidences.length >= 3 && (
42
+ (confidences[-1] < high_confidence_threshold &&
43
+ confidences[-2] < high_confidence_threshold &&
44
+ confidences[-1] < confidences[-2] &&
45
+ confidences[-2] < confidences[-3]) ||
46
+ (confidences[-1] < min_confidence_threshold &&
47
+ confidences[-2] < min_confidence_threshold)))
27
48
 
28
- if guesser.predicted?(date)
29
- rewind_by = 0
49
+ if predicted && confidence >= min_confidence_threshold
50
+ iterations_since_last_confident_schedule = 0
51
+ last_confident_schedule = guesser.schedule
30
52
  else
31
- rewind_by += 1
53
+ iterations_since_last_confident_schedule += 1
32
54
  end
33
55
 
34
- # puts "date: #{date}, confidences: #{confidences}, rewind_by: #{rewind_by}" if options[:verbose]
56
+ rewind_by = iterations_since_last_confident_schedule == guesser.count ? iterations_since_last_confident_schedule - 1 : iterations_since_last_confident_schedule
35
57
 
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
58
 
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]
59
+
60
+ if verbosity >= 1
61
+ output = " #{enumerator.index.to_s.rjust(3)} #{date}"
62
+ output << " #{"[#{guesser.count}]".rjust(5)} => "
63
+ output << "~#{(guesser.confidence.to_f * 100).to_i.to_s.rjust(2, "0")} @ "
64
+ output << guesser.schedule.humanize.ljust(130)
65
+ output << " :( move back #{rewind_by}" unless confident
66
+ puts output
67
+ end
68
+
69
+
70
+
71
+ unless confident
72
+
73
+ if last_confident_schedule
74
+ schedules << last_confident_schedule
75
+ elsif allow_null_schedules
76
+ guesser.dates.take(guesser.count - rewind_by).each do |date|
77
+ schedules << self.new(:kind => :never, :start_date => date)
78
+ end
79
+ end
45
80
 
46
- rewind_by -= 1 if rewind_by == guesser.count
47
81
  enumerator.rewind_by(rewind_by)
48
82
  guesser.restart!
49
83
  confidences = []
50
- rewind_by = 0
84
+ iterations_since_last_confident_schedule = 0
85
+ last_confident_schedule = nil
51
86
  end
52
87
  end
53
88
 
54
- guesser.stop!
89
+ if last_confident_schedule
90
+ schedules << last_confident_schedule
91
+ elsif allow_null_schedules
92
+ guesser.dates.each do |date|
93
+ schedules << self.new(:kind => :never, :start_date => date)
94
+ end
95
+ end
55
96
 
56
- guesser.guesses
97
+ schedules
57
98
  end
58
99
 
59
100
 
@@ -83,37 +124,25 @@ module Hiccup
83
124
  def initialize(klass, options={})
84
125
  @klass = klass
85
126
  @verbose = options.fetch(:verbose, false)
86
- @guesses = []
87
127
  start!
88
128
  end
89
129
 
90
- attr_reader :guesses, :confidence
130
+ attr_reader :confidence, :schedule, :dates
91
131
 
92
132
  def start!
93
- save_current_guess!
94
- reset_growing_understanding!
95
- end
96
- alias :restart! :start!
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
133
  @dates = []
108
134
  @schedule = nil
109
135
  @confidence = 0
110
136
  end
137
+ alias :restart! :start!
138
+
111
139
 
112
140
 
113
141
 
114
142
  def <<(date)
115
143
  @dates << date
116
144
  @schedule, @confidence = best_schedule_for(@dates)
145
+ date
117
146
  end
118
147
 
119
148
  def count
@@ -149,7 +178,7 @@ module Hiccup
149
178
  highest_popularity = patterns_by_popularity.keys.max # => 5
150
179
  most_popular = patterns_by_popularity[highest_popularity].first # => a
151
180
  start_date = Date.new(@start_date.year, *most_popular)
152
-
181
+
153
182
  [].tap do |guesses|
154
183
  (1...5).each do |skip|
155
184
  guesses << @klass.new.tap do |schedule|
@@ -267,7 +296,7 @@ module Hiccup
267
296
  puts ""
268
297
  end
269
298
 
270
- scored_guesses.reject { |(guess, score)| score.to_f < 0.333 }.first
299
+ scored_guesses.first
271
300
  end
272
301
 
273
302
  def score_guess(guess, input_dates)
@@ -349,6 +378,8 @@ module Hiccup
349
378
  @index = -1
350
379
  end
351
380
 
381
+ attr_reader :index
382
+
352
383
  def done?
353
384
  @index == @last_index
354
385
  end
@@ -1,3 +1,3 @@
1
1
  module Hiccup
2
- VERSION = "0.4.0"
2
+ VERSION = "0.4.1"
3
3
  end
@@ -189,6 +189,43 @@ class EnumerableTest < ActiveSupport::TestCase
189
189
 
190
190
 
191
191
 
192
+ test "should not predict dates before the beginning of a schedule" do
193
+ schedule = Schedule.new({
194
+ :kind => :weekly,
195
+ :weekly_pattern => %w{Monday},
196
+ :start_date => Date.new(2011, 1, 3),
197
+ :ends => true,
198
+ :end_date => Date.new(2011, 1, 31)})
199
+ assert_equal nil, schedule.first_occurrence_before(Date.new(2011,1,3))
200
+ assert_equal nil, schedule.first_occurrence_on_or_before(Date.new(2011,1,2))
201
+ end
202
+
203
+ test "should not predict dates after the end of a schedule" do
204
+ schedule = Schedule.new({
205
+ :kind => :weekly,
206
+ :weekly_pattern => %w{Monday},
207
+ :start_date => Date.new(2011, 1, 3),
208
+ :ends => true,
209
+ :end_date => Date.new(2011, 1, 31)})
210
+ assert_equal nil, schedule.first_occurrence_after(Date.new(2011,1,31))
211
+ assert_equal nil, schedule.first_occurrence_on_or_after(Date.new(2011,2, 1))
212
+ end
213
+
214
+
215
+
216
+ test "all methods should take any kind of date as an argument" do
217
+ schedule = Schedule.new({
218
+ :kind => :weekly,
219
+ :weekly_pattern => %w{Monday},
220
+ :start_date => Date.new(2011, 1, 1),
221
+ :ends => true,
222
+ :end_date => Date.new(2011, 1, 31)})
223
+ assert_equal Date.new(2011, 1, 17), schedule.first_occurrence_after(Time.new(2011, 1, 10))
224
+ assert_equal Date.new(2011, 1, 3), schedule.first_occurrence_before(Time.new(2011, 1, 10))
225
+ end
226
+
227
+
228
+
192
229
  def test_weekly_recurrence_and_skip
193
230
  schedule = Schedule.new({
194
231
  :kind => :weekly,
@@ -230,13 +230,20 @@ class InferableTest < ActiveSupport::TestCase
230
230
  end
231
231
  end
232
232
 
233
+ test "should create one non-recurring schedule for each break if asked" do
234
+ dates = %w{2012-03-05 2012-11-28 2012-12-05 2012-12-12} # a random Monday, then three Wednesdays
235
+ schedules = Schedule.infer(dates, allow_null_schedules: true)
236
+
237
+ assert_equal ["2012-03-05", "Every Wednesday"], schedules.map(&:humanize)
238
+ end
239
+
233
240
 
234
241
 
235
242
  test "should infer multiple schedules from mixed input" do
236
243
  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
244
  schedules = Schedule.infer(dates)
238
- assert_equal ["Every Monday", "Every Wednesday"],
239
- schedules.map(&:humanize)
245
+
246
+ assert_equal ["Every Monday", "Every Wednesday"], schedules.map(&:humanize)
240
247
  end
241
248
 
242
249
 
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.4.0
4
+ version: 0.4.1
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-12 00:00:00.000000000 Z
12
+ date: 2012-11-21 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: activesupport
@@ -179,7 +179,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
179
179
  version: '0'
180
180
  segments:
181
181
  - 0
182
- hash: -1083227062532008741
182
+ hash: -1495672392285233886
183
183
  required_rubygems_version: !ruby/object:Gem::Requirement
184
184
  none: false
185
185
  requirements:
@@ -188,7 +188,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
188
188
  version: '0'
189
189
  segments:
190
190
  - 0
191
- hash: -1083227062532008741
191
+ hash: -1495672392285233886
192
192
  requirements: []
193
193
  rubyforge_project: hiccup
194
194
  rubygems_version: 1.8.24