ice_cube_chosko 0.1.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.
- checksums.yaml +7 -0
- data/config/locales/en.yml +178 -0
- data/config/locales/es.yml +176 -0
- data/config/locales/ja.yml +107 -0
- data/lib/ice_cube.rb +92 -0
- data/lib/ice_cube/builders/hash_builder.rb +27 -0
- data/lib/ice_cube/builders/ical_builder.rb +59 -0
- data/lib/ice_cube/builders/string_builder.rb +76 -0
- data/lib/ice_cube/deprecated.rb +36 -0
- data/lib/ice_cube/errors/count_exceeded.rb +7 -0
- data/lib/ice_cube/errors/until_exceeded.rb +7 -0
- data/lib/ice_cube/flexible_hash.rb +40 -0
- data/lib/ice_cube/i18n.rb +24 -0
- data/lib/ice_cube/null_i18n.rb +28 -0
- data/lib/ice_cube/occurrence.rb +101 -0
- data/lib/ice_cube/parsers/hash_parser.rb +91 -0
- data/lib/ice_cube/parsers/ical_parser.rb +91 -0
- data/lib/ice_cube/parsers/yaml_parser.rb +19 -0
- data/lib/ice_cube/rule.rb +123 -0
- data/lib/ice_cube/rules/daily_rule.rb +16 -0
- data/lib/ice_cube/rules/hourly_rule.rb +16 -0
- data/lib/ice_cube/rules/minutely_rule.rb +16 -0
- data/lib/ice_cube/rules/monthly_rule.rb +16 -0
- data/lib/ice_cube/rules/secondly_rule.rb +15 -0
- data/lib/ice_cube/rules/weekly_rule.rb +16 -0
- data/lib/ice_cube/rules/yearly_rule.rb +16 -0
- data/lib/ice_cube/schedule.rb +529 -0
- data/lib/ice_cube/single_occurrence_rule.rb +28 -0
- data/lib/ice_cube/time_util.rb +328 -0
- data/lib/ice_cube/validated_rule.rb +184 -0
- data/lib/ice_cube/validations/count.rb +61 -0
- data/lib/ice_cube/validations/daily_interval.rb +54 -0
- data/lib/ice_cube/validations/day.rb +71 -0
- data/lib/ice_cube/validations/day_of_month.rb +55 -0
- data/lib/ice_cube/validations/day_of_week.rb +77 -0
- data/lib/ice_cube/validations/day_of_year.rb +61 -0
- data/lib/ice_cube/validations/fixed_value.rb +95 -0
- data/lib/ice_cube/validations/hour_of_day.rb +55 -0
- data/lib/ice_cube/validations/hourly_interval.rb +54 -0
- data/lib/ice_cube/validations/lock.rb +95 -0
- data/lib/ice_cube/validations/minute_of_hour.rb +54 -0
- data/lib/ice_cube/validations/minutely_interval.rb +54 -0
- data/lib/ice_cube/validations/month_of_year.rb +54 -0
- data/lib/ice_cube/validations/monthly_interval.rb +53 -0
- data/lib/ice_cube/validations/schedule_lock.rb +46 -0
- data/lib/ice_cube/validations/second_of_minute.rb +54 -0
- data/lib/ice_cube/validations/secondly_interval.rb +51 -0
- data/lib/ice_cube/validations/until.rb +57 -0
- data/lib/ice_cube/validations/weekly_interval.rb +67 -0
- data/lib/ice_cube/validations/yearly_interval.rb +53 -0
- data/lib/ice_cube/version.rb +5 -0
- data/spec/spec_helper.rb +64 -0
- metadata +166 -0
@@ -0,0 +1,28 @@
|
|
1
|
+
module IceCube
|
2
|
+
|
3
|
+
class SingleOccurrenceRule < Rule
|
4
|
+
|
5
|
+
attr_reader :time
|
6
|
+
|
7
|
+
def initialize(time)
|
8
|
+
@time = TimeUtil.ensure_time time
|
9
|
+
end
|
10
|
+
|
11
|
+
# Always terminating
|
12
|
+
def terminating?
|
13
|
+
true
|
14
|
+
end
|
15
|
+
|
16
|
+
def next_time(t, schedule, closing_time)
|
17
|
+
unless closing_time && closing_time < t
|
18
|
+
time if time.to_i >= t.to_i
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def to_hash
|
23
|
+
{ :time => time }
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
@@ -0,0 +1,328 @@
|
|
1
|
+
require 'date'
|
2
|
+
require 'time'
|
3
|
+
|
4
|
+
module IceCube
|
5
|
+
module TimeUtil
|
6
|
+
|
7
|
+
extend Deprecated
|
8
|
+
|
9
|
+
DAYS = {
|
10
|
+
:sunday => 0, :monday => 1, :tuesday => 2, :wednesday => 3,
|
11
|
+
:thursday => 4, :friday => 5, :saturday => 6
|
12
|
+
}
|
13
|
+
|
14
|
+
ICAL_DAYS = {
|
15
|
+
'SU' => :sunday, 'MO' => :monday, 'TU' => :tuesday, 'WE' => :wednesday,
|
16
|
+
'TH' => :thursday, 'FR' => :friday, 'SA' => :saturday
|
17
|
+
}
|
18
|
+
|
19
|
+
MONTHS = {
|
20
|
+
:january => 1, :february => 2, :march => 3, :april => 4, :may => 5,
|
21
|
+
:june => 6, :july => 7, :august => 8, :september => 9, :october => 10,
|
22
|
+
:november => 11, :december => 12
|
23
|
+
}
|
24
|
+
|
25
|
+
CLOCK_VALUES = [:year, :month, :day, :hour, :min, :sec]
|
26
|
+
|
27
|
+
# Provides a Time.now without the usec, in the reference zone or utc offset
|
28
|
+
def self.now(reference=Time.now)
|
29
|
+
match_zone(Time.at(Time.now.to_i), reference)
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.build_in_zone(args, reference)
|
33
|
+
if reference.respond_to?(:time_zone)
|
34
|
+
reference.time_zone.local(*args)
|
35
|
+
elsif reference.utc?
|
36
|
+
Time.utc(*args)
|
37
|
+
elsif reference.zone
|
38
|
+
Time.local(*args)
|
39
|
+
else
|
40
|
+
Time.new(*args << reference.utc_offset)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def self.match_zone(input_time, reference)
|
45
|
+
return unless time = ensure_time(input_time)
|
46
|
+
time = if reference.respond_to? :time_zone
|
47
|
+
time.in_time_zone(reference.time_zone)
|
48
|
+
else
|
49
|
+
if reference.utc?
|
50
|
+
time.utc
|
51
|
+
elsif reference.zone
|
52
|
+
time.getlocal
|
53
|
+
else
|
54
|
+
time.getlocal(reference.utc_offset)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
(Date === input_time) ? beginning_of_date(time, reference) : time
|
58
|
+
end
|
59
|
+
|
60
|
+
# Ensure that this is either nil, or a time
|
61
|
+
def self.ensure_time(time, date_eod = false)
|
62
|
+
case time
|
63
|
+
when DateTime
|
64
|
+
warn "IceCube: DateTime support is deprecated (please use Time) at: #{ caller[2] }"
|
65
|
+
Time.local(time.year, time.month, time.day, time.hour, time.min, time.sec)
|
66
|
+
when Date
|
67
|
+
date_eod ? end_of_date(time) : time.to_time
|
68
|
+
else
|
69
|
+
time
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
# Ensure that this is either nil, or a date
|
74
|
+
def self.ensure_date(date)
|
75
|
+
case date
|
76
|
+
when Date then date
|
77
|
+
else
|
78
|
+
return Date.new(date.year, date.month, date.day)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
# Serialize a time appropriate for storing
|
83
|
+
def self.serialize_time(time)
|
84
|
+
if time.respond_to?(:time_zone)
|
85
|
+
{:time => time.utc, :zone => time.time_zone.name}
|
86
|
+
elsif time.is_a?(Time)
|
87
|
+
time
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
# Deserialize a time serialized with serialize_time or in ISO8601 string format
|
92
|
+
def self.deserialize_time(time_or_hash)
|
93
|
+
case time_or_hash
|
94
|
+
when Time
|
95
|
+
time_or_hash
|
96
|
+
when Hash
|
97
|
+
hash = FlexibleHash.new(time_or_hash)
|
98
|
+
hash[:time].in_time_zone(hash[:zone])
|
99
|
+
when String
|
100
|
+
Time.parse(time_or_hash)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
# Check the deserialized time offset string against actual local time
|
105
|
+
# offset to try and preserve the original offset for plain Ruby Time. If
|
106
|
+
# the offset is the same as local we can assume the same original zone and
|
107
|
+
# keep it. If it was serialized with a different offset than local TZ it
|
108
|
+
# will lose the zone and not support DST.
|
109
|
+
def self.restore_deserialized_offset(time, orig_offset_str)
|
110
|
+
return time if time.respond_to?(:time_zone) ||
|
111
|
+
time.getlocal(orig_offset_str).utc_offset == time.utc_offset
|
112
|
+
warn "IceCube: parsed Time from nonlocal TZ. Use ActiveSupport to fix DST at: #{ caller[0] }"
|
113
|
+
time.localtime(orig_offset_str)
|
114
|
+
end
|
115
|
+
|
116
|
+
# Get the beginning of a date
|
117
|
+
def self.beginning_of_date(date, reference=Time.now)
|
118
|
+
build_in_zone([date.year, date.month, date.day, 0, 0, 0], reference)
|
119
|
+
end
|
120
|
+
|
121
|
+
# Get the end of a date
|
122
|
+
def self.end_of_date(date, reference=Time.now)
|
123
|
+
build_in_zone([date.year, date.month, date.day, 23, 59, 59], reference)
|
124
|
+
end
|
125
|
+
|
126
|
+
# Convert a symbol to a numeric month
|
127
|
+
def self.sym_to_month(sym)
|
128
|
+
MONTHS.fetch(sym) do |k|
|
129
|
+
MONTHS.values.detect { |i| i.to_s == k.to_s } or
|
130
|
+
raise ArgumentError, "Expecting Fixnum or Symbol value for month. " \
|
131
|
+
"No such month: #{k.inspect}"
|
132
|
+
end
|
133
|
+
end
|
134
|
+
deprecated_alias :symbol_to_month, :sym_to_month
|
135
|
+
|
136
|
+
# Convert a symbol to a wday number
|
137
|
+
def self.sym_to_wday(sym)
|
138
|
+
DAYS.fetch(sym) do |k|
|
139
|
+
DAYS.values.detect { |i| i.to_s == k.to_s } or
|
140
|
+
raise ArgumentError, "Expecting Fixnum or Symbol value for weekday. " \
|
141
|
+
"No such weekday: #{k.inspect}"
|
142
|
+
end
|
143
|
+
end
|
144
|
+
deprecated_alias :symbol_to_day, :sym_to_wday
|
145
|
+
|
146
|
+
# Convert wday number to day symbol
|
147
|
+
def self.wday_to_sym(wday)
|
148
|
+
return sym = wday if DAYS.keys.include? wday
|
149
|
+
DAYS.invert.fetch(wday) do |i|
|
150
|
+
raise ArgumentError, "Expecting Fixnum value for weekday. " \
|
151
|
+
"No such wday number: #{i.inspect}"
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
# Convert a symbol to an ical day (SU, MO)
|
156
|
+
def self.week_start(sym)
|
157
|
+
raise ArgumentError, "Invalid day: #{str}" unless DAYS.keys.include?(sym)
|
158
|
+
day = sym.to_s.upcase[0..1]
|
159
|
+
day
|
160
|
+
end
|
161
|
+
|
162
|
+
# Convert weekday from base sunday to the schedule's week start.
|
163
|
+
def self.normalize_wday(wday, week_start)
|
164
|
+
(wday - sym_to_wday(week_start)) % 7
|
165
|
+
end
|
166
|
+
deprecated_alias :normalize_weekday, :normalize_wday
|
167
|
+
|
168
|
+
def self.ical_day_to_symbol(str)
|
169
|
+
day = ICAL_DAYS[str]
|
170
|
+
raise ArgumentError, "Invalid day: #{str}" if day.nil?
|
171
|
+
day
|
172
|
+
end
|
173
|
+
|
174
|
+
# Return the count of the number of times wday appears in the month,
|
175
|
+
# and which of those time falls on
|
176
|
+
def self.which_occurrence_in_month(time, wday)
|
177
|
+
first_occurrence = ((7 - Time.utc(time.year, time.month, 1).wday) + time.wday) % 7 + 1
|
178
|
+
this_weekday_in_month_count = ((days_in_month(time) - first_occurrence + 1) / 7.0).ceil
|
179
|
+
nth_occurrence_of_weekday = (time.mday - first_occurrence) / 7 + 1
|
180
|
+
[nth_occurrence_of_weekday, this_weekday_in_month_count]
|
181
|
+
end
|
182
|
+
|
183
|
+
# Get the days in the month for +time
|
184
|
+
def self.days_in_month(time)
|
185
|
+
date = Date.new(time.year, time.month, 1)
|
186
|
+
((date >> 1) - date).to_i
|
187
|
+
end
|
188
|
+
|
189
|
+
# Get the days in the following month for +time
|
190
|
+
def self.days_in_next_month(time)
|
191
|
+
date = Date.new(time.year, time.month, 1) >> 1
|
192
|
+
((date >> 1) - date).to_i
|
193
|
+
end
|
194
|
+
|
195
|
+
# Count the number of days to the same day of the next month without
|
196
|
+
# overflowing shorter months
|
197
|
+
def self.days_to_next_month(time)
|
198
|
+
date = Date.new(time.year, time.month, time.day)
|
199
|
+
((date >> 1) - date).to_i
|
200
|
+
end
|
201
|
+
|
202
|
+
# Get a day of the month in the month of a given time without overflowing
|
203
|
+
# into the next month. Accepts days from positive (start of month forward) or
|
204
|
+
# negative (from end of month)
|
205
|
+
def self.day_of_month(value, date)
|
206
|
+
if value.to_i > 0
|
207
|
+
[value, days_in_month(date)].min
|
208
|
+
else
|
209
|
+
[1 + days_in_month(date) + value, 1].max
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
# Number of days in a year
|
214
|
+
def self.days_in_year(time)
|
215
|
+
date = Date.new(time.year, 1, 1)
|
216
|
+
((date >> 12) - date).to_i
|
217
|
+
end
|
218
|
+
|
219
|
+
# Number of days to n years
|
220
|
+
def self.days_in_n_years(time, year_distance)
|
221
|
+
date = Date.new(time.year, time.month, time.day)
|
222
|
+
((date >> year_distance * 12) - date).to_i
|
223
|
+
end
|
224
|
+
|
225
|
+
# The number of days in n months
|
226
|
+
def self.days_in_n_months(time, month_distance)
|
227
|
+
date = Date.new(time.year, time.month, time.day)
|
228
|
+
((date >> month_distance) - date).to_i
|
229
|
+
end
|
230
|
+
|
231
|
+
def self.dst_change(time)
|
232
|
+
one_hour_ago = time - ONE_HOUR
|
233
|
+
if time.dst? ^ one_hour_ago.dst?
|
234
|
+
(time.utc_offset - one_hour_ago.utc_offset) / ONE_HOUR
|
235
|
+
end
|
236
|
+
end
|
237
|
+
|
238
|
+
def self.same_clock?(t1, t2)
|
239
|
+
CLOCK_VALUES.all? { |i| t1.send(i) == t2.send(i) }
|
240
|
+
end
|
241
|
+
|
242
|
+
# A utility class for safely moving time around
|
243
|
+
class TimeWrapper
|
244
|
+
|
245
|
+
def initialize(time, dst_adjust = true)
|
246
|
+
@dst_adjust = dst_adjust
|
247
|
+
@time = time
|
248
|
+
end
|
249
|
+
|
250
|
+
# Get the wrapper time back
|
251
|
+
def to_time
|
252
|
+
@time
|
253
|
+
end
|
254
|
+
|
255
|
+
# DST-safely add an interval of time to the wrapped time
|
256
|
+
def add(type, val)
|
257
|
+
type = :day if type == :wday
|
258
|
+
adjust do
|
259
|
+
@time += case type
|
260
|
+
when :year then TimeUtil.days_in_n_years(@time, val) * ONE_DAY
|
261
|
+
when :month then TimeUtil.days_in_n_months(@time, val) * ONE_DAY
|
262
|
+
when :day then val * ONE_DAY
|
263
|
+
when :hour then val * ONE_HOUR
|
264
|
+
when :min then val * ONE_MINUTE
|
265
|
+
when :sec then val
|
266
|
+
end
|
267
|
+
end
|
268
|
+
end
|
269
|
+
|
270
|
+
# Clear everything below a certain type
|
271
|
+
CLEAR_ORDER = [:sec, :min, :hour, :day, :month, :year]
|
272
|
+
def clear_below(type)
|
273
|
+
type = :day if type == :wday
|
274
|
+
CLEAR_ORDER.each do |ptype|
|
275
|
+
break if ptype == type
|
276
|
+
adjust do
|
277
|
+
send(:"clear_#{ptype}")
|
278
|
+
end
|
279
|
+
end
|
280
|
+
end
|
281
|
+
|
282
|
+
private
|
283
|
+
|
284
|
+
def adjust(&block)
|
285
|
+
if @dst_adjust
|
286
|
+
off = @time.utc_offset
|
287
|
+
yield
|
288
|
+
diff = off - @time.utc_offset
|
289
|
+
@time += diff if diff != 0
|
290
|
+
else
|
291
|
+
yield
|
292
|
+
end
|
293
|
+
end
|
294
|
+
|
295
|
+
def clear_sec
|
296
|
+
@time.sec > 0 ? @time -= @time.sec : @time
|
297
|
+
end
|
298
|
+
|
299
|
+
def clear_min
|
300
|
+
@time.min > 0 ? @time -= (@time.min * ONE_MINUTE) : @time
|
301
|
+
end
|
302
|
+
|
303
|
+
def clear_hour
|
304
|
+
@time.hour > 0 ? @time -= (@time.hour * ONE_HOUR) : @time
|
305
|
+
end
|
306
|
+
|
307
|
+
# Move to the first of the month, 0 hours
|
308
|
+
def clear_day
|
309
|
+
@time.day > 1 ? @time -= (@time.day - 1) * ONE_DAY : @time
|
310
|
+
end
|
311
|
+
|
312
|
+
# Clear to january 1st
|
313
|
+
def clear_month
|
314
|
+
@time -= ONE_DAY
|
315
|
+
until @time.month == 12
|
316
|
+
@time -= TimeUtil.days_in_month(@time) * ONE_DAY
|
317
|
+
end
|
318
|
+
@time += ONE_DAY
|
319
|
+
end
|
320
|
+
|
321
|
+
def clear_year
|
322
|
+
@time
|
323
|
+
end
|
324
|
+
|
325
|
+
end
|
326
|
+
|
327
|
+
end
|
328
|
+
end
|
@@ -0,0 +1,184 @@
|
|
1
|
+
module IceCube
|
2
|
+
|
3
|
+
class ValidatedRule < Rule
|
4
|
+
|
5
|
+
include Validations::ScheduleLock
|
6
|
+
|
7
|
+
include Validations::HourOfDay
|
8
|
+
include Validations::MinuteOfHour
|
9
|
+
include Validations::SecondOfMinute
|
10
|
+
include Validations::DayOfMonth
|
11
|
+
include Validations::DayOfWeek
|
12
|
+
include Validations::Day
|
13
|
+
include Validations::MonthOfYear
|
14
|
+
include Validations::DayOfYear
|
15
|
+
|
16
|
+
include Validations::Count
|
17
|
+
include Validations::Until
|
18
|
+
|
19
|
+
# Validations ordered for efficiency in sequence of:
|
20
|
+
# * descending intervals
|
21
|
+
# * boundary limits
|
22
|
+
# * base values by cardinality (n = 60, 60, 31, 24, 12, 7)
|
23
|
+
# * locks by cardinality (n = 365, 60, 60, 31, 24, 12, 7)
|
24
|
+
# * interval multiplier
|
25
|
+
VALIDATION_ORDER = [
|
26
|
+
:year, :month, :day, :wday, :hour, :min, :sec, :count, :until,
|
27
|
+
:base_sec, :base_min, :base_day, :base_hour, :base_month, :base_wday,
|
28
|
+
:day_of_year, :second_of_minute, :minute_of_hour, :day_of_month,
|
29
|
+
:hour_of_day, :month_of_year, :day_of_week,
|
30
|
+
:interval
|
31
|
+
]
|
32
|
+
|
33
|
+
attr_reader :validations
|
34
|
+
|
35
|
+
def initialize(interval = 1, *)
|
36
|
+
@validations = Hash.new
|
37
|
+
end
|
38
|
+
|
39
|
+
def base_interval_validation
|
40
|
+
@validations[:interval].first
|
41
|
+
end
|
42
|
+
|
43
|
+
def other_interval_validations
|
44
|
+
Array(@validations[base_interval_validation.type])
|
45
|
+
end
|
46
|
+
|
47
|
+
def base_interval_type
|
48
|
+
base_interval_validation.type
|
49
|
+
end
|
50
|
+
|
51
|
+
# Compute the next time after (or including) the specified time in respect
|
52
|
+
# to the given schedule
|
53
|
+
def next_time(time, schedule, closing_time)
|
54
|
+
@time = time
|
55
|
+
@schedule = schedule
|
56
|
+
|
57
|
+
return nil unless find_acceptable_time_before(closing_time)
|
58
|
+
|
59
|
+
@uses += 1 if @time
|
60
|
+
@time
|
61
|
+
end
|
62
|
+
|
63
|
+
def skipped_for_dst
|
64
|
+
@uses -= 1 if @uses > 0
|
65
|
+
end
|
66
|
+
|
67
|
+
def dst_adjust?
|
68
|
+
@validations[:interval].any? &:dst_adjust?
|
69
|
+
end
|
70
|
+
|
71
|
+
def to_s
|
72
|
+
builder = StringBuilder.new
|
73
|
+
@validations.each do |name, validations|
|
74
|
+
validations.each do |validation|
|
75
|
+
validation.build_s(builder)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
builder.to_s
|
79
|
+
end
|
80
|
+
|
81
|
+
def to_hash
|
82
|
+
builder = HashBuilder.new(self)
|
83
|
+
@validations.each do |name, validations|
|
84
|
+
validations.each do |validation|
|
85
|
+
validation.build_hash(builder)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
builder.to_hash
|
89
|
+
end
|
90
|
+
|
91
|
+
def to_ical
|
92
|
+
builder = IcalBuilder.new
|
93
|
+
@validations.each do |name, validations|
|
94
|
+
validations.each do |validation|
|
95
|
+
validation.build_ical(builder)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
builder.to_s
|
99
|
+
end
|
100
|
+
|
101
|
+
# Get the collection that contains validations of a certain type
|
102
|
+
def validations_for(key)
|
103
|
+
@validations[key] ||= []
|
104
|
+
end
|
105
|
+
|
106
|
+
# Fully replace validations
|
107
|
+
def replace_validations_for(key, arr)
|
108
|
+
if arr.nil?
|
109
|
+
@validations.delete(key)
|
110
|
+
else
|
111
|
+
@validations[key] = arr
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
# Remove the specified base validations
|
116
|
+
def clobber_base_validations(*types)
|
117
|
+
types.each do |type|
|
118
|
+
@validations.delete(:"base_#{type}")
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
private
|
123
|
+
|
124
|
+
def normalized_interval(interval)
|
125
|
+
int = interval.to_i
|
126
|
+
raise ArgumentError, "'#{interval}' is not a valid input for interval. Please pass an integer." unless int > 0
|
127
|
+
int
|
128
|
+
end
|
129
|
+
|
130
|
+
def finds_acceptable_time?
|
131
|
+
validation_names.all? do |type|
|
132
|
+
validation_accepts_or_updates_time?(@validations[type])
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
def find_acceptable_time_before(boundary)
|
137
|
+
until finds_acceptable_time?
|
138
|
+
return false if past_closing_time?(boundary)
|
139
|
+
end
|
140
|
+
true
|
141
|
+
end
|
142
|
+
|
143
|
+
# Returns true if all validations for the current rule match
|
144
|
+
# otherwise false and shifts to the first (largest) unmatched offset
|
145
|
+
#
|
146
|
+
def validation_accepts_or_updates_time?(validations_for_type)
|
147
|
+
res = validations_for_type.each_with_object([]) do |validation, offsets|
|
148
|
+
r = validation.validate(@time, @schedule)
|
149
|
+
return true if r.nil? || r == 0
|
150
|
+
offsets << r
|
151
|
+
end
|
152
|
+
shift_time_by_validation(res, validations_for_type.first)
|
153
|
+
false
|
154
|
+
end
|
155
|
+
|
156
|
+
def shift_time_by_validation(res, validation)
|
157
|
+
return unless (interval = res.min)
|
158
|
+
wrapper = TimeUtil::TimeWrapper.new(@time, validation.dst_adjust?)
|
159
|
+
wrapper.add(validation.type, interval)
|
160
|
+
wrapper.clear_below(validation.type)
|
161
|
+
|
162
|
+
# Move over DST if blocked, no adjustments
|
163
|
+
if wrapper.to_time <= @time
|
164
|
+
wrapper = TimeUtil::TimeWrapper.new(wrapper.to_time, false)
|
165
|
+
until wrapper.to_time > @time
|
166
|
+
wrapper.add(:min, 10) # smallest interval
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
# And then get the correct time out
|
171
|
+
@time = wrapper.to_time
|
172
|
+
end
|
173
|
+
|
174
|
+
def past_closing_time?(closing_time)
|
175
|
+
closing_time && @time > closing_time
|
176
|
+
end
|
177
|
+
|
178
|
+
def validation_names
|
179
|
+
VALIDATION_ORDER & @validations.keys
|
180
|
+
end
|
181
|
+
|
182
|
+
end
|
183
|
+
|
184
|
+
end
|