ice_cube 0.9.1 → 0.9.2
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/ice_cube/builders/hash_builder.rb +1 -1
- data/lib/ice_cube/builders/string_builder.rb +4 -4
- data/lib/ice_cube/rule.rb +1 -1
- data/lib/ice_cube/rules/secondly_rule.rb +1 -1
- data/lib/ice_cube/schedule.rb +27 -10
- data/lib/ice_cube/single_occurrence_rule.rb +1 -1
- data/lib/ice_cube/time_util.rb +21 -2
- data/lib/ice_cube/validated_rule.rb +57 -36
- data/lib/ice_cube/validations/count.rb +1 -1
- data/lib/ice_cube/validations/day.rb +1 -1
- data/lib/ice_cube/validations/hourly_interval.rb +1 -1
- data/lib/ice_cube/validations/lock.rb +2 -2
- data/lib/ice_cube/validations/until.rb +1 -0
- data/lib/ice_cube/validations/weekly_interval.rb +1 -1
- data/lib/ice_cube/version.rb +1 -1
- metadata +2 -2
@@ -41,8 +41,8 @@ module IceCube
|
|
41
41
|
class << self
|
42
42
|
|
43
43
|
NUMBER_SUFFIX = ['th', 'st', 'nd', 'rd', 'th', 'th', 'th', 'th', 'th', 'th']
|
44
|
-
SPECIAL_SUFFIX = { 11 => 'th', 12 => 'th', 13 => 'th', 14 => 'th' }
|
45
|
-
|
44
|
+
SPECIAL_SUFFIX = { 11 => 'th', 12 => 'th', 13 => 'th', 14 => 'th' }
|
45
|
+
|
46
46
|
# influenced by ActiveSupport's to_sentence
|
47
47
|
def sentence(array)
|
48
48
|
case array.length
|
@@ -63,12 +63,12 @@ module IceCube
|
|
63
63
|
else
|
64
64
|
suffix = SPECIAL_SUFFIX.include?(number) ?
|
65
65
|
SPECIAL_SUFFIX[number] : NUMBER_SUFFIX[number.abs % 10]
|
66
|
-
number.to_s << suffix
|
66
|
+
number.to_s << suffix
|
67
67
|
end
|
68
68
|
end
|
69
69
|
|
70
70
|
end
|
71
|
-
|
71
|
+
|
72
72
|
end
|
73
73
|
|
74
74
|
end
|
data/lib/ice_cube/rule.rb
CHANGED
data/lib/ice_cube/schedule.rb
CHANGED
@@ -7,27 +7,37 @@ module IceCube
|
|
7
7
|
extend ::Deprecated
|
8
8
|
|
9
9
|
# Get the start time
|
10
|
-
|
10
|
+
attr_reader :start_time
|
11
11
|
deprecated_alias :start_date, :start_time
|
12
|
-
deprecated_alias :start_date=, :start_time=
|
13
12
|
|
14
13
|
# Get the duration
|
15
14
|
attr_accessor :duration
|
16
15
|
|
17
16
|
# Get the end time
|
18
|
-
|
17
|
+
attr_reader :end_time
|
19
18
|
deprecated_alias :end_date, :end_time
|
20
|
-
deprecated_alias :end_date=, :end_time=
|
21
19
|
|
22
20
|
# Create a new schedule
|
23
21
|
def initialize(start_time = nil, options = {})
|
24
|
-
|
25
|
-
|
22
|
+
self.start_time = start_time || TimeUtil.now
|
23
|
+
self.end_time = options[:end_time]
|
26
24
|
@duration = options[:duration]
|
27
25
|
@all_recurrence_rules = []
|
28
26
|
@all_exception_rules = []
|
29
27
|
end
|
30
28
|
|
29
|
+
# Set start_time
|
30
|
+
def start_time=(start_time)
|
31
|
+
@start_time = TimeUtil.ensure_time start_time
|
32
|
+
end
|
33
|
+
deprecated_alias :start_date=, :start_time=
|
34
|
+
|
35
|
+
# Set end_time
|
36
|
+
def end_time=(end_time)
|
37
|
+
@end_time = TimeUtil.ensure_time end_time
|
38
|
+
end
|
39
|
+
deprecated_alias :end_date=, :end_time=
|
40
|
+
|
31
41
|
# Add a recurrence time to the schedule
|
32
42
|
def add_recurrence_time(time)
|
33
43
|
return nil if time.nil?
|
@@ -145,17 +155,20 @@ module IceCube
|
|
145
155
|
end
|
146
156
|
|
147
157
|
# The next n occurrences after now
|
148
|
-
def next_occurrences(num, from =
|
158
|
+
def next_occurrences(num, from = nil)
|
159
|
+
from ||= TimeUtil.now(@start_time.utc?)
|
149
160
|
find_occurrences(from + 1, nil, num)
|
150
161
|
end
|
151
162
|
|
152
163
|
# The next occurrence after now (overridable)
|
153
|
-
def next_occurrence(from =
|
164
|
+
def next_occurrence(from = nil)
|
165
|
+
from ||= TimeUtil.now(@start_time.utc?)
|
154
166
|
find_occurrences(from + 1, nil, 1).first
|
155
167
|
end
|
156
168
|
|
157
169
|
# The remaining occurrences (same requirements as all_occurrences)
|
158
|
-
def remaining_occurrences(from =
|
170
|
+
def remaining_occurrences(from = nil)
|
171
|
+
from ||= TimeUtil.now(@start_time.utc?)
|
159
172
|
find_occurrences(from)
|
160
173
|
end
|
161
174
|
|
@@ -179,6 +192,7 @@ module IceCube
|
|
179
192
|
|
180
193
|
# Return a boolean indicating if an occurrence falls on a certain date
|
181
194
|
def occurs_on?(date)
|
195
|
+
date = TimeUtil.ensure_date date
|
182
196
|
begin_time = TimeUtil.beginning_of_date(date)
|
183
197
|
closing_time = TimeUtil.end_of_date(date)
|
184
198
|
occurs_between?(begin_time, closing_time)
|
@@ -199,6 +213,7 @@ module IceCube
|
|
199
213
|
# @param [Time] closing_time - the last time to consider
|
200
214
|
# @return [Boolean] whether or not the schedules conflict at all
|
201
215
|
def conflicts_with?(other_schedule, closing_time = nil)
|
216
|
+
closing_time = TimeUtil.ensure_time closing_time
|
202
217
|
unless terminating? || other_schedule.terminating? || closing_time
|
203
218
|
raise ArgumentError.new 'At least one schedule must be terminating to use #conflicts_with?'
|
204
219
|
end
|
@@ -302,7 +317,7 @@ module IceCube
|
|
302
317
|
schedule = IceCube::Schedule.new TimeUtil.deserialize_time(data[:start_date])
|
303
318
|
schedule.duration = data[:duration] if data[:duration]
|
304
319
|
schedule.end_time = TimeUtil.deserialize_time(data[:end_time]) if data[:end_time]
|
305
|
-
data[:rrules] && data[:rrules].each { |h| schedule.rrule(IceCube::Rule.from_hash(h)) }
|
320
|
+
data[:rrules] && data[:rrules].each { |h| schedule.rrule(IceCube::Rule.from_hash(h)) }
|
306
321
|
data[:exrules] && data[:exrules].each { |h| schedule.exrule(IceCube::Rule.from_hash(h)) }
|
307
322
|
data[:rtimes] && data[:rtimes].each do |t|
|
308
323
|
schedule.add_recurrence_time TimeUtil.deserialize_time(t)
|
@@ -345,6 +360,8 @@ module IceCube
|
|
345
360
|
# Find all of the occurrences for the schedule between opening_time
|
346
361
|
# and closing_time
|
347
362
|
def find_occurrences(opening_time, closing_time = nil, limit = nil, &block)
|
363
|
+
opening_time = TimeUtil.ensure_time opening_time
|
364
|
+
closing_time = TimeUtil.ensure_time closing_time
|
348
365
|
reset
|
349
366
|
answers = []
|
350
367
|
opening_time = start_time if opening_time < start_time
|
data/lib/ice_cube/time_util.rb
CHANGED
@@ -19,8 +19,27 @@ module IceCube
|
|
19
19
|
}
|
20
20
|
|
21
21
|
# Provides a Time.now without the usec
|
22
|
-
def self.now
|
23
|
-
Time.at
|
22
|
+
def self.now(utc = false)
|
23
|
+
time = Time.at(Time.now.to_i)
|
24
|
+
time = time.utc if utc
|
25
|
+
time
|
26
|
+
end
|
27
|
+
|
28
|
+
# Ensure that this is either nil, or a time
|
29
|
+
def self.ensure_time(time, date_eod = false)
|
30
|
+
case time
|
31
|
+
when DateTime then time.to_time
|
32
|
+
when Date then date_eod ? time.to_time.end_of_day : time.to_time
|
33
|
+
else time
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# Ensure that this is either nil, or a date
|
38
|
+
def self.ensure_date(date)
|
39
|
+
case date
|
40
|
+
when Date then date
|
41
|
+
else date.to_time
|
42
|
+
end
|
24
43
|
end
|
25
44
|
|
26
45
|
# Serialize a time appropriate for storing
|
@@ -18,49 +18,21 @@ module IceCube
|
|
18
18
|
|
19
19
|
# Compute the next time after (or including) the specified time in respect
|
20
20
|
# to the given schedule
|
21
|
-
# NOTE: optimization target, sort the rules by their type, year first
|
22
|
-
# so we can make bigger jumps more often
|
23
21
|
def next_time(time, schedule, closing_time)
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
validation.validate(time, schedule)
|
29
|
-
end
|
30
|
-
# If there is any nil, then we're set - otherwise choose the lowest
|
31
|
-
if res.any? { |r| r.nil? || r == 0 }
|
32
|
-
true
|
33
|
-
else
|
34
|
-
return nil if res.all? { |r| r === true } # allow quick escaping
|
35
|
-
res.reject! { |r| r.nil? || r == 0 || r === true }
|
36
|
-
if fwd = res.min
|
37
|
-
type = vals.first.type # get the jump type
|
38
|
-
dst_adjust = !vals.first.respond_to?(:dst_adjust?) || vals.first.dst_adjust?
|
39
|
-
wrapper = TimeUtil::TimeWrapper.new(time, dst_adjust)
|
40
|
-
wrapper.add(type, fwd)
|
41
|
-
wrapper.clear_below(type)
|
42
|
-
# Move over DST if blocked, no adjustments
|
43
|
-
if wrapper.to_time <= time
|
44
|
-
wrapper = TimeUtil::TimeWrapper.new(wrapper.to_time, false)
|
45
|
-
until wrapper.to_time > time
|
46
|
-
wrapper.add(:min, 10) # smallest interval
|
47
|
-
end
|
48
|
-
end
|
49
|
-
# And then get the correct time out
|
50
|
-
time = wrapper.to_time
|
51
|
-
end
|
52
|
-
false
|
53
|
-
end
|
54
|
-
end
|
22
|
+
@time = time
|
23
|
+
@schedule = schedule
|
24
|
+
|
25
|
+
until finds_acceptable_time?
|
55
26
|
# Prevent a non-matching infinite loop
|
56
|
-
return nil if closing_time && time > closing_time
|
27
|
+
return nil if closing_time && @time > closing_time
|
57
28
|
end
|
29
|
+
|
58
30
|
# NOTE Uses may be 1 higher than proper here since end_time isn't
|
59
31
|
# validated in this class. This is okay now, since we never expose it -
|
60
32
|
# but if we ever do - we should check that above this line, and return
|
61
33
|
# nil if end_time is past
|
62
|
-
@uses += 1 if time
|
63
|
-
time
|
34
|
+
@uses += 1 if @time
|
35
|
+
@time
|
64
36
|
end
|
65
37
|
|
66
38
|
def to_s
|
@@ -115,6 +87,55 @@ module IceCube
|
|
115
87
|
end
|
116
88
|
end
|
117
89
|
|
90
|
+
private
|
91
|
+
|
92
|
+
# NOTE: optimization target, sort the rules by their type, year first
|
93
|
+
# so we can make bigger jumps more often
|
94
|
+
def finds_acceptable_time?
|
95
|
+
@validations.all? do |name, validations_for_type|
|
96
|
+
validation_accepts_or_updates_time?(validations_for_type)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def validation_accepts_or_updates_time?(validations_for_type)
|
101
|
+
res = validated_results(validations_for_type)
|
102
|
+
# If there is any nil, then we're set - otherwise choose the lowest
|
103
|
+
if res.any? { |r| r.nil? || r == 0 }
|
104
|
+
true
|
105
|
+
else
|
106
|
+
return nil if res.all? { |r| r === true } # allow quick escaping
|
107
|
+
res.reject! { |r| r.nil? || r == 0 || r === true }
|
108
|
+
shift_time_by_validation(res, validations_for_type)
|
109
|
+
false
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
def validated_results(validations_for_type)
|
114
|
+
validations_for_type.map do |validation|
|
115
|
+
validation.validate(@time, @schedule)
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
def shift_time_by_validation(res, vals)
|
120
|
+
return unless res.min
|
121
|
+
type = vals.first.type # get the jump type
|
122
|
+
dst_adjust = !vals.first.respond_to?(:dst_adjust?) || vals.first.dst_adjust?
|
123
|
+
wrapper = TimeUtil::TimeWrapper.new(@time, dst_adjust)
|
124
|
+
wrapper.add(type, res.min)
|
125
|
+
wrapper.clear_below(type)
|
126
|
+
|
127
|
+
# Move over DST if blocked, no adjustments
|
128
|
+
if wrapper.to_time <= @time
|
129
|
+
wrapper = TimeUtil::TimeWrapper.new(wrapper.to_time, false)
|
130
|
+
until wrapper.to_time > @time
|
131
|
+
wrapper.add(:min, 10) # smallest interval
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
# And then get the correct time out
|
136
|
+
@time = wrapper.to_time
|
137
|
+
end
|
138
|
+
|
118
139
|
end
|
119
140
|
|
120
141
|
end
|
@@ -42,7 +42,7 @@ module IceCube
|
|
42
42
|
|
43
43
|
def validate(time, schedule)
|
44
44
|
start_time = schedule.start_time
|
45
|
-
sec = (time.to_i - time.to_i % ONE_HOUR) -
|
45
|
+
sec = (time.to_i - time.to_i % ONE_HOUR) -
|
46
46
|
(start_time.to_i - start_time.to_i % ONE_HOUR)
|
47
47
|
hours = sec / ONE_HOUR
|
48
48
|
unless hours % interval == 0
|
@@ -23,11 +23,11 @@ module IceCube
|
|
23
23
|
# If this number is positive, then follow our normal procedure
|
24
24
|
if start > 0
|
25
25
|
return start >= time.day ? start - time.day : days_in_this_month - time.day + start
|
26
|
-
end
|
26
|
+
end
|
27
27
|
# If the number is negative, and it resolved against the current month
|
28
28
|
# puts it in the future, just return the difference
|
29
29
|
days_in_this_month = TimeUtil.days_in_month(time)
|
30
|
-
start_one = days_in_this_month + start + 1
|
30
|
+
start_one = days_in_this_month + start + 1
|
31
31
|
if start_one >= time.day
|
32
32
|
return start_one - time.day
|
33
33
|
end
|
@@ -45,7 +45,7 @@ module IceCube
|
|
45
45
|
st = schedule.start_time
|
46
46
|
start_date = Date.new(st.year, st.month, st.day)
|
47
47
|
weeks = (
|
48
|
-
(date - TimeUtil.normalize_weekday(date.wday, week_start)) -
|
48
|
+
(date - TimeUtil.normalize_weekday(date.wday, week_start)) -
|
49
49
|
(start_date - TimeUtil.normalize_weekday(start_date.wday, week_start))
|
50
50
|
) / 7
|
51
51
|
unless weeks % interval == 0
|
data/lib/ice_cube/version.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ice_cube
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.9.
|
4
|
+
version: 0.9.2
|
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-
|
12
|
+
date: 2012-12-08 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rspec
|