montrose 0.10.1 → 0.11.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c6375eeb97c6ac8c0ea36b6be0b5281b4ffeceb5fa6fa591f4aab7c801240fcc
4
- data.tar.gz: 903145e001fb0ddd6a4dff5f7cf9a9e0583e9d2242a1b00283e1a3de3d5a03ce
3
+ metadata.gz: 01fee4660a2a1e95d60de269d491675460289ae52f6c582aee5ea6be39a883cc
4
+ data.tar.gz: ece2fbd3f36909337abd6712025053385f6ceedf95695a7852b6e05b6c804906
5
5
  SHA512:
6
- metadata.gz: 9598d8bc00e29c9adbf8b1d9323dcd02a2dc7aa54ae6d6a25ccc61ab5ffdc9dc1f0c8a390732bf6a1a8181b287fbee5a11c2c4b399b6cc81871276c374391baf
7
- data.tar.gz: 74813535d8ec8248ee87e0ce2aa500a3e85722a0ad06b310b8de62d50fadc1819cd914046b04c4a0bd9fc6e76217de62510fa9a7af60ebe63d80c2b7e9fdd63b
6
+ metadata.gz: bb823d11097cd9228ec7631809bece2b1028adf728c7a4919284dfc7a6fcafa3d65c9d1ed446f60f1a16335143830ae36b4725c6d980b5cb6a152b80961f4e0b
7
+ data.tar.gz: deb0db72983faf47192095aef16e7512f928c4ff4f1f6fd20f913394850b354fc2e004dcda70455d08abe4471a2647d8b3a31832814f737cf68a09d6d9ffbaa8
@@ -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
- :betweeen option.
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
 
@@ -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 }
@@ -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
@@ -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
@@ -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)
@@ -14,6 +14,7 @@ module Montrose
14
14
  Rule::After,
15
15
  Rule::Until,
16
16
  Rule::Between,
17
+ Rule::During,
17
18
  Rule::Except,
18
19
  Rule::Total,
19
20
  Rule::TimeOfDay,
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Montrose
4
- VERSION = "0.10.1"
4
+ VERSION = "0.11.0"
5
5
  end
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.10.1
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-07-22 00:00:00.000000000 Z
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
- rubygems_version: 3.0.3
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