montrose 0.10.1 → 0.11.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/CHANGELOG.md +13 -1
- data/README.md +50 -0
- data/lib/montrose/chainable.rb +17 -0
- data/lib/montrose/options.rb +21 -0
- data/lib/montrose/rule.rb +1 -0
- data/lib/montrose/rule/during.rb +55 -0
- data/lib/montrose/schedule.rb +59 -0
- data/lib/montrose/stack.rb +1 -0
- data/lib/montrose/version.rb +1 -1
- metadata +5 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 01fee4660a2a1e95d60de269d491675460289ae52f6c582aee5ea6be39a883cc
|
4
|
+
data.tar.gz: ece2fbd3f36909337abd6712025053385f6ceedf95695a7852b6e05b6c804906
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: bb823d11097cd9228ec7631809bece2b1028adf728c7a4919284dfc7a6fcafa3d65c9d1ed446f60f1a16335143830ae36b4725c6d980b5cb6a152b80961f4e0b
|
7
|
+
data.tar.gz: deb0db72983faf47192095aef16e7512f928c4ff4f1f6fd20f913394850b354fc2e004dcda70455d08abe4471a2647d8b3a31832814f737cf68a09d6d9ffbaa8
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,15 @@
|
|
1
|
+
### 0.11.0 - (2019-08-16)
|
2
|
+
|
3
|
+
* enhancements
|
4
|
+
* Adds `Recurrence#during` to support recurrences within time-of-day ranges,
|
5
|
+
e.g. `Montrose.every(20.minutes).during("9am-5pm")`
|
6
|
+
|
7
|
+
### 0.10.1 - (2019-07-22)
|
8
|
+
|
9
|
+
* enhancements
|
10
|
+
* Adds `Schedule.dump` and `Schedule.load` to support ActiveRecord column
|
11
|
+
serialization
|
12
|
+
|
1
13
|
### 0.10.0 - (2019-07-17)
|
2
14
|
|
3
15
|
* enhancements
|
@@ -61,7 +73,7 @@
|
|
61
73
|
* Previously, the :between option served as a shorthand for :starts to :until.
|
62
74
|
Now, when both :starts and :between are provided, the recurrence will behave
|
63
75
|
as if anchored by the given :starts option, but filtered through the given
|
64
|
-
:
|
76
|
+
:between option.
|
65
77
|
* The :exclude_end option changes the default behavior of :until--when the
|
66
78
|
timestamp of the interval matches the :until timestamp, it will be included
|
67
79
|
by default unless the :exclude_end option is set to true.
|
data/README.md
CHANGED
@@ -277,6 +277,15 @@ Montrose.every(90.minutes, total: 4)
|
|
277
277
|
# every 20 minutes from 9:00 AM to 4:40 PM every day
|
278
278
|
Montrose.every(20.minutes, hour: 9..16)
|
279
279
|
|
280
|
+
# every 20 minutes from 9:00 AM to 4:40 PM every day with time-of-day precision
|
281
|
+
r = Montrose.every(20.minutes)
|
282
|
+
r.during("9am-4:40pm") # as semantic time-of-day range OR
|
283
|
+
r.during(time.change(hour: 9)..time.change(hour: 4: min: 40)) # as ruby time range OR
|
284
|
+
r.during([9, 0, 0], [16, 40, 0]) # as hour, min, sec tuple pairs for start, end
|
285
|
+
|
286
|
+
# every 20 minutes during multiple time-of-day ranges
|
287
|
+
Montrose.every(20.minutes).during("9am-12pm", "1pm-5pm")
|
288
|
+
|
280
289
|
# Minutely
|
281
290
|
Montrose.minutely
|
282
291
|
Montrose.r(every: :minute)
|
@@ -366,6 +375,40 @@ r.events.take(10).each { |date| puts date.to_s }
|
|
366
375
|
r.events.lazy.select { |time| time > 1.month.from_now }.take(3).each { |date| puts date.to_s }
|
367
376
|
```
|
368
377
|
|
378
|
+
### Combining recurrences
|
379
|
+
|
380
|
+
It may be necessary to combine several recurrence rules into a single
|
381
|
+
enumeration of events. For this purpose, there is `Montrose::Schedule`. To create a schedule of multiple recurrences:
|
382
|
+
|
383
|
+
```ruby
|
384
|
+
recurrence_1 = Montrose.monthly(day: { friday: [1] })
|
385
|
+
recurrence_2 = Montrose.weekly(on: :tuesday)
|
386
|
+
|
387
|
+
schedule = Montrose::Schedule.build do |s|
|
388
|
+
s << recurrence_1
|
389
|
+
s << recurrence_2
|
390
|
+
end
|
391
|
+
|
392
|
+
# add after building
|
393
|
+
s << Montrose.yearly
|
394
|
+
```
|
395
|
+
The `Schedule#<<` method also accepts valid recurrence options as hashes:
|
396
|
+
```ruby
|
397
|
+
schedule = Montrose::Schedule.build do |s|
|
398
|
+
s << { day: { friday: [1] } }
|
399
|
+
s << { on: :tuesday }
|
400
|
+
end
|
401
|
+
```
|
402
|
+
A schedule acts like a collection of recurrence rules that also behaves as a single
|
403
|
+
stream of events:
|
404
|
+
|
405
|
+
```ruby
|
406
|
+
schedule.events # => #<Enumerator: ...>
|
407
|
+
schedule.each do |event|
|
408
|
+
puts event
|
409
|
+
end
|
410
|
+
```
|
411
|
+
|
369
412
|
### Ruby on Rails
|
370
413
|
|
371
414
|
Instances of `Montrose::Recurrence` support the ActiveRecord serialization API so recurrence objects can be marshalled to and from a single database column:
|
@@ -374,6 +417,13 @@ Instances of `Montrose::Recurrence` support the ActiveRecord serialization API s
|
|
374
417
|
class RecurringEvent < ApplicationRecord
|
375
418
|
serialize :recurrence, Montrose::Recurrence
|
376
419
|
|
420
|
+
end
|
421
|
+
```
|
422
|
+
`Montrose::Schedule` can also be serialized:
|
423
|
+
```ruby
|
424
|
+
class RecurringEvent < ApplicationRecord
|
425
|
+
serialize :recurrence, Montrose::Schedule
|
426
|
+
|
377
427
|
end
|
378
428
|
```
|
379
429
|
|
data/lib/montrose/chainable.rb
CHANGED
@@ -168,6 +168,23 @@ module Montrose
|
|
168
168
|
merge(between: date_range)
|
169
169
|
end
|
170
170
|
|
171
|
+
# Create a recurrence occurring within a time-of-day range or ranges.
|
172
|
+
# Given time ranges will parse as times-of-day and ignore given dates.
|
173
|
+
#
|
174
|
+
# @param [Range<Time>,String,Array<Array>] time-of-day range(s)
|
175
|
+
#
|
176
|
+
# @example
|
177
|
+
# Montrose.every(20.minutes).during("9am-5pm")
|
178
|
+
# Montrose.every(20.minutes).during(time.change(hour: 9)..time.change(hour: 5))
|
179
|
+
# Montrose.every(20.minutes).during([9, 0, 0], [17, 0, 0])
|
180
|
+
# Montrose.every(20.minutes).during("9am-12pm", "1pm-5pm")
|
181
|
+
#
|
182
|
+
# @return [Montrose::Recurrence]
|
183
|
+
#
|
184
|
+
def during(time_of_day, *extras)
|
185
|
+
merge(during: time_of_day.array_concat(extras))
|
186
|
+
end
|
187
|
+
|
171
188
|
# Create a recurrence through :on option
|
172
189
|
#
|
173
190
|
# @param day [Hash,Symbol] weekday or day of month as hash, e.g. { friday: 13 }
|
data/lib/montrose/options.rb
CHANGED
@@ -86,6 +86,7 @@ module Montrose
|
|
86
86
|
def_option :starts
|
87
87
|
def_option :until
|
88
88
|
def_option :between
|
89
|
+
def_option :during
|
89
90
|
def_option :hour
|
90
91
|
def_option :day
|
91
92
|
def_option :mday
|
@@ -179,6 +180,15 @@ module Montrose
|
|
179
180
|
@hour = map_arg(hours) { |h| assert_hour(h) }
|
180
181
|
end
|
181
182
|
|
183
|
+
def during=(during)
|
184
|
+
@during = case during
|
185
|
+
when Range
|
186
|
+
[decompose_during_arg(during)]
|
187
|
+
else
|
188
|
+
map_arg(during) { |d| decompose_during_arg(d) }
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
182
192
|
def day=(days)
|
183
193
|
@day = nested_map_arg(days) { |d| day_number!(d) }
|
184
194
|
end
|
@@ -355,5 +365,16 @@ module Montrose
|
|
355
365
|
def duration_to_frequency_parts(duration)
|
356
366
|
duration.parts.first
|
357
367
|
end
|
368
|
+
|
369
|
+
def decompose_during_arg(during)
|
370
|
+
case during
|
371
|
+
when Range
|
372
|
+
[decompose_during_arg(during.first), decompose_during_arg(during.last)]
|
373
|
+
when String
|
374
|
+
during.split(%r{[-—–]}).map { |d| as_time_parts(d) }
|
375
|
+
else
|
376
|
+
as_time_parts(during)
|
377
|
+
end
|
378
|
+
end
|
358
379
|
end
|
359
380
|
end
|
data/lib/montrose/rule.rb
CHANGED
@@ -40,6 +40,7 @@ require "montrose/rule/between"
|
|
40
40
|
require "montrose/rule/day_of_month"
|
41
41
|
require "montrose/rule/day_of_week"
|
42
42
|
require "montrose/rule/day_of_year"
|
43
|
+
require "montrose/rule/during"
|
43
44
|
require "montrose/rule/except"
|
44
45
|
require "montrose/rule/hour_of_day"
|
45
46
|
require "montrose/rule/month_of_year"
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Montrose
|
4
|
+
module Rule
|
5
|
+
class During
|
6
|
+
include Montrose::Rule
|
7
|
+
|
8
|
+
def self.apply_options(opts)
|
9
|
+
opts[:during]
|
10
|
+
end
|
11
|
+
|
12
|
+
# Initializes rule
|
13
|
+
#
|
14
|
+
# @param during [Array<Array<Fixnum>>] array of time parts arrays, e.g. [[9, 0, 0], [17, 0, 0]], i.e., "9 to 5"
|
15
|
+
#
|
16
|
+
def initialize(during)
|
17
|
+
@during = during.map { |first, last| TimeOfDayRange.new(first, last) }
|
18
|
+
end
|
19
|
+
|
20
|
+
def include?(time)
|
21
|
+
@during.any? { |range| range.include?(time) }
|
22
|
+
end
|
23
|
+
|
24
|
+
class TimeOfDay
|
25
|
+
def initialize(hour, min, sec)
|
26
|
+
@hour = hour
|
27
|
+
@min = min
|
28
|
+
@sec = sec
|
29
|
+
end
|
30
|
+
|
31
|
+
def seconds_since_midnight
|
32
|
+
@seconds_since_midnight ||= (@hour * 60 * 60) + (@min * 60) + @sec
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
class TimeOfDayRange
|
37
|
+
def initialize(first, last, exclude_end: false)
|
38
|
+
@first = TimeOfDay.new(*first)
|
39
|
+
@last = TimeOfDay.new(*last)
|
40
|
+
@exclude_end = exclude_end
|
41
|
+
end
|
42
|
+
|
43
|
+
def include?(time)
|
44
|
+
range.include?(time.seconds_since_midnight.to_i)
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
def range
|
50
|
+
@range ||= Range.new(@first.seconds_since_midnight, @last.seconds_since_midnight, @exclude_end)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
data/lib/montrose/schedule.rb
CHANGED
@@ -8,6 +8,8 @@ module Montrose
|
|
8
8
|
# @attr_reader [Array] rules the list of recurrences
|
9
9
|
#
|
10
10
|
class Schedule
|
11
|
+
include Enumerable
|
12
|
+
|
11
13
|
attr_accessor :rules
|
12
14
|
|
13
15
|
class << self
|
@@ -86,6 +88,31 @@ module Montrose
|
|
86
88
|
@rules.any? { |r| r.include?(timestamp) }
|
87
89
|
end
|
88
90
|
|
91
|
+
# Iterate over the events of a recurrence. Along with the Enumerable
|
92
|
+
# module, this makes Montrose occurrences enumerable like other Ruby
|
93
|
+
# collections
|
94
|
+
#
|
95
|
+
# @example Iterate over a finite recurrence
|
96
|
+
# schedule = Montrose::Schedule.build do |s|
|
97
|
+
# s << { every: :day }
|
98
|
+
# end
|
99
|
+
# schedule.each do |event|
|
100
|
+
# puts event
|
101
|
+
# end
|
102
|
+
#
|
103
|
+
# @example Iterate over an infinite recurrence
|
104
|
+
# schedule = Montrose::Schedule.build do |s|
|
105
|
+
# s << { every: :day }
|
106
|
+
# end
|
107
|
+
# schedule.lazy.each do |event|
|
108
|
+
# puts event
|
109
|
+
# end
|
110
|
+
#
|
111
|
+
# @return [Enumerator] an enumerator of recurrence timestamps
|
112
|
+
def each(&block)
|
113
|
+
events.each(&block)
|
114
|
+
end
|
115
|
+
|
89
116
|
# Returns an enumerator for iterating over timestamps in the schedule
|
90
117
|
#
|
91
118
|
# @example Return the events
|
@@ -106,10 +133,42 @@ module Montrose
|
|
106
133
|
end
|
107
134
|
end
|
108
135
|
|
136
|
+
# Returns an array of the options used to create the recurrence
|
137
|
+
#
|
138
|
+
# @return [Array] array of hashes of recurrence options
|
139
|
+
#
|
109
140
|
def to_a
|
110
141
|
@rules.map(&:to_hash)
|
111
142
|
end
|
112
143
|
|
144
|
+
# Returns json string of options used to create the schedule
|
145
|
+
#
|
146
|
+
# @return [String] json of schedule recurrences
|
147
|
+
#
|
148
|
+
def to_json(*args)
|
149
|
+
JSON.dump(to_a, *args)
|
150
|
+
end
|
151
|
+
|
152
|
+
# Returns json array of options used to create the schedule
|
153
|
+
#
|
154
|
+
# @return [Array] json of schedule recurrence options
|
155
|
+
#
|
156
|
+
def as_json(*args)
|
157
|
+
to_a.as_json(*args)
|
158
|
+
end
|
159
|
+
|
160
|
+
# Returns options used to create the schedule recurrences in YAML format
|
161
|
+
#
|
162
|
+
# @return [String] YAML-formatted schedule recurrence options
|
163
|
+
#
|
164
|
+
def to_yaml(*args)
|
165
|
+
YAML.dump(JSON.parse(to_json(*args)))
|
166
|
+
end
|
167
|
+
|
168
|
+
def inspect
|
169
|
+
"#<#{self.class}:#{object_id.to_s(16)} #{to_a.inspect}>"
|
170
|
+
end
|
171
|
+
|
113
172
|
private
|
114
173
|
|
115
174
|
def active_enums(enums)
|
data/lib/montrose/stack.rb
CHANGED
data/lib/montrose/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: montrose
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.11.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ross Kaffenberger
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2019-
|
11
|
+
date: 2019-08-16 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -169,6 +169,7 @@ files:
|
|
169
169
|
- lib/montrose/rule/day_of_month.rb
|
170
170
|
- lib/montrose/rule/day_of_week.rb
|
171
171
|
- lib/montrose/rule/day_of_year.rb
|
172
|
+
- lib/montrose/rule/during.rb
|
172
173
|
- lib/montrose/rule/except.rb
|
173
174
|
- lib/montrose/rule/hour_of_day.rb
|
174
175
|
- lib/montrose/rule/month_of_year.rb
|
@@ -203,7 +204,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
203
204
|
- !ruby/object:Gem::Version
|
204
205
|
version: '0'
|
205
206
|
requirements: []
|
206
|
-
|
207
|
+
rubyforge_project:
|
208
|
+
rubygems_version: 2.7.6
|
207
209
|
signing_key:
|
208
210
|
specification_version: 4
|
209
211
|
summary: Recurring events in Ruby
|