ice_cube 0.9.1 → 0.9.2
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/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
|