hiccup 0.5.14 → 0.5.15
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.
- checksums.yaml +4 -4
- data/lib/hiccup.rb +17 -17
- data/lib/hiccup/convenience.rb +9 -9
- data/lib/hiccup/core_ext/date.rb +7 -7
- data/lib/hiccup/core_ext/duration.rb +4 -4
- data/lib/hiccup/core_ext/enumerable.rb +2 -2
- data/lib/hiccup/core_ext/fixnum.rb +2 -2
- data/lib/hiccup/core_ext/hash.rb +2 -2
- data/lib/hiccup/enumerable.rb +43 -29
- data/lib/hiccup/enumerable/annually_enumerator.rb +23 -23
- data/lib/hiccup/enumerable/monthly_date_enumerator.rb +2 -2
- data/lib/hiccup/enumerable/monthly_enumerator.rb +40 -40
- data/lib/hiccup/enumerable/never_enumerator.rb +8 -8
- data/lib/hiccup/enumerable/schedule_enumerator.rb +39 -39
- data/lib/hiccup/enumerable/weekly_enumerator.rb +30 -30
- data/lib/hiccup/errors.rb +4 -0
- data/lib/hiccup/humanizable.rb +19 -19
- data/lib/hiccup/inferable.rb +30 -30
- data/lib/hiccup/inferable/dates_enumerator.rb +6 -6
- data/lib/hiccup/inferable/guesser.rb +21 -21
- data/lib/hiccup/inferable/score.rb +7 -7
- data/lib/hiccup/inferable/scorer.rb +19 -19
- data/lib/hiccup/schedule.rb +10 -10
- data/lib/hiccup/serializable/ical.rb +13 -13
- data/lib/hiccup/serializers/ical.rb +59 -59
- data/lib/hiccup/validatable.rb +23 -23
- data/lib/hiccup/version.rb +1 -1
- data/test/core_ext_date_test.rb +5 -5
- data/test/duration_ext_test.rb +8 -8
- data/test/enumerable_test.rb +103 -103
- data/test/humanizable_test.rb +24 -24
- data/test/ical_serializable_test.rb +29 -29
- data/test/inferrable_test.rb +84 -84
- data/test/leap_year_test.rb +7 -7
- data/test/monthly_enumerator_test.rb +13 -13
- data/test/performance_test.rb +7 -7
- data/test/validatable_test.rb +1 -1
- data/test/weekly_enumerator_test.rb +38 -38
- metadata +4 -3
@@ -3,7 +3,7 @@ require 'hiccup/enumerable/schedule_enumerator'
|
|
3
3
|
module Hiccup
|
4
4
|
module Enumerable
|
5
5
|
class MonthlyEnumerator < ScheduleEnumerator
|
6
|
-
|
6
|
+
|
7
7
|
def self.for(schedule)
|
8
8
|
if schedule.monthly_pattern.empty?
|
9
9
|
NeverEnumerator
|
@@ -13,45 +13,45 @@ module Hiccup
|
|
13
13
|
self
|
14
14
|
end
|
15
15
|
end
|
16
|
-
|
17
|
-
|
18
|
-
|
16
|
+
|
17
|
+
|
18
|
+
|
19
19
|
def started?
|
20
20
|
!@position.nil?
|
21
21
|
end
|
22
|
-
|
23
|
-
|
24
|
-
|
22
|
+
|
23
|
+
|
24
|
+
|
25
25
|
protected
|
26
|
-
|
26
|
+
|
27
27
|
attr_reader :year, :month, :cycle, :last_day_of_month
|
28
|
-
|
29
|
-
|
30
|
-
|
28
|
+
|
29
|
+
|
30
|
+
|
31
31
|
def advance!
|
32
32
|
@position += 1
|
33
33
|
next_month if @position >= cycle.length
|
34
|
-
|
34
|
+
|
35
35
|
day = cycle[@position]
|
36
36
|
return self.next if day > last_day_of_month
|
37
37
|
Date.new(year, month, day)
|
38
38
|
rescue
|
39
39
|
advance!
|
40
40
|
end
|
41
|
-
|
41
|
+
|
42
42
|
def rewind!
|
43
43
|
@position -= 1
|
44
44
|
prev_month if @position < 0
|
45
|
-
|
45
|
+
|
46
46
|
day = cycle[@position]
|
47
47
|
return self.prev if day > last_day_of_month
|
48
48
|
Date.new(year, month, day)
|
49
49
|
rescue
|
50
50
|
rewind!
|
51
51
|
end
|
52
|
-
|
53
|
-
|
54
|
-
|
52
|
+
|
53
|
+
|
54
|
+
|
55
55
|
def first_occurrence_on_or_after(date)
|
56
56
|
@year, @month, seed_day = date.year, date.month, date.day
|
57
57
|
if skip > 1
|
@@ -62,19 +62,19 @@ module Hiccup
|
|
62
62
|
seed_day = first_day_of_month
|
63
63
|
end
|
64
64
|
end
|
65
|
-
|
65
|
+
|
66
66
|
get_context
|
67
|
-
|
67
|
+
|
68
68
|
@position = cycle.index { |day| day >= seed_day }
|
69
69
|
next_month unless @position
|
70
|
-
|
70
|
+
|
71
71
|
day = cycle[@position]
|
72
72
|
return self.next if day > last_day_of_month
|
73
73
|
Date.new(year, month, day)
|
74
74
|
rescue
|
75
75
|
advance!
|
76
76
|
end
|
77
|
-
|
77
|
+
|
78
78
|
def first_occurrence_on_or_before(date)
|
79
79
|
@year, @month, seed_day = date.year, date.month, date.day
|
80
80
|
if skip > 1
|
@@ -86,21 +86,21 @@ module Hiccup
|
|
86
86
|
seed_day = last_day_of_month
|
87
87
|
end
|
88
88
|
end
|
89
|
-
|
89
|
+
|
90
90
|
get_context
|
91
|
-
|
91
|
+
|
92
92
|
@position = cycle.rindex { |day| day <= seed_day }
|
93
93
|
prev_month unless @position
|
94
|
-
|
94
|
+
|
95
95
|
day = cycle[@position]
|
96
96
|
return self.prev if day > last_day_of_month
|
97
97
|
Date.new(year, month, day)
|
98
98
|
rescue
|
99
99
|
rewind!
|
100
100
|
end
|
101
|
-
|
102
|
-
|
103
|
-
|
101
|
+
|
102
|
+
|
103
|
+
|
104
104
|
def occurrences_in_month(year, month)
|
105
105
|
wday_of_first_of_month = Date.new(year, month, 1).wday
|
106
106
|
monthly_pattern.map do |occurrence|
|
@@ -117,48 +117,48 @@ module Hiccup
|
|
117
117
|
end
|
118
118
|
end
|
119
119
|
end
|
120
|
-
|
121
|
-
|
122
|
-
|
120
|
+
|
121
|
+
|
122
|
+
|
123
123
|
def next_month
|
124
124
|
@position = 0
|
125
125
|
add_to_months skip
|
126
126
|
get_context
|
127
127
|
end
|
128
|
-
|
128
|
+
|
129
129
|
def add_to_months(offset)
|
130
130
|
@month += offset
|
131
131
|
@year, @month = @year + 1, @month - 12 while @month > 12
|
132
132
|
end
|
133
|
-
|
133
|
+
|
134
134
|
def prev_month
|
135
135
|
@position = @cycle.length - 1
|
136
136
|
subtract_from_months skip
|
137
137
|
get_context
|
138
138
|
end
|
139
|
-
|
139
|
+
|
140
140
|
def subtract_from_months(offset)
|
141
141
|
@month -= offset
|
142
142
|
@year, @month = @year - 1, @month + 12 while @month < 1
|
143
143
|
end
|
144
|
-
|
144
|
+
|
145
145
|
def get_context
|
146
146
|
@last_day_of_month = [4, 6, 9, 11].member?(month) ? 30 : 31
|
147
147
|
@last_day_of_month = leap_year?(year) ? 29 : 28 if month == 2
|
148
148
|
@cycle = occurrences_in_month(year, month).sort
|
149
149
|
end
|
150
|
-
|
151
|
-
|
152
|
-
|
150
|
+
|
151
|
+
|
152
|
+
|
153
153
|
def months_since_schedule_start(year, month)
|
154
154
|
(year - start_date.year) * 12 + (month - start_date.month)
|
155
155
|
end
|
156
|
-
|
156
|
+
|
157
157
|
def first_day_of_month
|
158
158
|
1
|
159
159
|
end
|
160
|
-
|
161
|
-
|
160
|
+
|
161
|
+
|
162
162
|
end
|
163
163
|
end
|
164
164
|
end
|
@@ -3,26 +3,26 @@ require 'hiccup/enumerable/schedule_enumerator'
|
|
3
3
|
module Hiccup
|
4
4
|
module Enumerable
|
5
5
|
class NeverEnumerator < ScheduleEnumerator
|
6
|
-
|
7
|
-
|
6
|
+
|
7
|
+
|
8
8
|
def next
|
9
9
|
@cursor = @cursor ? nil : first_occurrence_on_or_after(seed_date)
|
10
10
|
end
|
11
|
-
|
11
|
+
|
12
12
|
def prev
|
13
13
|
@cursor = @cursor ? nil : first_occurrence_on_or_before(seed_date)
|
14
14
|
end
|
15
|
-
|
16
|
-
|
15
|
+
|
16
|
+
|
17
17
|
def first_occurrence_on_or_after(date)
|
18
18
|
start_date if date <= start_date
|
19
19
|
end
|
20
|
-
|
20
|
+
|
21
21
|
def first_occurrence_on_or_before(date)
|
22
22
|
start_date unless date < start_date
|
23
23
|
end
|
24
|
-
|
25
|
-
|
24
|
+
|
25
|
+
|
26
26
|
end
|
27
27
|
end
|
28
28
|
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
module Hiccup
|
2
2
|
module Enumerable
|
3
3
|
class ScheduleEnumerator
|
4
|
-
|
4
|
+
|
5
5
|
def self.enum_for(schedule)
|
6
6
|
case schedule.kind
|
7
7
|
when :weekly then WeeklyEnumerator
|
@@ -10,9 +10,9 @@ module Hiccup
|
|
10
10
|
else NeverEnumerator
|
11
11
|
end
|
12
12
|
end
|
13
|
-
|
14
|
-
|
15
|
-
|
13
|
+
|
14
|
+
|
15
|
+
|
16
16
|
def initialize(schedule, seed_date)
|
17
17
|
@schedule = schedule
|
18
18
|
@ends = schedule.ends?
|
@@ -20,95 +20,95 @@ module Hiccup
|
|
20
20
|
@seed_date = seed_date.to_date if seed_date.respond_to?(:to_date)
|
21
21
|
@cursor = nil
|
22
22
|
end
|
23
|
-
|
23
|
+
|
24
24
|
attr_reader :schedule, :seed_date, :cursor
|
25
|
-
|
26
|
-
|
27
|
-
|
25
|
+
|
26
|
+
|
27
|
+
|
28
28
|
def next
|
29
29
|
@cursor = started? ? advance! : first_occurrence_on_or_after(seed_start_date)
|
30
30
|
return nil if @cursor && ends? && @cursor > end_date
|
31
31
|
@cursor
|
32
32
|
end
|
33
|
-
|
33
|
+
|
34
34
|
def prev
|
35
35
|
@cursor = started? ? rewind! : first_occurrence_on_or_before(seed_end_date)
|
36
36
|
return nil if @cursor && @cursor < start_date
|
37
37
|
@cursor
|
38
38
|
end
|
39
|
-
|
40
|
-
|
41
|
-
|
39
|
+
|
40
|
+
|
41
|
+
|
42
42
|
def started?
|
43
43
|
!@cursor.nil?
|
44
44
|
end
|
45
|
-
|
45
|
+
|
46
46
|
def ends?
|
47
47
|
@ends
|
48
48
|
end
|
49
|
-
|
50
|
-
|
51
|
-
|
49
|
+
|
50
|
+
|
51
|
+
|
52
52
|
protected
|
53
|
-
|
54
|
-
|
55
|
-
|
53
|
+
|
54
|
+
|
55
|
+
|
56
56
|
delegate :start_date, :weekly_pattern, :monthly_pattern, :end_date, :skip, :to => :schedule
|
57
|
-
|
58
|
-
|
59
|
-
|
57
|
+
|
58
|
+
|
59
|
+
|
60
60
|
def leap_year?(year)
|
61
61
|
return false unless (year % 4).zero?
|
62
62
|
return (year % 400).zero? if (year % 100).zero?
|
63
63
|
true
|
64
64
|
end
|
65
|
-
|
66
|
-
|
67
|
-
|
65
|
+
|
66
|
+
|
67
|
+
|
68
68
|
def seed_start_date
|
69
69
|
return start_date if (seed_date < start_date)
|
70
70
|
seed_date
|
71
71
|
end
|
72
|
-
|
72
|
+
|
73
73
|
def seed_end_date
|
74
74
|
return end_date if (ends? && seed_date > end_date)
|
75
75
|
seed_date
|
76
76
|
end
|
77
|
-
|
78
|
-
|
79
|
-
|
77
|
+
|
78
|
+
|
79
|
+
|
80
80
|
# These two methods DO assume that
|
81
81
|
# date is predicted by the given schedule
|
82
82
|
# Subclasses can probably supply more
|
83
83
|
# performant implementations of these.
|
84
|
-
|
84
|
+
|
85
85
|
def advance!
|
86
86
|
puts "calling ScheduleEnumerator#advance! slow!"
|
87
87
|
first_occurrence_on_or_after(cursor + 1)
|
88
88
|
end
|
89
|
-
|
89
|
+
|
90
90
|
def rewind!
|
91
91
|
puts "calling ScheduleEnumerator#rewind! slow!"
|
92
92
|
first_occurrence_on_or_before(cursor - 1)
|
93
93
|
end
|
94
|
-
|
95
|
-
|
96
|
-
|
94
|
+
|
95
|
+
|
96
|
+
|
97
97
|
# These two methods DO NOT assume that
|
98
98
|
# date is predicted by the given schedule
|
99
99
|
# Subclasses _must_ provide implementations
|
100
100
|
# of these methods.
|
101
|
-
|
101
|
+
|
102
102
|
def first_occurrence_on_or_after(date)
|
103
103
|
raise NotImplementedError
|
104
104
|
end
|
105
|
-
|
105
|
+
|
106
106
|
def first_occurrence_on_or_before(date)
|
107
107
|
raise NotImplementedError
|
108
108
|
end
|
109
|
-
|
110
|
-
|
111
|
-
|
109
|
+
|
110
|
+
|
111
|
+
|
112
112
|
end
|
113
113
|
end
|
114
114
|
end
|
@@ -3,57 +3,57 @@ require 'hiccup/enumerable/schedule_enumerator'
|
|
3
3
|
module Hiccup
|
4
4
|
module Enumerable
|
5
5
|
class WeeklyEnumerator < ScheduleEnumerator
|
6
|
-
|
6
|
+
|
7
7
|
def initialize(*args)
|
8
8
|
super
|
9
|
-
|
9
|
+
|
10
10
|
@wday_pattern = weekly_pattern.map do |weekday|
|
11
11
|
Date::DAYNAMES.index(weekday)
|
12
12
|
end.sort
|
13
|
-
|
13
|
+
|
14
14
|
if @wday_pattern.empty?
|
15
15
|
@base_date = start_date
|
16
16
|
@starting_index = 0
|
17
17
|
@cycle = []
|
18
18
|
return
|
19
19
|
end
|
20
|
-
|
20
|
+
|
21
21
|
start_wday = start_date.wday
|
22
22
|
if start_wday <= @wday_pattern.first or start_wday > @wday_pattern.last
|
23
23
|
@base_date = start_date
|
24
24
|
else
|
25
25
|
@base_date = start_date - (start_wday - @wday_pattern.first)
|
26
26
|
end
|
27
|
-
|
27
|
+
|
28
28
|
@starting_index = wday_pattern.index { |wday| wday >= start_wday } || 0
|
29
29
|
@cycle = calculate_cycle(schedule)
|
30
30
|
end
|
31
|
-
|
31
|
+
|
32
32
|
protected
|
33
|
-
|
34
|
-
|
35
|
-
|
33
|
+
|
34
|
+
|
35
|
+
|
36
36
|
attr_reader :base_date,
|
37
37
|
:wday_pattern,
|
38
38
|
:starting_index,
|
39
39
|
:cycle,
|
40
40
|
:position
|
41
|
-
|
42
|
-
|
43
|
-
|
41
|
+
|
42
|
+
|
43
|
+
|
44
44
|
def advance!
|
45
45
|
date = cursor + cycle[position]
|
46
46
|
@position = (position + 1) % cycle.length
|
47
47
|
date
|
48
48
|
end
|
49
|
-
|
49
|
+
|
50
50
|
def rewind!
|
51
51
|
@position = position <= 0 ? cycle.length - 1 : position - 1
|
52
52
|
cursor - cycle[position]
|
53
53
|
end
|
54
|
-
|
55
|
-
|
56
|
-
|
54
|
+
|
55
|
+
|
56
|
+
|
57
57
|
def first_occurrence_on_or_after(date)
|
58
58
|
result = nil
|
59
59
|
wday = date.wday
|
@@ -61,16 +61,16 @@ module Hiccup
|
|
61
61
|
wd = wd + 7 if wd < wday
|
62
62
|
days_in_the_future = wd - wday
|
63
63
|
temp = date + days_in_the_future
|
64
|
-
|
64
|
+
|
65
65
|
remainder = ((temp - base_date) / 7).to_i % skip
|
66
66
|
temp += (skip - remainder) * 7 if remainder > 0
|
67
|
-
|
67
|
+
|
68
68
|
result = temp if !result || (temp < result)
|
69
69
|
end
|
70
70
|
@position = position_of(result) if result
|
71
71
|
result
|
72
72
|
end
|
73
|
-
|
73
|
+
|
74
74
|
def first_occurrence_on_or_before(date)
|
75
75
|
result = nil
|
76
76
|
wday = date.wday
|
@@ -78,43 +78,43 @@ module Hiccup
|
|
78
78
|
wd = wd - 7 if wd > wday
|
79
79
|
days_in_the_past = wday - wd
|
80
80
|
temp = date - days_in_the_past
|
81
|
-
|
81
|
+
|
82
82
|
remainder = ((temp - base_date) / 7).to_i % skip
|
83
83
|
temp -= remainder * 7 if remainder > 0
|
84
|
-
|
84
|
+
|
85
85
|
result = temp if !result || (temp > result)
|
86
86
|
end
|
87
87
|
@position = position_of(result) if result
|
88
88
|
result
|
89
89
|
end
|
90
|
-
|
91
|
-
|
92
|
-
|
90
|
+
|
91
|
+
|
92
|
+
|
93
93
|
def calculate_cycle(schedule)
|
94
94
|
cycle = []
|
95
95
|
offset = wday_pattern[starting_index]
|
96
96
|
wdays = wday_pattern.map { |wday| wday - offset }.sort
|
97
|
-
|
97
|
+
|
98
98
|
while wdays.first <= 0
|
99
99
|
wdays.push (wdays.shift + 7 * skip)
|
100
100
|
end
|
101
|
-
|
101
|
+
|
102
102
|
cycle = [wdays.first]
|
103
103
|
wdays.each_cons(2) do |wday1, wday2|
|
104
104
|
cycle << (wday2 - wday1)
|
105
105
|
end
|
106
106
|
cycle
|
107
107
|
end
|
108
|
-
|
108
|
+
|
109
109
|
def position_of(date)
|
110
110
|
date_i = wday_pattern.index(date.wday)
|
111
111
|
position = date_i - starting_index
|
112
112
|
position += wday_pattern.length if position < 0
|
113
113
|
position
|
114
114
|
end
|
115
|
-
|
116
|
-
|
117
|
-
|
115
|
+
|
116
|
+
|
117
|
+
|
118
118
|
end
|
119
119
|
end
|
120
120
|
end
|