ice_cube 0.16.2 → 0.16.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/ice_cube.rb +1 -3
- data/lib/ice_cube/i18n.rb +10 -7
- data/lib/ice_cube/input_alignment.rb +89 -0
- data/lib/ice_cube/null_i18n.rb +12 -6
- data/lib/ice_cube/occurrence.rb +25 -23
- data/lib/ice_cube/parsers/ical_parser.rb +7 -4
- data/lib/ice_cube/rule.rb +4 -11
- data/lib/ice_cube/rules/daily_rule.rb +9 -0
- data/lib/ice_cube/rules/hourly_rule.rb +9 -0
- data/lib/ice_cube/rules/minutely_rule.rb +9 -0
- data/lib/ice_cube/rules/monthly_rule.rb +9 -0
- data/lib/ice_cube/rules/secondly_rule.rb +9 -0
- data/lib/ice_cube/rules/weekly_rule.rb +10 -1
- data/lib/ice_cube/rules/yearly_rule.rb +9 -0
- data/lib/ice_cube/schedule.rb +10 -9
- data/lib/ice_cube/single_occurrence_rule.rb +4 -0
- data/lib/ice_cube/time_util.rb +26 -16
- data/lib/ice_cube/validated_rule.rb +10 -19
- data/lib/ice_cube/validations/count.rb +1 -2
- data/lib/ice_cube/validations/daily_interval.rb +5 -1
- data/lib/ice_cube/validations/day.rb +6 -2
- data/lib/ice_cube/validations/day_of_month.rb +5 -0
- data/lib/ice_cube/validations/hour_of_day.rb +23 -0
- data/lib/ice_cube/validations/hourly_interval.rb +2 -0
- data/lib/ice_cube/validations/minute_of_hour.rb +16 -0
- data/lib/ice_cube/validations/minutely_interval.rb +2 -0
- data/lib/ice_cube/validations/month_of_year.rb +5 -0
- data/lib/ice_cube/validations/monthly_interval.rb +4 -1
- data/lib/ice_cube/validations/schedule_lock.rb +4 -0
- data/lib/ice_cube/validations/second_of_minute.rb +19 -3
- data/lib/ice_cube/validations/secondly_interval.rb +2 -0
- data/lib/ice_cube/validations/until.rb +1 -2
- data/lib/ice_cube/validations/weekly_interval.rb +0 -2
- data/lib/ice_cube/version.rb +1 -1
- data/spec/spec_helper.rb +32 -9
- metadata +5 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: afc29d6bebdb8a25e7926863560b6b73214887d7
|
4
|
+
data.tar.gz: 850cad27d4f06dd30052222e4c30f6fa8b3b50e5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 351c83281b34657ff2a64c8fc03e023265803ba8776d6f6a98c90fbbf04f14c0e0c9a7d7ee40f5a8c739cea31283b60c429b37521f7fe4e7ea61bc2175123c46
|
7
|
+
data.tar.gz: d02fba63d82b7a26077cadf400a0bd45172d6d047c26533164f786654b925e0b6e28902cefec8ada0a2df10f08596237c848271ffbb85094323ec515a71f2431
|
data/lib/ice_cube.rb
CHANGED
@@ -1,8 +1,5 @@
|
|
1
1
|
require 'date'
|
2
2
|
require 'ice_cube/deprecated'
|
3
|
-
require 'ice_cube/i18n'
|
4
|
-
|
5
|
-
IceCube::I18n.detect_backend!
|
6
3
|
|
7
4
|
module IceCube
|
8
5
|
|
@@ -10,6 +7,7 @@ module IceCube
|
|
10
7
|
|
11
8
|
autoload :TimeUtil, 'ice_cube/time_util'
|
12
9
|
autoload :FlexibleHash, 'ice_cube/flexible_hash'
|
10
|
+
autoload :I18n, 'ice_cube/i18n'
|
13
11
|
|
14
12
|
autoload :Rule, 'ice_cube/rule'
|
15
13
|
autoload :Schedule, 'ice_cube/schedule'
|
data/lib/ice_cube/i18n.rb
CHANGED
@@ -1,5 +1,10 @@
|
|
1
|
+
require 'ice_cube/null_i18n'
|
2
|
+
|
1
3
|
module IceCube
|
2
4
|
module I18n
|
5
|
+
|
6
|
+
LOCALES_PATH = File.expand_path(File.join('..', '..', '..', 'config', 'locales'), __FILE__)
|
7
|
+
|
3
8
|
def self.t(*args)
|
4
9
|
backend.t(*args)
|
5
10
|
end
|
@@ -9,16 +14,14 @@ module IceCube
|
|
9
14
|
end
|
10
15
|
|
11
16
|
def self.backend
|
12
|
-
@backend
|
17
|
+
@backend ||= detect_backend!
|
13
18
|
end
|
14
19
|
|
15
20
|
def self.detect_backend!
|
16
|
-
|
17
|
-
::I18n
|
18
|
-
|
19
|
-
|
20
|
-
require 'ice_cube/null_i18n'
|
21
|
-
@backend = NullI18n
|
21
|
+
::I18n.load_path += Dir[File.join(LOCALES_PATH, '*.yml')]
|
22
|
+
::I18n
|
23
|
+
rescue NameError
|
24
|
+
NullI18n
|
22
25
|
end
|
23
26
|
end
|
24
27
|
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
module IceCube
|
2
|
+
class InputAlignment
|
3
|
+
|
4
|
+
def initialize(rule, value, rule_part)
|
5
|
+
@rule = rule
|
6
|
+
@value = value
|
7
|
+
@rule_part = rule_part
|
8
|
+
end
|
9
|
+
|
10
|
+
attr_reader :rule, :value, :rule_part
|
11
|
+
|
12
|
+
def verify(freq, options={}, &block)
|
13
|
+
@rule.validations[:interval] or return
|
14
|
+
|
15
|
+
case @rule
|
16
|
+
when DailyRule
|
17
|
+
verify_wday_alignment(freq, &block)
|
18
|
+
when MonthlyRule
|
19
|
+
verify_month_alignment(freq, &block)
|
20
|
+
else
|
21
|
+
verify_freq_alignment(freq, &block)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def interval_validation
|
28
|
+
@interval_validation ||= @rule.validations[:interval].first
|
29
|
+
end
|
30
|
+
|
31
|
+
def interval_value
|
32
|
+
@interval_value ||= (rule_part == :interval) ? value : interval_validation.interval
|
33
|
+
end
|
34
|
+
|
35
|
+
def fixed_validations
|
36
|
+
@fixed_validations ||= @rule.validations.values.flatten.select { |v|
|
37
|
+
interval_type = (v.type == :wday ? :day : v.type)
|
38
|
+
v.class < Validations::FixedValue &&
|
39
|
+
interval_type == rule.base_interval_validation.type
|
40
|
+
}
|
41
|
+
end
|
42
|
+
|
43
|
+
def verify_freq_alignment(freq)
|
44
|
+
interval_validation.type == freq or return
|
45
|
+
(last_validation = fixed_validations.min_by(&:value)) or return
|
46
|
+
|
47
|
+
alignment = (value - last_validation.value) % interval_validation.interval
|
48
|
+
return if alignment.zero?
|
49
|
+
|
50
|
+
validation_values = fixed_validations.map(&:value).join(', ')
|
51
|
+
if rule_part == :interval
|
52
|
+
message = "interval(#{value}) " \
|
53
|
+
"must be a multiple of " \
|
54
|
+
"intervals in #{last_validation.key}(#{validation_values})"
|
55
|
+
else
|
56
|
+
message = "intervals in #{last_validation.key}(#{validation_values}, #{value}) " \
|
57
|
+
"must be multiples of " \
|
58
|
+
"interval(#{interval_validation.interval})"
|
59
|
+
end
|
60
|
+
|
61
|
+
yield ArgumentError.new(message)
|
62
|
+
end
|
63
|
+
|
64
|
+
def verify_month_alignment(_freq)
|
65
|
+
return if interval_value == 1 || (interval_value % 12).zero?
|
66
|
+
return if fixed_validations.empty?
|
67
|
+
|
68
|
+
message = "month_of_year can only be used with interval(1) or multiples of interval(12)"
|
69
|
+
|
70
|
+
yield ArgumentError.new(message)
|
71
|
+
end
|
72
|
+
|
73
|
+
def verify_wday_alignment(freq)
|
74
|
+
return if interval_value == 1
|
75
|
+
|
76
|
+
if freq == :wday
|
77
|
+
return if (interval_value % 7).zero?
|
78
|
+
return if Array(@rule.validations[:day]).empty?
|
79
|
+
message = "day can only be used with multiples of interval(7)"
|
80
|
+
else
|
81
|
+
(fixed_validation = fixed_validations.first) or return
|
82
|
+
message = "#{fixed_validation.key} can only be used with interval(1)"
|
83
|
+
end
|
84
|
+
|
85
|
+
yield ArgumentError.new(message)
|
86
|
+
end
|
87
|
+
|
88
|
+
end
|
89
|
+
end
|
data/lib/ice_cube/null_i18n.rb
CHANGED
@@ -7,13 +7,19 @@ module IceCube
|
|
7
7
|
|
8
8
|
base = base[options[:count] == 1 ? "one" : "other"] if options[:count]
|
9
9
|
|
10
|
-
|
11
|
-
|
12
|
-
|
10
|
+
case base
|
11
|
+
when Hash
|
12
|
+
base.each_with_object({}) do |(k, v), hash|
|
13
|
+
hash[k.is_a?(String) ? k.to_sym : k] = v
|
13
14
|
end
|
15
|
+
when Array
|
16
|
+
base.each_with_index.each_with_object({}) do |(v, k), hash|
|
17
|
+
hash[k] = v
|
18
|
+
end
|
19
|
+
else
|
20
|
+
return base unless base.include?('%{')
|
21
|
+
base % options
|
14
22
|
end
|
15
|
-
|
16
|
-
options.reduce(base) { |result, (find, replace)| result.gsub("%{#{find}}", "#{replace}") }
|
17
23
|
end
|
18
24
|
|
19
25
|
def self.l(date_or_time, options = {})
|
@@ -22,7 +28,7 @@ module IceCube
|
|
22
28
|
end
|
23
29
|
|
24
30
|
def self.config
|
25
|
-
@config ||= YAML.
|
31
|
+
@config ||= YAML.load_file(File.join(IceCube::I18n::LOCALES_PATH, 'en.yml'))['en']
|
26
32
|
end
|
27
33
|
end
|
28
34
|
end
|
data/lib/ice_cube/occurrence.rb
CHANGED
@@ -1,4 +1,3 @@
|
|
1
|
-
require 'forwardable'
|
2
1
|
require 'delegate'
|
3
2
|
|
4
3
|
module IceCube
|
@@ -20,18 +19,16 @@ module IceCube
|
|
20
19
|
# Time.now - Occurrence.new(start_time) # => 3600
|
21
20
|
#
|
22
21
|
class Occurrence < SimpleDelegator
|
22
|
+
include Comparable
|
23
23
|
|
24
24
|
# Report class name as 'Time' to thwart type checking.
|
25
25
|
def self.name
|
26
26
|
'Time'
|
27
27
|
end
|
28
28
|
|
29
|
-
# Optimize for common methods to avoid method_missing
|
30
|
-
extend Forwardable
|
31
|
-
def_delegators :start_time, :to_i, :<=>, :==
|
32
|
-
def_delegators :to_range, :cover?, :include?, :each, :first, :last
|
33
|
-
|
34
29
|
attr_reader :start_time, :end_time
|
30
|
+
alias first start_time
|
31
|
+
alias last end_time
|
35
32
|
|
36
33
|
def initialize(start_time, end_time=nil)
|
37
34
|
@start_time = start_time
|
@@ -39,29 +36,34 @@ module IceCube
|
|
39
36
|
__setobj__ @start_time
|
40
37
|
end
|
41
38
|
|
39
|
+
def to_i
|
40
|
+
@start_time.to_i
|
41
|
+
end
|
42
|
+
|
43
|
+
def <=>(other)
|
44
|
+
@start_time <=> other
|
45
|
+
end
|
46
|
+
|
42
47
|
def is_a?(klass)
|
43
48
|
klass == ::Time || super
|
44
49
|
end
|
45
50
|
alias_method :kind_of?, :is_a?
|
46
51
|
|
47
|
-
def intersects?
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
end
|
61
|
-
else
|
62
|
-
cover? other
|
63
|
-
end
|
52
|
+
def intersects?(other)
|
53
|
+
return cover?(other) unless other.is_a?(Occurrence) || other.is_a?(Range)
|
54
|
+
|
55
|
+
this_start = first + 1
|
56
|
+
this_end = last # exclude end boundary
|
57
|
+
other_start = other.first + 1
|
58
|
+
other_end = other.last + 1
|
59
|
+
|
60
|
+
!(this_end < other_start || this_start > other_end)
|
61
|
+
end
|
62
|
+
|
63
|
+
def cover?(other)
|
64
|
+
to_range.cover?(other)
|
64
65
|
end
|
66
|
+
alias_method :include?, :cover?
|
65
67
|
|
66
68
|
def comparable_time
|
67
69
|
start_time
|
@@ -7,12 +7,15 @@ module IceCube
|
|
7
7
|
(property, tzid) = property.split(';')
|
8
8
|
case property
|
9
9
|
when 'DTSTART'
|
10
|
-
data[:start_time] =
|
10
|
+
data[:start_time] = TimeUtil.deserialize_time(value)
|
11
11
|
when 'DTEND'
|
12
|
-
data[:end_time] =
|
12
|
+
data[:end_time] = TimeUtil.deserialize_time(value)
|
13
|
+
when 'RDATE'
|
14
|
+
data[:rtimes] ||= []
|
15
|
+
data[:rtimes] += value.split(',').map { |v| TimeUtil.deserialize_time(v) }
|
13
16
|
when 'EXDATE'
|
14
17
|
data[:extimes] ||= []
|
15
|
-
data[:extimes] += value.split(',').map{|v|
|
18
|
+
data[:extimes] += value.split(',').map { |v| TimeUtil.deserialize_time(v) }
|
16
19
|
when 'DURATION'
|
17
20
|
data[:duration] # FIXME
|
18
21
|
when 'RRULE'
|
@@ -41,7 +44,7 @@ module IceCube
|
|
41
44
|
when 'COUNT'
|
42
45
|
params[:count] = value.to_i
|
43
46
|
when 'UNTIL'
|
44
|
-
params[:until] =
|
47
|
+
params[:until] = TimeUtil.deserialize_time(value).utc
|
45
48
|
when 'WKST'
|
46
49
|
params[:week_start] = TimeUtil.ical_day_to_symbol(value)
|
47
50
|
when 'BYSECOND'
|
data/lib/ice_cube/rule.rb
CHANGED
@@ -19,11 +19,9 @@ module IceCube
|
|
19
19
|
until_time || occurrence_count
|
20
20
|
end
|
21
21
|
|
22
|
-
def ==(
|
23
|
-
|
24
|
-
|
25
|
-
hash && hash == rule.to_hash
|
26
|
-
end
|
22
|
+
def ==(other)
|
23
|
+
return false unless other.is_a? Rule
|
24
|
+
hash == other.hash
|
27
25
|
end
|
28
26
|
|
29
27
|
def hash
|
@@ -31,7 +29,7 @@ module IceCube
|
|
31
29
|
end
|
32
30
|
|
33
31
|
def to_ical
|
34
|
-
raise MethodNotImplemented, "Expected to be
|
32
|
+
raise MethodNotImplemented, "Expected to be overridden by subclasses"
|
35
33
|
end
|
36
34
|
|
37
35
|
# Convert from ical string and create a rule
|
@@ -60,11 +58,6 @@ module IceCube
|
|
60
58
|
next_time(time, schedule, time).to_i == time.to_i
|
61
59
|
end
|
62
60
|
|
63
|
-
# Whether this rule requires a full run
|
64
|
-
def full_required?
|
65
|
-
!@count.nil?
|
66
|
-
end
|
67
|
-
|
68
61
|
class << self
|
69
62
|
|
70
63
|
# Convert from a hash and create a rule
|
@@ -2,6 +2,15 @@ module IceCube
|
|
2
2
|
|
3
3
|
class DailyRule < ValidatedRule
|
4
4
|
|
5
|
+
include Validations::HourOfDay
|
6
|
+
include Validations::MinuteOfHour
|
7
|
+
include Validations::SecondOfMinute
|
8
|
+
include Validations::DayOfMonth
|
9
|
+
include Validations::DayOfWeek
|
10
|
+
include Validations::Day
|
11
|
+
include Validations::MonthOfYear
|
12
|
+
# include Validations::DayOfYear # n/a
|
13
|
+
|
5
14
|
include Validations::DailyInterval
|
6
15
|
|
7
16
|
def initialize(interval = 1)
|
@@ -2,6 +2,15 @@ module IceCube
|
|
2
2
|
|
3
3
|
class HourlyRule < ValidatedRule
|
4
4
|
|
5
|
+
include Validations::HourOfDay
|
6
|
+
include Validations::MinuteOfHour
|
7
|
+
include Validations::SecondOfMinute
|
8
|
+
include Validations::DayOfMonth
|
9
|
+
include Validations::DayOfWeek
|
10
|
+
include Validations::Day
|
11
|
+
include Validations::MonthOfYear
|
12
|
+
include Validations::DayOfYear
|
13
|
+
|
5
14
|
include Validations::HourlyInterval
|
6
15
|
|
7
16
|
def initialize(interval = 1)
|
@@ -2,6 +2,15 @@ module IceCube
|
|
2
2
|
|
3
3
|
class MinutelyRule < ValidatedRule
|
4
4
|
|
5
|
+
include Validations::HourOfDay
|
6
|
+
include Validations::MinuteOfHour
|
7
|
+
include Validations::SecondOfMinute
|
8
|
+
include Validations::DayOfMonth
|
9
|
+
include Validations::DayOfWeek
|
10
|
+
include Validations::Day
|
11
|
+
include Validations::MonthOfYear
|
12
|
+
include Validations::DayOfYear
|
13
|
+
|
5
14
|
include Validations::MinutelyInterval
|
6
15
|
|
7
16
|
def initialize(interval = 1)
|
@@ -2,6 +2,15 @@ module IceCube
|
|
2
2
|
|
3
3
|
class MonthlyRule < ValidatedRule
|
4
4
|
|
5
|
+
include Validations::HourOfDay
|
6
|
+
include Validations::MinuteOfHour
|
7
|
+
include Validations::SecondOfMinute
|
8
|
+
include Validations::DayOfMonth
|
9
|
+
include Validations::DayOfWeek
|
10
|
+
include Validations::Day
|
11
|
+
include Validations::MonthOfYear
|
12
|
+
# include Validations::DayOfYear # n/a
|
13
|
+
|
5
14
|
include Validations::MonthlyInterval
|
6
15
|
|
7
16
|
def initialize(interval = 1)
|
@@ -2,6 +2,15 @@ module IceCube
|
|
2
2
|
|
3
3
|
class SecondlyRule < ValidatedRule
|
4
4
|
|
5
|
+
include Validations::HourOfDay
|
6
|
+
include Validations::MinuteOfHour
|
7
|
+
include Validations::SecondOfMinute
|
8
|
+
include Validations::DayOfMonth
|
9
|
+
include Validations::DayOfWeek
|
10
|
+
include Validations::Day
|
11
|
+
include Validations::MonthOfYear
|
12
|
+
include Validations::DayOfYear
|
13
|
+
|
5
14
|
include Validations::SecondlyInterval
|
6
15
|
|
7
16
|
def initialize(interval = 1)
|
@@ -2,6 +2,15 @@ module IceCube
|
|
2
2
|
|
3
3
|
class WeeklyRule < ValidatedRule
|
4
4
|
|
5
|
+
include Validations::HourOfDay
|
6
|
+
include Validations::MinuteOfHour
|
7
|
+
include Validations::SecondOfMinute
|
8
|
+
# include Validations::DayOfMonth # n/a
|
9
|
+
include Validations::DayOfWeek
|
10
|
+
include Validations::Day
|
11
|
+
include Validations::MonthOfYear
|
12
|
+
# include Validations::DayOfYear # n/a
|
13
|
+
|
5
14
|
include Validations::WeeklyInterval
|
6
15
|
|
7
16
|
attr_reader :week_start
|
@@ -28,7 +37,7 @@ module IceCube
|
|
28
37
|
time = TimeUtil::TimeWrapper.new(start_time)
|
29
38
|
offset = wday_offset(step_time, start_time)
|
30
39
|
time.add(:day, offset)
|
31
|
-
time.to_time
|
40
|
+
super step_time, time.to_time
|
32
41
|
end
|
33
42
|
|
34
43
|
# Calculate how many days to the first wday validation in the correct
|
@@ -2,6 +2,15 @@ module IceCube
|
|
2
2
|
|
3
3
|
class YearlyRule < ValidatedRule
|
4
4
|
|
5
|
+
include Validations::HourOfDay
|
6
|
+
include Validations::MinuteOfHour
|
7
|
+
include Validations::SecondOfMinute
|
8
|
+
include Validations::DayOfMonth
|
9
|
+
include Validations::DayOfWeek
|
10
|
+
include Validations::Day
|
11
|
+
include Validations::MonthOfYear
|
12
|
+
include Validations::DayOfYear
|
13
|
+
|
5
14
|
include Validations::YearlyInterval
|
6
15
|
|
7
16
|
def initialize(interval = 1)
|
data/lib/ice_cube/schedule.rb
CHANGED
@@ -340,9 +340,9 @@ module IceCube
|
|
340
340
|
IcalParser.schedule_from_ical(ical, options)
|
341
341
|
end
|
342
342
|
|
343
|
-
#
|
344
|
-
def
|
345
|
-
|
343
|
+
# Hook for YAML.dump, enables to_yaml
|
344
|
+
def encode_with(coder)
|
345
|
+
coder.represent_object nil, to_hash
|
346
346
|
end
|
347
347
|
|
348
348
|
# Load the schedule from yaml
|
@@ -371,6 +371,7 @@ module IceCube
|
|
371
371
|
end
|
372
372
|
data
|
373
373
|
end
|
374
|
+
alias_method :to_h, :to_hash
|
374
375
|
|
375
376
|
# Load the schedule from a hash
|
376
377
|
def self.from_hash(original_hash, options = {})
|
@@ -383,7 +384,7 @@ module IceCube
|
|
383
384
|
# Determine if the schedule will end
|
384
385
|
# @return [Boolean] true if ending, false if repeating forever
|
385
386
|
def terminating?
|
386
|
-
|
387
|
+
@all_recurrence_rules.all?(&:terminating?)
|
387
388
|
end
|
388
389
|
|
389
390
|
def hash
|
@@ -423,7 +424,7 @@ module IceCube
|
|
423
424
|
def enumerate_occurrences(opening_time, closing_time = nil, options = {})
|
424
425
|
opening_time = TimeUtil.match_zone(opening_time, start_time)
|
425
426
|
closing_time = TimeUtil.match_zone(closing_time, start_time)
|
426
|
-
opening_time +=
|
427
|
+
opening_time += TimeUtil.subsec(start_time) - TimeUtil.subsec(opening_time)
|
427
428
|
opening_time = start_time if opening_time < start_time
|
428
429
|
spans = options[:spans] == true && duration != 0
|
429
430
|
Enumerator.new do |yielder|
|
@@ -445,12 +446,12 @@ module IceCube
|
|
445
446
|
# Get the next time after (or including) a specific time
|
446
447
|
def next_time(time, closing_time)
|
447
448
|
loop do
|
448
|
-
min_time = recurrence_rules_with_implicit_start_occurrence.reduce(nil) do |
|
449
|
+
min_time = recurrence_rules_with_implicit_start_occurrence.reduce(nil) do |best_time, rule|
|
449
450
|
begin
|
450
|
-
new_time = rule.next_time(time, start_time,
|
451
|
-
[
|
451
|
+
new_time = rule.next_time(time, start_time, best_time || closing_time)
|
452
|
+
[best_time, new_time].compact.min
|
452
453
|
rescue StopIteration
|
453
|
-
|
454
|
+
best_time
|
454
455
|
end
|
455
456
|
end
|
456
457
|
break unless min_time
|
data/lib/ice_cube/time_util.rb
CHANGED
@@ -170,20 +170,13 @@ module IceCube
|
|
170
170
|
|
171
171
|
# Convert wday number to day symbol
|
172
172
|
def self.wday_to_sym(wday)
|
173
|
-
return
|
173
|
+
return wday if DAYS.keys.include? wday
|
174
174
|
DAYS.invert.fetch(wday) do |i|
|
175
175
|
raise ArgumentError, "Expecting Integer value for weekday. " \
|
176
176
|
"No such wday number: #{i.inspect}"
|
177
177
|
end
|
178
178
|
end
|
179
179
|
|
180
|
-
# Convert a symbol to an ical day (SU, MO)
|
181
|
-
def self.week_start(sym)
|
182
|
-
raise ArgumentError, "Invalid day: #{str}" unless DAYS.keys.include?(sym)
|
183
|
-
day = sym.to_s.upcase[0..1]
|
184
|
-
day
|
185
|
-
end
|
186
|
-
|
187
180
|
# Convert weekday from base sunday to the schedule's week start.
|
188
181
|
def self.normalize_wday(wday, week_start)
|
189
182
|
(wday - sym_to_wday(week_start)) % 7
|
@@ -260,8 +253,19 @@ module IceCube
|
|
260
253
|
end
|
261
254
|
end
|
262
255
|
|
263
|
-
|
264
|
-
|
256
|
+
# Handle discrepancies between various time types
|
257
|
+
# - Time has subsec
|
258
|
+
# - DateTime does not
|
259
|
+
# - ActiveSupport::TimeWithZone can wrap either type, depending on version
|
260
|
+
# or if `parse` or `now`/`local` was used to build it.
|
261
|
+
def self.subsec(time)
|
262
|
+
if time.respond_to?(:subsec)
|
263
|
+
time.subsec
|
264
|
+
elsif time.respond_to?(:sec_fraction)
|
265
|
+
time.sec_fraction
|
266
|
+
else
|
267
|
+
0.0
|
268
|
+
end
|
265
269
|
end
|
266
270
|
|
267
271
|
# A utility class for safely moving time around
|
@@ -271,7 +275,7 @@ module IceCube
|
|
271
275
|
@dst_adjust = dst_adjust
|
272
276
|
@base = time
|
273
277
|
if dst_adjust
|
274
|
-
@time = Time.utc(time.year, time.month, time.day, time.hour, time.min, time.sec +
|
278
|
+
@time = Time.utc(time.year, time.month, time.day, time.hour, time.min, time.sec + TimeUtil.subsec(time))
|
275
279
|
else
|
276
280
|
@time = time
|
277
281
|
end
|
@@ -307,7 +311,17 @@ module IceCube
|
|
307
311
|
end
|
308
312
|
end
|
309
313
|
|
310
|
-
|
314
|
+
def hour=(value)
|
315
|
+
@time += (value * ONE_HOUR) - (@time.hour * ONE_HOUR)
|
316
|
+
end
|
317
|
+
|
318
|
+
def min=(value)
|
319
|
+
@time += (value * ONE_MINUTE) - (@time.min * ONE_MINUTE)
|
320
|
+
end
|
321
|
+
|
322
|
+
def sec=(value)
|
323
|
+
@time += (value) - (@time.sec)
|
324
|
+
end
|
311
325
|
|
312
326
|
def clear_sec
|
313
327
|
@time.sec > 0 ? @time -= @time.sec : @time
|
@@ -335,10 +349,6 @@ module IceCube
|
|
335
349
|
@time += ONE_DAY
|
336
350
|
end
|
337
351
|
|
338
|
-
def clear_year
|
339
|
-
@time
|
340
|
-
end
|
341
|
-
|
342
352
|
end
|
343
353
|
|
344
354
|
end
|
@@ -1,18 +1,11 @@
|
|
1
|
+
require 'ice_cube/input_alignment'
|
2
|
+
|
1
3
|
module IceCube
|
2
4
|
|
3
5
|
class ValidatedRule < Rule
|
4
6
|
|
5
7
|
include Validations::ScheduleLock
|
6
8
|
|
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
9
|
include Validations::Count
|
17
10
|
include Validations::Until
|
18
11
|
|
@@ -51,10 +44,6 @@ module IceCube
|
|
51
44
|
Array(@validations[base_interval_validation.type])
|
52
45
|
end
|
53
46
|
|
54
|
-
def base_interval_type
|
55
|
-
base_interval_validation.type
|
56
|
-
end
|
57
|
-
|
58
47
|
# Compute the next time after (or including) the specified time in respect
|
59
48
|
# to the given start time
|
60
49
|
def next_time(time, start_time, closing_time)
|
@@ -74,12 +63,8 @@ module IceCube
|
|
74
63
|
start_time
|
75
64
|
end
|
76
65
|
|
77
|
-
def
|
78
|
-
|
79
|
-
end
|
80
|
-
|
81
|
-
def dst_adjust?
|
82
|
-
@validations[:interval].any?(&:dst_adjust?)
|
66
|
+
def full_required?
|
67
|
+
!occurrence_count.nil?
|
83
68
|
end
|
84
69
|
|
85
70
|
def to_s
|
@@ -193,6 +178,12 @@ module IceCube
|
|
193
178
|
VALIDATION_ORDER & @validations.keys
|
194
179
|
end
|
195
180
|
|
181
|
+
def verify_alignment(value, freq, rule_part)
|
182
|
+
InputAlignment.new(self, value, rule_part).verify(freq) do |error|
|
183
|
+
yield error
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
196
187
|
end
|
197
188
|
|
198
189
|
end
|
@@ -4,14 +4,13 @@ module IceCube
|
|
4
4
|
|
5
5
|
# Value reader for limit
|
6
6
|
def occurrence_count
|
7
|
-
@count
|
7
|
+
(arr = @validations[:count]) && (val = arr[0]) && val.count
|
8
8
|
end
|
9
9
|
|
10
10
|
def count(max)
|
11
11
|
unless max.nil? || max.is_a?(Integer)
|
12
12
|
raise ArgumentError, "Expecting Integer or nil value for count, got #{max.inspect}"
|
13
13
|
end
|
14
|
-
@count = max
|
15
14
|
replace_validations_for(:count, max && [Validation.new(max, self)])
|
16
15
|
self
|
17
16
|
end
|
@@ -4,7 +4,11 @@ module IceCube
|
|
4
4
|
|
5
5
|
# Add a new interval validation
|
6
6
|
def interval(interval)
|
7
|
-
|
7
|
+
interval = normalized_interval(interval)
|
8
|
+
verify_alignment(interval, :wday, :interval) { |error| raise error }
|
9
|
+
verify_alignment(interval, :day, :interval) { |error| raise error }
|
10
|
+
|
11
|
+
@interval = interval
|
8
12
|
replace_validations_for(:interval, [Validation.new(@interval)])
|
9
13
|
clobber_base_validations(:wday, :day)
|
10
14
|
self
|
@@ -1,5 +1,3 @@
|
|
1
|
-
require 'date'
|
2
|
-
|
3
1
|
module IceCube
|
4
2
|
|
5
3
|
module Validations::Day
|
@@ -12,6 +10,8 @@ module IceCube
|
|
12
10
|
raise ArgumentError, "expecting Integer or Symbol value for day, got #{day.inspect}"
|
13
11
|
end
|
14
12
|
day = TimeUtil.sym_to_wday(day)
|
13
|
+
verify_alignment(day, :wday, :day) { |error| raise error }
|
14
|
+
|
15
15
|
validations_for(:day) << Validation.new(day)
|
16
16
|
end
|
17
17
|
clobber_base_validations(:wday, :day)
|
@@ -27,6 +27,10 @@ module IceCube
|
|
27
27
|
@day = day
|
28
28
|
end
|
29
29
|
|
30
|
+
def key
|
31
|
+
:day
|
32
|
+
end
|
33
|
+
|
30
34
|
def type
|
31
35
|
:wday
|
32
36
|
end
|
@@ -7,6 +7,7 @@ module IceCube
|
|
7
7
|
unless day.is_a?(Integer)
|
8
8
|
raise ArgumentError, "expecting Integer value for day, got #{day.inspect}"
|
9
9
|
end
|
10
|
+
verify_alignment(day, :day, :day_of_month) { |error| raise error }
|
10
11
|
validations_for(:day_of_month) << Validation.new(day)
|
11
12
|
end
|
12
13
|
clobber_base_validations(:day, :wday)
|
@@ -22,6 +23,10 @@ module IceCube
|
|
22
23
|
@day = day
|
23
24
|
end
|
24
25
|
|
26
|
+
def key
|
27
|
+
:day_of_month
|
28
|
+
end
|
29
|
+
|
25
30
|
def type
|
26
31
|
:day
|
27
32
|
end
|
@@ -8,12 +8,31 @@ module IceCube
|
|
8
8
|
unless hour.is_a?(Integer)
|
9
9
|
raise ArgumentError, "expecting Integer value for hour, got #{hour.inspect}"
|
10
10
|
end
|
11
|
+
|
12
|
+
verify_alignment(hour, :hour, :hour_of_day) { |error| raise error }
|
13
|
+
|
11
14
|
validations_for(:hour_of_day) << Validation.new(hour)
|
12
15
|
end
|
13
16
|
clobber_base_validations(:hour)
|
14
17
|
self
|
15
18
|
end
|
16
19
|
|
20
|
+
def realign(opening_time, start_time)
|
21
|
+
return super unless validations[:hour_of_day]
|
22
|
+
freq = base_interval_validation.interval
|
23
|
+
|
24
|
+
first_hour = Array(validations[:hour_of_day]).min_by(&:value)
|
25
|
+
time = TimeUtil::TimeWrapper.new(start_time, false)
|
26
|
+
if freq > 1
|
27
|
+
offset = first_hour.validate(opening_time, start_time)
|
28
|
+
time.add(:hour, offset - freq)
|
29
|
+
else
|
30
|
+
time.hour = first_hour.value
|
31
|
+
end
|
32
|
+
|
33
|
+
super opening_time, time.to_time
|
34
|
+
end
|
35
|
+
|
17
36
|
class Validation < Validations::FixedValue
|
18
37
|
|
19
38
|
attr_reader :hour
|
@@ -23,6 +42,10 @@ module IceCube
|
|
23
42
|
@hour = hour
|
24
43
|
end
|
25
44
|
|
45
|
+
def key
|
46
|
+
:hour_of_day
|
47
|
+
end
|
48
|
+
|
26
49
|
def type
|
27
50
|
:hour
|
28
51
|
end
|
@@ -3,6 +3,8 @@ module IceCube
|
|
3
3
|
module Validations::HourlyInterval
|
4
4
|
|
5
5
|
def interval(interval)
|
6
|
+
verify_alignment(interval, :hour, :interval) { |error| raise error }
|
7
|
+
|
6
8
|
@interval = normalized_interval(interval)
|
7
9
|
replace_validations_for(:interval, [Validation.new(@interval)])
|
8
10
|
clobber_base_validations(:hour)
|
@@ -7,12 +7,24 @@ module IceCube
|
|
7
7
|
unless minute.is_a?(Integer)
|
8
8
|
raise ArgumentError, "expecting Integer value for minute, got #{minute.inspect}"
|
9
9
|
end
|
10
|
+
|
11
|
+
verify_alignment(minute, :min, :minute_of_hour) { |error| raise error }
|
12
|
+
|
10
13
|
validations_for(:minute_of_hour) << Validation.new(minute)
|
11
14
|
end
|
12
15
|
clobber_base_validations(:min)
|
13
16
|
self
|
14
17
|
end
|
15
18
|
|
19
|
+
def realign(opening_time, start_time)
|
20
|
+
return super unless validations[:minute_of_hour]
|
21
|
+
|
22
|
+
first_minute = validations[:minute_of_hour].min_by(&:value)
|
23
|
+
time = TimeUtil::TimeWrapper.new(start_time, false)
|
24
|
+
time.min = first_minute.value
|
25
|
+
super opening_time, time.to_time
|
26
|
+
end
|
27
|
+
|
16
28
|
class Validation < Validations::FixedValue
|
17
29
|
|
18
30
|
attr_reader :minute
|
@@ -22,6 +34,10 @@ module IceCube
|
|
22
34
|
@minute = minute
|
23
35
|
end
|
24
36
|
|
37
|
+
def key
|
38
|
+
:minute_of_hour
|
39
|
+
end
|
40
|
+
|
25
41
|
def type
|
26
42
|
:min
|
27
43
|
end
|
@@ -3,6 +3,8 @@ module IceCube
|
|
3
3
|
module Validations::MinutelyInterval
|
4
4
|
|
5
5
|
def interval(interval)
|
6
|
+
verify_alignment(interval, :min, :interval) { |error| raise error }
|
7
|
+
|
6
8
|
@interval = normalized_interval(interval)
|
7
9
|
replace_validations_for(:interval, [Validation.new(@interval)])
|
8
10
|
clobber_base_validations(:min)
|
@@ -8,6 +8,7 @@ module IceCube
|
|
8
8
|
raise ArgumentError, "expecting Integer or Symbol value for month, got #{month.inspect}"
|
9
9
|
end
|
10
10
|
month = TimeUtil.sym_to_month(month)
|
11
|
+
verify_alignment(month, :month, :month_of_year) { |error| raise error }
|
11
12
|
validations_for(:month_of_year) << Validation.new(month)
|
12
13
|
end
|
13
14
|
clobber_base_validations :month
|
@@ -23,6 +24,10 @@ module IceCube
|
|
23
24
|
@month = month
|
24
25
|
end
|
25
26
|
|
27
|
+
def key
|
28
|
+
:month_of_year
|
29
|
+
end
|
30
|
+
|
26
31
|
def type
|
27
32
|
:month
|
28
33
|
end
|
@@ -3,7 +3,10 @@ module IceCube
|
|
3
3
|
module Validations::MonthlyInterval
|
4
4
|
|
5
5
|
def interval(interval)
|
6
|
-
|
6
|
+
interval = normalized_interval(interval)
|
7
|
+
verify_alignment(interval, :month, :interval) { |error| raise error }
|
8
|
+
|
9
|
+
@interval = interval
|
7
10
|
replace_validations_for(:interval, [Validation.new(@interval)])
|
8
11
|
clobber_base_validations(:month)
|
9
12
|
self
|
@@ -4,15 +4,27 @@ module IceCube
|
|
4
4
|
|
5
5
|
def second_of_minute(*seconds)
|
6
6
|
seconds.flatten.each do |second|
|
7
|
-
|
8
|
-
|
9
|
-
|
7
|
+
unless second.is_a?(Integer)
|
8
|
+
raise ArgumentError, "Expecting Integer value for second, got #{second.inspect}"
|
9
|
+
end
|
10
|
+
|
11
|
+
verify_alignment(second, :sec, :second_of_minute) { |error| raise error }
|
12
|
+
|
10
13
|
validations_for(:second_of_minute) << Validation.new(second)
|
11
14
|
end
|
12
15
|
clobber_base_validations :sec
|
13
16
|
self
|
14
17
|
end
|
15
18
|
|
19
|
+
def realign(opening_time, start_time)
|
20
|
+
return super unless validations[:second_of_minute]
|
21
|
+
|
22
|
+
first_second = Array(validations[:second_of_minute]).min_by(&:value)
|
23
|
+
time = TimeUtil::TimeWrapper.new(start_time, false)
|
24
|
+
time.sec = first_second.value
|
25
|
+
super opening_time, time.to_time
|
26
|
+
end
|
27
|
+
|
16
28
|
class Validation < Validations::FixedValue
|
17
29
|
|
18
30
|
attr_reader :second
|
@@ -22,6 +34,10 @@ module IceCube
|
|
22
34
|
@second = second
|
23
35
|
end
|
24
36
|
|
37
|
+
def key
|
38
|
+
:second_of_minute
|
39
|
+
end
|
40
|
+
|
25
41
|
def type
|
26
42
|
:sec
|
27
43
|
end
|
@@ -3,6 +3,8 @@ module IceCube
|
|
3
3
|
module Validations::SecondlyInterval
|
4
4
|
|
5
5
|
def interval(interval)
|
6
|
+
verify_alignment(interval, :sec, :interval) { |error| raise error }
|
7
|
+
|
6
8
|
@interval = normalized_interval(interval)
|
7
9
|
replace_validations_for(:interval, [Validation.new(@interval)])
|
8
10
|
clobber_base_validations(:sec)
|
@@ -6,12 +6,11 @@ module IceCube
|
|
6
6
|
|
7
7
|
# Value reader for limit
|
8
8
|
def until_time
|
9
|
-
@until
|
9
|
+
(arr = @validations[:until]) && (val = arr[0]) && val.time
|
10
10
|
end
|
11
11
|
deprecated_alias :until_date, :until_time
|
12
12
|
|
13
13
|
def until(time)
|
14
|
-
@until = time
|
15
14
|
replace_validations_for(:until, time.nil? ? nil : [Validation.new(time)])
|
16
15
|
self
|
17
16
|
end
|
data/lib/ice_cube/version.rb
CHANGED
data/spec/spec_helper.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
require "bundler/setup"
|
2
2
|
require 'ice_cube'
|
3
|
+
require 'timeout'
|
3
4
|
|
4
5
|
begin
|
5
6
|
require 'simplecov'
|
@@ -19,6 +20,17 @@ WORLD_TIME_ZONES = [
|
|
19
20
|
'Pacific/Auckland', # +1200 / +1300
|
20
21
|
]
|
21
22
|
|
23
|
+
# TODO: enable warnings here and update specs to call IceCube objects correctly
|
24
|
+
def Object.const_missing(sym)
|
25
|
+
case sym
|
26
|
+
when :Schedule, :Rule, :Occurrence, :TimeUtil, :ONE_DAY, :ONE_HOUR, :ONE_MINUTE
|
27
|
+
# warn "Use IceCube::#{sym}", caller[0]
|
28
|
+
IceCube.const_get(sym)
|
29
|
+
else
|
30
|
+
super
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
22
34
|
RSpec.configure do |config|
|
23
35
|
# Enable flags like --only-failures and --next-failure
|
24
36
|
config.example_status_persistence_file_path = ".rspec_status"
|
@@ -29,6 +41,8 @@ RSpec.configure do |config|
|
|
29
41
|
|
30
42
|
Dir[File.dirname(__FILE__) + '/support/**/*'].each { |f| require f }
|
31
43
|
|
44
|
+
config.warnings = true
|
45
|
+
|
32
46
|
config.include WarningHelpers
|
33
47
|
|
34
48
|
config.before :each do |example|
|
@@ -37,15 +51,18 @@ RSpec.configure do |config|
|
|
37
51
|
end
|
38
52
|
end
|
39
53
|
|
40
|
-
config.around :each do |example|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
54
|
+
config.around :each, system_time_zone: true do |example|
|
55
|
+
orig_zone = ENV['TZ']
|
56
|
+
ENV['TZ'] = example.metadata[:system_time_zone]
|
57
|
+
example.run
|
58
|
+
ENV['TZ'] = orig_zone
|
59
|
+
end
|
60
|
+
|
61
|
+
config.around :each, locale: true do |example|
|
62
|
+
orig_locale = I18n.locale
|
63
|
+
I18n.locale = example.metadata[:locale]
|
64
|
+
example.run
|
65
|
+
I18n.locale = orig_locale
|
49
66
|
end
|
50
67
|
|
51
68
|
config.around :each, expect_warnings: true do |example|
|
@@ -53,4 +70,10 @@ RSpec.configure do |config|
|
|
53
70
|
example.run
|
54
71
|
end
|
55
72
|
end
|
73
|
+
|
74
|
+
config.around :each do |example|
|
75
|
+
Timeout.timeout(example.metadata.fetch(:timeout, 1)) do
|
76
|
+
example.run
|
77
|
+
end
|
78
|
+
end
|
56
79
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ice_cube
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.16.
|
4
|
+
version: 0.16.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- John Crepezzi
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2018-07-24 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rake
|
@@ -62,6 +62,7 @@ files:
|
|
62
62
|
- lib/ice_cube/errors/until_exceeded.rb
|
63
63
|
- lib/ice_cube/flexible_hash.rb
|
64
64
|
- lib/ice_cube/i18n.rb
|
65
|
+
- lib/ice_cube/input_alignment.rb
|
65
66
|
- lib/ice_cube/null_i18n.rb
|
66
67
|
- lib/ice_cube/occurrence.rb
|
67
68
|
- lib/ice_cube/parsers/hash_parser.rb
|
@@ -120,8 +121,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
120
121
|
- !ruby/object:Gem::Version
|
121
122
|
version: '0'
|
122
123
|
requirements: []
|
123
|
-
rubyforge_project:
|
124
|
-
rubygems_version: 2.6.
|
124
|
+
rubyforge_project:
|
125
|
+
rubygems_version: 2.6.14
|
125
126
|
signing_key:
|
126
127
|
specification_version: 4
|
127
128
|
summary: Ruby Date Recurrence Library
|