montrose 0.11.0 → 0.13.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 +4 -4
- data/.circleci/config.yml +66 -0
- data/Appraisals +8 -12
- data/CHANGELOG.md +24 -0
- data/Guardfile +2 -2
- data/README.md +27 -15
- data/Rakefile +2 -4
- data/bin/setup +1 -0
- data/bin/standardrb +29 -0
- data/gemfiles/activesupport_5.2.gemfile +5 -1
- data/gemfiles/activesupport_6.0.gemfile +5 -1
- data/gemfiles/{activesupport_4.2.gemfile → activesupport_6.1.gemfile} +5 -1
- data/gemfiles/{activesupport_5.0.gemfile → activesupport_7.0.gemfile} +5 -1
- data/lib/montrose/chainable.rb +26 -10
- data/lib/montrose/clock.rb +4 -4
- data/lib/montrose/day.rb +83 -0
- data/lib/montrose/frequency.rb +58 -25
- data/lib/montrose/hour.rb +22 -0
- data/lib/montrose/ical.rb +128 -0
- data/lib/montrose/minute.rb +22 -0
- data/lib/montrose/month.rb +47 -0
- data/lib/montrose/month_day.rb +25 -0
- data/lib/montrose/options.rb +73 -79
- data/lib/montrose/recurrence.rb +40 -13
- data/lib/montrose/rule/between.rb +1 -1
- data/lib/montrose/rule/covering.rb +40 -0
- data/lib/montrose/rule/during.rb +7 -15
- data/lib/montrose/rule/minute_of_hour.rb +25 -0
- data/lib/montrose/rule/nth_day_of_month.rb +0 -2
- data/lib/montrose/rule/nth_day_of_year.rb +0 -2
- data/lib/montrose/rule/time_of_day.rb +1 -1
- data/lib/montrose/rule/until.rb +1 -1
- data/lib/montrose/rule.rb +18 -16
- data/lib/montrose/schedule.rb +6 -8
- data/lib/montrose/stack.rb +3 -4
- data/lib/montrose/time_of_day.rb +48 -0
- data/lib/montrose/utils.rb +2 -40
- data/lib/montrose/version.rb +1 -1
- data/lib/montrose/week.rb +20 -0
- data/lib/montrose/year_day.rb +25 -0
- data/lib/montrose.rb +43 -11
- data/montrose.gemspec +17 -17
- metadata +43 -36
- data/.rubocop.yml +0 -136
- data/.travis.yml +0 -33
- data/bin/rubocop +0 -16
- data/gemfiles/activesupport_4.1.gemfile +0 -12
- data/gemfiles/activesupport_5.1.gemfile +0 -12
data/lib/montrose/frequency.rb
CHANGED
@@ -1,14 +1,19 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "montrose/errors"
|
4
|
-
require "montrose/options"
|
5
|
-
|
6
3
|
module Montrose
|
7
4
|
# Abstract class for special recurrence rule required
|
8
5
|
# in all instances of Recurrence. Frequency describes
|
9
6
|
# the base recurrence interval.
|
10
7
|
#
|
11
8
|
class Frequency
|
9
|
+
autoload :Daily, "montrose/frequency/daily"
|
10
|
+
autoload :Hourly, "montrose/frequency/hourly"
|
11
|
+
autoload :Minutely, "montrose/frequency/minutely"
|
12
|
+
autoload :Monthly, "montrose/frequency/monthly"
|
13
|
+
autoload :Secondly, "montrose/frequency/secondly"
|
14
|
+
autoload :Weekly, "montrose/frequency/weekly"
|
15
|
+
autoload :Yearly, "montrose/frequency/yearly"
|
16
|
+
|
12
17
|
include Montrose::Rule
|
13
18
|
|
14
19
|
FREQUENCY_TERMS = {
|
@@ -25,24 +30,60 @@ module Montrose
|
|
25
30
|
|
26
31
|
attr_reader :time, :starts
|
27
32
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
33
|
+
class << self
|
34
|
+
def parse(input)
|
35
|
+
if input.respond_to?(:parts)
|
36
|
+
frequency, interval = duration_to_frequency_parts(input)
|
37
|
+
{every: frequency.to_s.singularize.to_sym, interval: interval}
|
38
|
+
elsif input.is_a?(Numeric)
|
39
|
+
frequency, interval = numeric_to_frequency_parts(input)
|
40
|
+
{every: frequency, interval: interval}
|
41
|
+
else
|
42
|
+
{every: Frequency.assert(input)}
|
43
|
+
end
|
35
44
|
end
|
36
45
|
|
37
|
-
|
38
|
-
|
46
|
+
# Factory method for instantiating the appropriate Frequency
|
47
|
+
# subclass.
|
48
|
+
#
|
49
|
+
def from_options(opts)
|
50
|
+
frequency = opts.fetch(:every) { fail ConfigurationError, "Please specify the :every option" }
|
51
|
+
class_name = FREQUENCY_TERMS.fetch(frequency.to_s) {
|
52
|
+
fail "Don't know how to enumerate every: #{frequency}"
|
53
|
+
}
|
54
|
+
|
55
|
+
Montrose::Frequency.const_get(class_name).new(opts)
|
56
|
+
end
|
57
|
+
|
58
|
+
def from_term(term)
|
59
|
+
FREQUENCY_TERMS.invert.map { |k, v| [k.downcase, v] }.to_h.fetch(term.downcase) do
|
60
|
+
fail "Don't know how to convert #{term} to a Montrose frequency"
|
61
|
+
end
|
62
|
+
end
|
39
63
|
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
64
|
+
# @private
|
65
|
+
def assert(frequency)
|
66
|
+
FREQUENCY_TERMS.key?(frequency.to_s) || fail(ConfigurationError,
|
67
|
+
"Don't know how to enumerate every: #{frequency}")
|
44
68
|
|
45
|
-
|
69
|
+
frequency.to_sym
|
70
|
+
end
|
71
|
+
|
72
|
+
# @private
|
73
|
+
def numeric_to_frequency_parts(number)
|
74
|
+
parts = nil
|
75
|
+
%i[year month week day hour minute].each do |freq|
|
76
|
+
div, mod = number.divmod(1.send(freq))
|
77
|
+
parts = [freq, div]
|
78
|
+
return parts if mod.zero?
|
79
|
+
end
|
80
|
+
parts
|
81
|
+
end
|
82
|
+
|
83
|
+
# @private
|
84
|
+
def duration_to_frequency_parts(duration)
|
85
|
+
duration.parts.first
|
86
|
+
end
|
46
87
|
end
|
47
88
|
|
48
89
|
def initialize(opts = {})
|
@@ -67,11 +108,3 @@ module Montrose
|
|
67
108
|
end
|
68
109
|
end
|
69
110
|
end
|
70
|
-
|
71
|
-
require "montrose/frequency/daily"
|
72
|
-
require "montrose/frequency/hourly"
|
73
|
-
require "montrose/frequency/minutely"
|
74
|
-
require "montrose/frequency/monthly"
|
75
|
-
require "montrose/frequency/secondly"
|
76
|
-
require "montrose/frequency/weekly"
|
77
|
-
require "montrose/frequency/yearly"
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Montrose
|
2
|
+
class Hour
|
3
|
+
HOURS_IN_DAY = 1.upto(24).to_a.freeze
|
4
|
+
|
5
|
+
class << self
|
6
|
+
def parse(arg)
|
7
|
+
case arg
|
8
|
+
when String
|
9
|
+
parse(arg.split(","))
|
10
|
+
else
|
11
|
+
Array(arg).map { |h| assert(h.to_i) }.presence
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def assert(hour)
|
16
|
+
raise ConfigurationError, "Out of range: #{HOURS_IN_DAY.inspect} does not include #{hour}" unless HOURS_IN_DAY.include?(hour)
|
17
|
+
|
18
|
+
hour
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,128 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Montrose
|
4
|
+
class ICal
|
5
|
+
# DTSTART;TZID=US-Eastern:19970902T090000
|
6
|
+
# RRULE:FREQ=DAILY;INTERVAL=2
|
7
|
+
def self.parse(ical)
|
8
|
+
new(ical).parse
|
9
|
+
end
|
10
|
+
|
11
|
+
def initialize(ical)
|
12
|
+
@ical = ical
|
13
|
+
end
|
14
|
+
|
15
|
+
def parse
|
16
|
+
time_zone = extract_time_zone(@ical)
|
17
|
+
|
18
|
+
Time.use_zone(time_zone) do
|
19
|
+
Hash[*parse_properties(@ical)]
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def extract_time_zone(ical_string)
|
26
|
+
_label, time_string = ical_string.split("\n").grep(/^DTSTART/).join.split(";")
|
27
|
+
time_zone_rule, _ = time_string.split(":")
|
28
|
+
_label, time_zone = (time_zone_rule || "").split("=")
|
29
|
+
time_zone
|
30
|
+
end
|
31
|
+
|
32
|
+
# First pass parsing to normalize arbitrary line breaks
|
33
|
+
def property_lines(ical_string)
|
34
|
+
ical_string.split("\n").each_with_object([]) do |line, lines|
|
35
|
+
case line
|
36
|
+
when /^(DTSTART|DTEND|EXDATE|RDATE|RRULE)/
|
37
|
+
lines << line
|
38
|
+
else
|
39
|
+
(lines.last || lines << "")
|
40
|
+
lines.last << line
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def parse_properties(ical_string)
|
46
|
+
property_lines(ical_string).flat_map do |line|
|
47
|
+
(property, value) = line.split(":")
|
48
|
+
(property, tzid) = property.split(";")
|
49
|
+
|
50
|
+
case property
|
51
|
+
when "DTSTART"
|
52
|
+
parse_dtstart(tzid, value)
|
53
|
+
when "DTEND"
|
54
|
+
warn "DTEND not currently supported!"
|
55
|
+
when "EXDATE"
|
56
|
+
parse_exdate(value)
|
57
|
+
when "RDATE"
|
58
|
+
warn "RDATE not currently supported!"
|
59
|
+
when "RRULE"
|
60
|
+
parse_rrule(value)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def parse_dtstart(tzid, time)
|
66
|
+
return [] unless time.present?
|
67
|
+
|
68
|
+
@starts_at = parse_time([tzid, time].compact.join(":"))
|
69
|
+
|
70
|
+
[:starts, @starts_at]
|
71
|
+
end
|
72
|
+
|
73
|
+
def parse_timezone(time_string)
|
74
|
+
time_zone_rule, _ = time_string.split(":")
|
75
|
+
_label, time_zone = (time_zone_rule || "").split("=")
|
76
|
+
time_zone
|
77
|
+
end
|
78
|
+
|
79
|
+
def parse_time(time_string)
|
80
|
+
time_zone = parse_timezone(time_string)
|
81
|
+
Montrose::Utils.parse_time(time_string).in_time_zone(time_zone)
|
82
|
+
end
|
83
|
+
|
84
|
+
def parse_exdate(exdate)
|
85
|
+
return [] unless exdate.present?
|
86
|
+
|
87
|
+
@except = Montrose::Utils.as_date(exdate) # only currently supports dates
|
88
|
+
|
89
|
+
[:except, @except]
|
90
|
+
end
|
91
|
+
|
92
|
+
def parse_rrule(rrule)
|
93
|
+
rrule.gsub(/\s+/, "").split(";").flat_map do |rule|
|
94
|
+
prop, value = rule.split("=")
|
95
|
+
case prop
|
96
|
+
when "FREQ"
|
97
|
+
[:every, Montrose::Frequency.from_term(value)]
|
98
|
+
when "INTERVAL"
|
99
|
+
[:interval, value.to_i]
|
100
|
+
when "COUNT"
|
101
|
+
[:total, value.to_i]
|
102
|
+
when "UNTIL"
|
103
|
+
[:until, parse_time(value)]
|
104
|
+
when "BYMINUTE"
|
105
|
+
[:minute, Montrose::Minute.parse(value)]
|
106
|
+
when "BYHOUR"
|
107
|
+
[:hour, Montrose::Hour.parse(value)]
|
108
|
+
when "BYMONTH"
|
109
|
+
[:month, Montrose::Month.parse(value)]
|
110
|
+
when "BYDAY"
|
111
|
+
[:day, Montrose::Day.parse(value)]
|
112
|
+
when "BYMONTHDAY"
|
113
|
+
[:mday, Montrose::MonthDay.parse(value)]
|
114
|
+
when "BYYEARDAY"
|
115
|
+
[:yday, Montrose::YearDay.parse(value)]
|
116
|
+
when "BYWEEKNO"
|
117
|
+
[:week, Montrose::Week.parse(value)]
|
118
|
+
when "WKST"
|
119
|
+
[:week_start, value]
|
120
|
+
when "BYSETPOS"
|
121
|
+
warn "BYSETPOS not currently supported!"
|
122
|
+
else
|
123
|
+
raise "Unrecognized rrule '#{rule}'"
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Montrose
|
2
|
+
class Minute
|
3
|
+
MINUTES_IN_HOUR = 0.upto(59).to_a.freeze
|
4
|
+
|
5
|
+
class << self
|
6
|
+
def parse(arg)
|
7
|
+
case arg
|
8
|
+
when String
|
9
|
+
parse(arg.split(","))
|
10
|
+
else
|
11
|
+
Array(arg).map { |m| assert(m.to_i) }.presence
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def assert(minute)
|
16
|
+
raise ConfigurationError, "Out of range: #{MINUTES_IN_HOUR.inspect} does not include #{minute}" unless MINUTES_IN_HOUR.include?(minute)
|
17
|
+
|
18
|
+
minute
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module Montrose
|
2
|
+
class Month
|
3
|
+
extend Montrose::Utils
|
4
|
+
|
5
|
+
NAMES = ::Date::MONTHNAMES # starts with nil to match 1-12 numbering
|
6
|
+
NUMBERS = NAMES.map.with_index { |_n, i| i.to_s }.slice(1, 12)
|
7
|
+
|
8
|
+
class << self
|
9
|
+
def parse(value)
|
10
|
+
case value
|
11
|
+
when String
|
12
|
+
parse(value.split(",").compact)
|
13
|
+
when Array
|
14
|
+
value.map { |m|
|
15
|
+
Montrose::Month.number!(m)
|
16
|
+
}.presence
|
17
|
+
else
|
18
|
+
parse(Array(value))
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def names
|
23
|
+
NAMES
|
24
|
+
end
|
25
|
+
|
26
|
+
def numbers
|
27
|
+
NUMBERS
|
28
|
+
end
|
29
|
+
|
30
|
+
def number(name)
|
31
|
+
case name
|
32
|
+
when Symbol, String
|
33
|
+
string = name.to_s
|
34
|
+
NAMES.index(string.titleize) || number(to_index(string))
|
35
|
+
when 1..12
|
36
|
+
name
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def number!(name)
|
41
|
+
numbers = NAMES.map.with_index { |_n, i| i.to_s }.slice(1, 12)
|
42
|
+
number(name) || raise(ConfigurationError,
|
43
|
+
"Did not recognize month #{name}, must be one of #{(NAMES + numbers).inspect}")
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Montrose
|
2
|
+
class MonthDay
|
3
|
+
class << self
|
4
|
+
MDAYS = (-31.upto(-1).to_a + 1.upto(31).to_a)
|
5
|
+
|
6
|
+
def parse(mdays)
|
7
|
+
return nil unless mdays.present?
|
8
|
+
|
9
|
+
case mdays
|
10
|
+
when String
|
11
|
+
parse(mdays.split(","))
|
12
|
+
else
|
13
|
+
Array(mdays).map { |d| assert(d.to_i) }
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def assert(number)
|
18
|
+
test = number.abs
|
19
|
+
raise ConfigurationError, "Out of range: #{MDAYS.inspect} does not include #{test}" unless MDAYS.include?(number.abs)
|
20
|
+
|
21
|
+
number
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
data/lib/montrose/options.rb
CHANGED
@@ -86,12 +86,15 @@ module Montrose
|
|
86
86
|
def_option :starts
|
87
87
|
def_option :until
|
88
88
|
def_option :between
|
89
|
+
def_option :covering
|
89
90
|
def_option :during
|
91
|
+
def_option :minute
|
90
92
|
def_option :hour
|
91
93
|
def_option :day
|
92
94
|
def_option :mday
|
93
95
|
def_option :yday
|
94
96
|
def_option :week
|
97
|
+
def_option :week_start
|
95
98
|
def_option :month
|
96
99
|
def_option :interval
|
97
100
|
def_option :total
|
@@ -112,6 +115,7 @@ module Montrose
|
|
112
115
|
week: nil,
|
113
116
|
month: nil,
|
114
117
|
total: nil,
|
118
|
+
week_start: nil,
|
115
119
|
exclude_end: nil
|
116
120
|
}
|
117
121
|
|
@@ -120,12 +124,12 @@ module Montrose
|
|
120
124
|
end
|
121
125
|
|
122
126
|
def to_hash
|
123
|
-
hash_pairs = self.class.defined_options.flat_map
|
127
|
+
hash_pairs = self.class.defined_options.flat_map { |opt_name|
|
124
128
|
[opt_name, send(opt_name)]
|
125
|
-
|
129
|
+
}
|
126
130
|
Hash[*hash_pairs].reject { |_k, v| v.nil? }
|
127
131
|
end
|
128
|
-
|
132
|
+
alias_method :to_h, :to_hash
|
129
133
|
|
130
134
|
def []=(option, val)
|
131
135
|
send(:"#{option}=", val)
|
@@ -142,13 +146,13 @@ module Montrose
|
|
142
146
|
self.class.new(h1.merge(h2))
|
143
147
|
end
|
144
148
|
|
145
|
-
def fetch(key, *args
|
146
|
-
|
149
|
+
def fetch(key, *args)
|
150
|
+
raise ArgumentError, "wrong number of arguments (#{args.length} for 1..2)" if args.length > 1
|
147
151
|
|
148
152
|
found = send(key)
|
149
153
|
return found if found
|
150
154
|
return args.first if args.length == 1
|
151
|
-
|
155
|
+
raise KeyError, "Key #{key.inspect} not found" unless block_given?
|
152
156
|
|
153
157
|
yield
|
154
158
|
end
|
@@ -165,8 +169,8 @@ module Montrose
|
|
165
169
|
@every = parsed.fetch(:every)
|
166
170
|
end
|
167
171
|
|
168
|
-
|
169
|
-
|
172
|
+
alias_method :frequency, :every
|
173
|
+
alias_method :frequency=, :every=
|
170
174
|
|
171
175
|
def starts=(time)
|
172
176
|
@starts = normalize_time(as_time(time)) || default_starts
|
@@ -176,47 +180,58 @@ module Montrose
|
|
176
180
|
@until = normalize_time(as_time(time)) || default_until
|
177
181
|
end
|
178
182
|
|
183
|
+
def minute=(minutes)
|
184
|
+
@minute = Minute.parse(minutes)
|
185
|
+
end
|
186
|
+
|
179
187
|
def hour=(hours)
|
180
188
|
@hour = map_arg(hours) { |h| assert_hour(h) }
|
181
189
|
end
|
182
190
|
|
183
|
-
def during=(
|
184
|
-
@during =
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
191
|
+
def during=(during_arg)
|
192
|
+
@during = decompose_during_arg(during_arg)
|
193
|
+
.each_with_object([]) { |(time_of_day_first, time_of_day_last), all|
|
194
|
+
if time_of_day_last < time_of_day_first
|
195
|
+
all.push(
|
196
|
+
[time_of_day_first.parts, end_of_day.parts],
|
197
|
+
[beginning_of_day.parts, time_of_day_last.parts]
|
198
|
+
)
|
199
|
+
else
|
200
|
+
all.push([time_of_day_first.parts, time_of_day_last.parts])
|
201
|
+
end
|
202
|
+
}.presence
|
190
203
|
end
|
191
204
|
|
192
205
|
def day=(days)
|
193
|
-
@day =
|
206
|
+
@day = Day.parse(days)
|
194
207
|
end
|
195
208
|
|
196
209
|
def mday=(mdays)
|
197
|
-
@mday =
|
210
|
+
@mday = MonthDay.parse(mdays)
|
198
211
|
end
|
199
212
|
|
200
213
|
def yday=(ydays)
|
201
|
-
@yday =
|
214
|
+
@yday = YearDay.parse(ydays)
|
202
215
|
end
|
203
216
|
|
204
217
|
def week=(weeks)
|
205
|
-
@week =
|
218
|
+
@week = Week.parse(weeks)
|
206
219
|
end
|
207
220
|
|
208
221
|
def month=(months)
|
209
|
-
@month =
|
222
|
+
@month = Month.parse(months)
|
210
223
|
end
|
211
224
|
|
212
225
|
def between=(range)
|
213
|
-
|
226
|
+
if Montrose.enable_deprecated_between_masking?
|
227
|
+
@covering = range
|
228
|
+
end
|
214
229
|
self[:starts] = range.first unless self[:starts]
|
215
230
|
self[:until] = range.last unless self[:until]
|
216
231
|
end
|
217
232
|
|
218
233
|
def at=(time)
|
219
|
-
@at = map_arg(time) { |t|
|
234
|
+
@at = map_arg(time) { |t| time_of_day_parse(t).parts }
|
220
235
|
end
|
221
236
|
|
222
237
|
def on=(arg)
|
@@ -257,51 +272,16 @@ module Montrose
|
|
257
272
|
self.class.default_until
|
258
273
|
end
|
259
274
|
|
260
|
-
def nested_map_arg(arg, &block)
|
261
|
-
case arg
|
262
|
-
when Hash
|
263
|
-
arg.each_with_object({}) do |(k, v), hash|
|
264
|
-
hash[yield k] = [*v]
|
265
|
-
end
|
266
|
-
else
|
267
|
-
map_arg(arg, &block)
|
268
|
-
end
|
269
|
-
end
|
270
|
-
|
271
275
|
def map_arg(arg, &block)
|
272
276
|
return nil unless arg
|
273
277
|
|
274
278
|
Array(arg).map(&block)
|
275
279
|
end
|
276
280
|
|
277
|
-
def map_days(arg)
|
278
|
-
map_arg(arg) { |d| day_number!(d) }
|
279
|
-
end
|
280
|
-
|
281
|
-
def map_mdays(arg)
|
282
|
-
map_arg(arg) { |d| assert_mday(d) }
|
283
|
-
end
|
284
|
-
|
285
|
-
def map_ydays(arg)
|
286
|
-
map_arg(arg) { |d| assert_yday(d) }
|
287
|
-
end
|
288
|
-
|
289
281
|
def assert_hour(hour)
|
290
282
|
assert_range_includes(1..::Montrose::Utils::MAX_HOURS_IN_DAY, hour)
|
291
283
|
end
|
292
284
|
|
293
|
-
def assert_mday(mday)
|
294
|
-
assert_range_includes(1..::Montrose::Utils::MAX_DAYS_IN_MONTH, mday, :absolute)
|
295
|
-
end
|
296
|
-
|
297
|
-
def assert_yday(yday)
|
298
|
-
assert_range_includes(1..::Montrose::Utils::MAX_DAYS_IN_YEAR, yday, :absolute)
|
299
|
-
end
|
300
|
-
|
301
|
-
def assert_week(week)
|
302
|
-
assert_range_includes(1..::Montrose::Utils::MAX_WEEKS_IN_YEAR, week, :absolute)
|
303
|
-
end
|
304
|
-
|
305
285
|
def decompose_on_arg(arg)
|
306
286
|
case arg
|
307
287
|
when Hash
|
@@ -309,52 +289,45 @@ module Montrose
|
|
309
289
|
key, val = month_or_day(k)
|
310
290
|
result[key] = val
|
311
291
|
result[:mday] ||= []
|
312
|
-
result[:mday] +=
|
292
|
+
result[:mday] += Montrose::MonthDay.parse(v)
|
313
293
|
end
|
314
294
|
else
|
315
|
-
{
|
295
|
+
{day: Montrose::Day.parse(arg)}
|
316
296
|
end
|
317
297
|
end
|
318
298
|
|
319
299
|
def month_or_day(key)
|
320
|
-
month =
|
300
|
+
month = Montrose::Month.number(key)
|
321
301
|
return [:month, month] if month
|
322
302
|
|
323
|
-
day =
|
303
|
+
day = Montrose::Day.number(key)
|
324
304
|
return [:day, day] if day
|
325
305
|
|
326
|
-
|
306
|
+
raise ConfigurationError, "Did not recognize #{key} as a month or day"
|
327
307
|
end
|
328
308
|
|
329
309
|
def assert_range_includes(range, item, absolute = false)
|
330
310
|
test = absolute ? item.abs : item
|
331
|
-
|
311
|
+
raise ConfigurationError, "Out of range: #{range.inspect} does not include #{test}" unless range.include?(test)
|
332
312
|
|
333
313
|
item
|
334
314
|
end
|
335
315
|
|
336
|
-
def as_time_parts(arg)
|
337
|
-
return arg if arg.is_a?(Array)
|
338
|
-
|
339
|
-
time = as_time(arg)
|
340
|
-
[time.hour, time.min, time.sec]
|
341
|
-
end
|
342
|
-
|
343
316
|
def parse_frequency(input)
|
344
317
|
if input.respond_to?(:parts)
|
345
318
|
frequency, interval = duration_to_frequency_parts(input)
|
346
|
-
{
|
319
|
+
{every: frequency.to_s.singularize.to_sym, interval: interval}
|
347
320
|
elsif input.is_a?(Numeric)
|
348
321
|
frequency, interval = numeric_to_frequency_parts(input)
|
349
|
-
{
|
322
|
+
{every: frequency, interval: interval}
|
350
323
|
else
|
351
|
-
{
|
324
|
+
{every: Frequency.assert(input)}
|
352
325
|
end
|
353
326
|
end
|
354
327
|
|
355
328
|
def numeric_to_frequency_parts(number)
|
356
329
|
parts = nil
|
357
|
-
[
|
330
|
+
%i[year month week day hour minute].each do |freq|
|
358
331
|
div, mod = number.divmod(1.send(freq))
|
359
332
|
parts = [freq, div]
|
360
333
|
return parts if mod.zero?
|
@@ -366,15 +339,36 @@ module Montrose
|
|
366
339
|
duration.parts.first
|
367
340
|
end
|
368
341
|
|
369
|
-
def decompose_during_arg(
|
370
|
-
case
|
342
|
+
def decompose_during_arg(during_arg)
|
343
|
+
case during_arg
|
344
|
+
when Range
|
345
|
+
[decompose_during_parts(during_arg)]
|
346
|
+
else
|
347
|
+
map_arg(during_arg) { |d| decompose_during_parts(d) } || []
|
348
|
+
end
|
349
|
+
end
|
350
|
+
|
351
|
+
def decompose_during_parts(during_parts)
|
352
|
+
case during_parts
|
371
353
|
when Range
|
372
|
-
[
|
354
|
+
decompose_during_parts([during_parts.first, during_parts.last])
|
373
355
|
when String
|
374
|
-
|
356
|
+
decompose_during_parts(during_parts.split(/[-—–]/))
|
375
357
|
else
|
376
|
-
|
358
|
+
during_parts.map { |parts| time_of_day_parse(parts) }
|
377
359
|
end
|
378
360
|
end
|
361
|
+
|
362
|
+
def time_of_day_parse(time_parts)
|
363
|
+
::Montrose::TimeOfDay.parse(time_parts)
|
364
|
+
end
|
365
|
+
|
366
|
+
def end_of_day
|
367
|
+
@end_of_day ||= time_of_day_parse(Time.now.end_of_day)
|
368
|
+
end
|
369
|
+
|
370
|
+
def beginning_of_day
|
371
|
+
@beginning_of_day ||= time_of_day_parse(Time.now.beginning_of_day)
|
372
|
+
end
|
379
373
|
end
|
380
374
|
end
|