ice_cube_chosko 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|