montrose 0.4.3 → 0.5.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 2624845de532b2888ed205eeb7e92be0e44475c2
4
- data.tar.gz: 0be802540d67f55b0b930a57202ee44683718bf1
3
+ metadata.gz: e2af0b4abedbea2421c73f457fcdbd66390870f1
4
+ data.tar.gz: dafba1cbbd0f14757bc95ffe81764a065947e2fa
5
5
  SHA512:
6
- metadata.gz: ac9b40468a9a9f00775f6d2041e1e98d6df31df6512a66f6870f43776f7dbbebc1f17c3901bcdfbae3dafd30761131601cbe42d9d709a042ac22db52dd57612f
7
- data.tar.gz: 6c49f3532b8a7b49654fa2a7f4ca217394d9e03d3b315ce015f23ff7652d28f180172103b3e75dd079bb47ba064946811ee8b0c9d086b5411a0a7b7c85bd5049
6
+ metadata.gz: f3c9e25c8f3e30cf8df3037e653f3c515ea25c3bf9d144da520a4e4a5e043a8a51c8b8eefe449d4c83ac0b87ab47d5a0c0e14a58e875846a1d9995a9472501c0
7
+ data.tar.gz: fb805c48e5e6d9880e6c3c091ea738bf2b6f4e21a59afa78cf3c467ad062cdf12f5802f46078138fbde9e74030f6d3893a83fee5877fc527b1c66da38c609312
@@ -112,3 +112,9 @@ Style/RegexpLiteral:
112
112
 
113
113
  Style/DotPosition:
114
114
  EnforcedStyle: trailing
115
+
116
+ Style/VariableNumber:
117
+ Enabled: false
118
+
119
+ Style/FormatString:
120
+ Enabled: false
@@ -12,6 +12,6 @@ matrix:
12
12
  fast_finish: true
13
13
  rvm:
14
14
  - 2.1.10
15
- - 2.2.5
16
- - 2.3.1
15
+ - 2.2.6
16
+ - 2.3.3
17
17
  sudo: false
@@ -1,4 +1,10 @@
1
- ### 0.4.3 - (2016-07-27)
1
+ ### 0.5.0 - (2016-11-23)
2
+
3
+ * enhancements
4
+ * Adds `Recurrence#include?`
5
+ * Improved documentation
6
+
7
+ ### 0.4.3 - (2016-11-20)
2
8
 
3
9
  * enhancements
4
10
  * Add CI support for ActiveSupport 4.1, 4.2, 5.0 (by @phlipper)
data/README.md CHANGED
@@ -351,9 +351,6 @@ Montrose.yearly(yday: [1, 100]) # yearly on the 1st and 100th day of year
351
351
  Montrose.yearly(on: { january: 31 })
352
352
  Montrose.r(every: :year, on: { 10 => 31 }, interval: 3)
353
353
 
354
- Montrose.daily(:day, except: "2017-01-31")
355
- Montrose.daily(except: [Date.today, "2017-01-31"])
356
-
357
354
  # Chaining
358
355
  Montrose.weekly.starting(3.weeks.from_now).on(:friday)
359
356
  Montrose.every(:day).at("4:05pm")
data/Rakefile CHANGED
@@ -20,6 +20,7 @@ task default: [:spec, :rubocop]
20
20
  namespace :doc do
21
21
  desc "Generate docs and publish to gh-pages"
22
22
  task :publish do
23
+ puts "Generating docs"
23
24
  require "fileutils"
24
25
  sh "yard doc"
25
26
  sh "git checkout gh-pages"
@@ -5,11 +5,216 @@ require "montrose/stack"
5
5
  require "montrose/clock"
6
6
 
7
7
  module Montrose
8
+ # Represents the rules for a set of recurring events. Can be instantiated
9
+ # and extended in a variety of ways as show in the examples below.
10
+ #
11
+ # @author Ross Kaffenberger
12
+ # @since 0.0.1
13
+ # @attr_reader [Montrose::Options] default_options contains the recurrence rules in hash-like format
14
+ #
15
+ # @example a new recurrence
16
+ # Montrose.r
17
+ # Montrose.recurrence
18
+ # Montrose::Recurrence.new
19
+ #
20
+ # @example daily for 10 occurrences
21
+ # Montrose.daily(total: 10)
22
+ #
23
+ # @example daily until December 23, 2015
24
+ # starts = Date.new(2015, 1, 1)
25
+ # ends = Date.new(2015, 12, 23)
26
+ # Montrose.daily(starts: starts, until: ends)
27
+ #
28
+ # @example every other day forever
29
+ # Montrose.daily(interval: 2)
30
+ #
31
+ # @example every 10 days 5 occurrences
32
+ # Montrose.every(10.days, total: 5)
33
+ #
34
+ # @example everyday in January for 3 years
35
+ # starts = Time.current.beginning_of_year
36
+ # ends = Time.current.end_of_year + 2.years
37
+ # Montrose.daily(month: :january, between: starts...ends)
38
+ #
39
+ # @example weekly for 10 occurrences
40
+ # Montrose.weekly(total: 10)
41
+ #
42
+ # @example weekly until December 23, 2015
43
+ # ends_on = Date.new(2015, 12, 23)
44
+ # starts_on = ends_on - 15.weeks
45
+ # Montrose.every(:week, until: ends_on, starts: starts_on)
46
+ #
47
+ # @example every other week forever
48
+ # Montrose.every(2.weeks)
49
+ #
50
+ # @example weekly on Tuesday and Thursday for five weeks from September 1, 2015 until October 5, 2015
51
+ # Montrose.weekly(on: [:tuesday, :thursday],
52
+ # between: Date.new(2015, 9, 1)..Date.new(2015, 10, 5))
53
+ #
54
+ # @example every other week on Mon, Wed and Fri until December 23 2015, but starting on Tuesday, September 1, 2015
55
+ # Montrose.every(2.weeks,
56
+ # on: [:monday, :wednesday, :friday],
57
+ # starts: Date.new(2015, 9, 1))
58
+ #
59
+ # @example every other week on Tuesday and Thursday, for 8 occurrences
60
+ # Montrose.weekly(on: [:tuesday, :thursday], total: 8, interval: 2)
61
+ #
62
+ # @example monthly on the first Friday for ten occurrences
63
+ # Montrose.monthly(day: { friday: [1] }, total: 10)
64
+ #
65
+ # @example monthly on the first Friday until December 23, 2015
66
+ # Montrose.every(:month, day: { friday: [1] }, until: Date.new(2016, 12, 23))
67
+ #
68
+ # @example every other month on the first and last Sunday of the month for 10 occurrences
69
+ # Montrose.every(:month, day: { sunday: [1, -1] }, interval: 2, total: 10)
70
+ #
71
+ # @example monthly on the second-to-last Monday of the month for 6 months
72
+ # Montrose.every(:month, day: { monday: [-2] }, total: 6)
73
+ #
74
+ # @example monthly on the third-to-the-last day of the month, forever
75
+ # Montrose.every(:month, mday: [-3])
76
+ #
77
+ # @example monthly on the 2nd and 15th of the month for 10 occurrences
78
+ # Montrose.every(:month, mday: [2, 15], total: 10)
79
+ #
80
+ # @example monthly on the first and last day of the month for 10 occurrences
81
+ # Montrose.monthly(mday: [1, -1], total: 10)
82
+ #
83
+ # @example every 18 months on the 10th thru 15th of the month for 10 occurrences
84
+ # Montrose.every(18.months, total: 10, mday: 10..15)
85
+ #
86
+ # @example every Tuesday, every other month
87
+ # Montrose.every(2.months, on: :tuesday)
88
+ #
89
+ # @example yearly in June and July for 10 occurrences
90
+ # Montrose.yearly(month: [:june, :july], total: 10)
91
+ #
92
+ # @example every other year on January, February, and March for 10 occurrences
93
+ # Montrose.every(2.years, month: [:january, :february, :march], total: 10)
94
+ #
95
+ # @example every third year on the 1st, 100th and 200th day for 10 occurrences
96
+ # Montrose.yearly(yday: [1, 100, 200], total: 10)
97
+ #
98
+ # @example every 20th Monday of the year, forever
99
+ # Montrose.yearly(day: { monday: [20] })
100
+ #
101
+ # @example Monday of week number 20 forever
102
+ # Montrose.yearly(week: [20], on: :monday)
103
+ #
104
+ # @example every Thursday in March, forever
105
+ # Montrose.monthly(month: :march, on: :thursday, at: "12 pm")
106
+ #
107
+ # @example every Thursday, but only during June, July, and August, forever" do
108
+ # Montrose.monthly(month: 6..8, on: :thursday)
109
+ #
110
+ # @example every Friday 13th, forever
111
+ # Montrose.monthly(on: { friday: 13 })
112
+ #
113
+ # @example first Saturday that follows the first Sunday of the month, forever
114
+ # Montrose.monthly(on: { saturday: 7..13 })
115
+ #
116
+ # @example every four years, the first Tuesday after a Monday in November, forever (U.S. Presidential Election day)
117
+ # Montrose.every(4.years, month: :november, on: { tuesday: 2..8 })
118
+ #
119
+ # @example every 3 hours from 9:00 AM to 5:00 PM on a specific day
120
+ # date = Date.new(2016, 9, 1)
121
+ # Montrose.hourly(between: date..(date+1), hour: 9..17, interval: 3)
122
+ #
123
+ # @example every 15 minutes for 6 occurrences
124
+ # Montrose.every(90.minutes, total: 6)
125
+ #
126
+ # @example every hour and a half for four occurrences
127
+ # Montrose.every(90.minutes, total: 4)
128
+ #
129
+ # @example every 20 minutes from 9:00 AM to 4:40 PM every day
130
+ # Montrose.every(20.minutes, hour: 9..16)
131
+ #
132
+ # @example Minutely
133
+ # Montrose.minutely
134
+ # Montrose.r(every: :minute)
135
+ # Montrose.every(10.minutes)
136
+ # Montrose.r(every: 10.minutes)
137
+ # Montrose.r(every: :minute, interval: 10) # every 10 minutes
138
+ # Montrose.minutely(until: "9:00 PM")
139
+ # Montrose.r(every: :minute, until: "9:00 PM")
140
+ #
141
+ # @example Daily
142
+ # Montrose.daily
143
+ # Montrose.every(:day)
144
+ # Montrose.r(every: :day)
145
+ # Montrose.every(9.days)
146
+ # Montrose.r(every: 9.days)
147
+ # Montrose.r(every: :day, interval: 9)
148
+ # Montrose.daily(at: "9:00 AM")
149
+ # Montrose.every(:day, at: "9:00 AM")
150
+ # Montrose.r(every: :day, at: "9:00 AM")
151
+ # Montrose.daily(total: 7)
152
+ # Montrose.every(:day, total: 7)
153
+ # Montrose.r(every: :day, total: 7)
154
+ #
155
+ # @example Weekly
156
+ # Montrose.weekly
157
+ # Montrose.every(:week)
158
+ # Montrose.r(every: :week)
159
+ # Montrose.every(:week, on: :monday)
160
+ # Montrose.every(:week, on: [:monday, :wednesday, :friday])
161
+ # Montrose.every(2.weeks, on: :friday)
162
+ # Montrose.every(:week, on: :friday, at: "3:41 PM")
163
+ # Montrose.weekly(on: :thursday)
164
+ #
165
+ # @example Monthly by month day
166
+ # Montrose.monthly(mday: 1) # first of the month
167
+ # Montrose.every(:month, mday: 1)
168
+ # Montrose.r(every: :month, mday: 1)
169
+ # Montrose.monthly(mday: [2, 15]) # 2nd and 15th of the month
170
+ # Montrose.monthly(mday: -3) # third-to-last day of the month
171
+ # Montrose.monthly(mday: 10..15) # 10th through the 15th day of the month
172
+ #
173
+ # @example Monthly by week day
174
+ # Montrose.monthly(day: :friday, interval: 2) # every Friday every other month
175
+ # Montrose.every(:month, day: :friday, interval: 2)
176
+ # Montrose.r(every: :month, day: :friday, interval: 2)
177
+ # Montrose.monthly(day: { friday: [1] }) # 1st Friday of the month
178
+ # Montrose.monthly(day: { Sunday: [1, -1] }) # first and last Sunday of the month
179
+ # Montrose.monthly(mday: 7..13, day: :saturday) # first Saturday that follow the first Sunday of the month
180
+ #
181
+ # @example Yearly
182
+ # Montrose.yearly
183
+ # Montrose.every(:year)
184
+ # Montrose.r(every: :year)
185
+ # Montrose.yearly(month: [:june, :july]) # yearly in June and July
186
+ # Montrose.yearly(month: 6..8, day: :thursday) # yearly in June, July, August on Thursday
187
+ # Montrose.yearly(yday: [1, 100]) # yearly on the 1st and 100th day of year
188
+ # Montrose.yearly(on: { january: 31 })
189
+ # Montrose.r(every: :year, on: { 10 => 31 }, interval: 3)
190
+ #
191
+ # @example Chaining
192
+ # Montrose.weekly.starting(3.weeks.from_now).on(:friday)
193
+ # Montrose.every(:day).at("4:05pm")
194
+ # Montrose.yearly.between(Time.current..10.years.from_now)
195
+ #
196
+ # @example Enumerating events
197
+ # r = Montrose.every(:month, mday: 31, until: "January 1, 2017")
198
+ # r.each { |time| puts time.to_s }
199
+ # r.take(10).to_a
200
+ #
201
+ # @example Merging rules
202
+ # r.merge(starts: "2017-01-01").each { |time| puts time.to_s }
203
+ #
204
+ # @example Using #events Enumerator
205
+ # r.events # => #<Enumerator: ...>
206
+ # r.events.take(10).each { |date| puts date.to_s }
207
+ # r.events.lazy.select { |time| time > 1.month.from_now }.take(3).each { |date| puts date.to_s }
208
+ #
8
209
  class Recurrence
210
+ extend Forwardable
9
211
  include Chainable
10
212
  include Enumerable
11
213
 
12
214
  attr_reader :default_options
215
+ def_delegator :@default_options, :total, :length
216
+ def_delegator :@default_options, :starts, :starts_at
217
+ def_delegator :@default_options, :until, :ends_at
13
218
 
14
219
  class << self
15
220
  def new(options = {})
@@ -28,7 +233,7 @@ module Montrose
28
233
  end
29
234
 
30
235
  def load(json)
31
- new JSON.load(json)
236
+ new JSON.parse(json)
32
237
  end
33
238
  end
34
239
 
@@ -36,24 +241,52 @@ module Montrose
36
241
  @default_options = Montrose::Options.new(opts)
37
242
  end
38
243
 
39
- # Returns an enumerator for iterating over
40
- # timestamps in the recurrence
244
+ # Returns an enumerator for iterating over timestamps in the recurrence
41
245
  #
42
- # @example
246
+ # @example Return the events
247
+ # recurrence = Montrose.recurrence(every: :day)
43
248
  # recurrence.events
44
249
  #
45
- # @return [Enumerator] a enumerator of recurrence timestamps
250
+ # @example Iterate over a finite recurrence
251
+ # recurrence = Montrose.recurrence(every: :day, until: 1.year.from_now)
252
+ # recurrence.events.each do |event|
253
+ # puts event
254
+ # end
255
+ #
256
+ # @example Iterate over an infinite recurrence
257
+ # recurrence = Montrose.recurrence(every: :day)
258
+ # recurrence.events.lazy.each do |event|
259
+ # puts event
260
+ # end
261
+ #
262
+ # @return [Enumerator] an enumerator of recurrence timestamps
46
263
  #
47
264
  def events
48
265
  event_enum
49
266
  end
50
267
 
268
+ # Iterate over the events of a recurrence. Along with the Enumerable
269
+ # module, this makes Montrose occurrences enumerable like other Ruby
270
+ # collections
271
+ #
272
+ # @example Iterate over a finite recurrence
273
+ # recurrence = Montrose.recurrence(every: :day, until: 1.year.from_now)
274
+ # recurrence.each do |event|
275
+ # puts event
276
+ # end
277
+ #
278
+ # @example Iterate over an infinite recurrence
279
+ # recurrence = Montrose.recurrence(every: :day)
280
+ # recurrence.lazy.each do |event|
281
+ # puts event
282
+ # end
283
+ #
284
+ # @return [Enumerator] an enumerator of recurrence timestamps
51
285
  def each(&block)
52
286
  events.each(&block)
53
287
  end
54
288
 
55
- # Returns a hash of the options used to create
56
- # the recurrence
289
+ # Returns a hash of the options used to create the recurrence
57
290
  #
58
291
  # @return [Hash] hash of recurrence options
59
292
  #
@@ -62,8 +295,7 @@ module Montrose
62
295
  end
63
296
  alias to_h to_hash
64
297
 
65
- # Returns json string of options used to create
66
- # the recurrence
298
+ # Returns json string of options used to create the recurrence
67
299
  #
68
300
  # @return [String] json of recurrence options
69
301
  #
@@ -75,6 +307,48 @@ module Montrose
75
307
  "#<#{self.class}:#{object_id.to_s(16)} #{to_h.inspect}>"
76
308
  end
77
309
 
310
+ # Return true/false if given timestamp equals a timestamp given
311
+ # by the recurrence
312
+ #
313
+ # @return [Boolean] whether or not timestamp is included in recurrence
314
+ #
315
+ def include?(timestamp)
316
+ return false if earlier?(timestamp) || later?(timestamp)
317
+
318
+ recurrence = finite? ? self : starts(timestamp)
319
+
320
+ recurrence.events.lazy.each do |event|
321
+ return true if event == timestamp
322
+ return false if event > timestamp
323
+ end or false
324
+ end
325
+
326
+ # Return true/false if recurrence will iterate infinitely
327
+ #
328
+ # @return [Boolean] whether or not recurrence is infinite
329
+ #
330
+ def finite?
331
+ ends_at || length
332
+ end
333
+
334
+ # Return true/false if given timestamp occurs before
335
+ # the recurrence
336
+ #
337
+ # @return [Boolean] whether or not timestamp is earlier
338
+ #
339
+ def earlier?(timestamp)
340
+ starts_at && timestamp < starts_at
341
+ end
342
+
343
+ # Return true/false if given timestamp occurs after
344
+ # the recurrence
345
+ #
346
+ # @return [Boolean] whether or not timestamp is later
347
+ #
348
+ def later?(timestamp)
349
+ ends_at && timestamp > ends_at
350
+ end
351
+
78
352
  private
79
353
 
80
354
  def event_enum
@@ -1,7 +1,24 @@
1
1
  module Montrose
2
+ # A schedule represents a group of recurrences
3
+ #
4
+ # @author Ross Kaffenberger
5
+ # @since 0.0.1
6
+ # @attr_reader [Array] rules the list of recurrences
7
+ #
2
8
  class Schedule
3
9
  attr_accessor :rules
4
10
 
11
+ # Instantiates a schedule and yields the instance to an optional
12
+ # block for building recurrences inline
13
+ #
14
+ # @example Build a schedule with multiple rules added in the given block
15
+ # schedule = Montrose::Schedule.build do |s|
16
+ # s << { every: :day }
17
+ # s << { every: :year }
18
+ # end
19
+ #
20
+ # @return [Montrose::Schedule]
21
+ #
5
22
  def self.build
6
23
  schedule = new
7
24
  yield schedule if block_given?
@@ -12,6 +29,18 @@ module Montrose
12
29
  @rules = []
13
30
  end
14
31
 
32
+ # Add a recurrence rule to the schedule, either by hash or recurrence
33
+ # instance
34
+ #
35
+ # @example Add a recurrence by hash
36
+ # schedule = Montrose::Schedule.new
37
+ # schedule << { every: :day }
38
+ #
39
+ # @example Add a recurrence by instance
40
+ # schedule = Montrose::Schedule.new
41
+ # recurrence = Montrose.recurrence(every: :day)
42
+ # schedule << recurrence
43
+ #
15
44
  def <<(rule)
16
45
  @rules << Montrose::Recurrence.new(rule)
17
46
 
@@ -19,10 +48,25 @@ module Montrose
19
48
  end
20
49
  alias add <<
21
50
 
22
- def include?(time)
23
- @rules.any? { |r| r.include?(time) }
51
+ # Return true/false if given timestamp is included in any of the rules
52
+ # found in the schedule
53
+ #
54
+ # @return [Boolean] whether or not timestamp is included in schedule
55
+ #
56
+ def include?(timestamp)
57
+ @rules.any? { |r| r.include?(timestamp) }
24
58
  end
25
59
 
60
+ # Returns an enumerator for iterating over timestamps in the schedule
61
+ #
62
+ # @example Return the events
63
+ # schedule = Montrose::Schedule.build do |s|
64
+ # s << { every: :day }
65
+ # end
66
+ # schedule.events
67
+ #
68
+ # @return [Enumerator] an enumerator of recurrence timestamps
69
+ #
26
70
  def events(opts = {})
27
71
  enums = @rules.map { |r| r.merge(opts).events }
28
72
  Enumerator.new do |y|
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module Montrose
3
- VERSION = "0.4.3".freeze
3
+ VERSION = "0.5.0".freeze
4
4
  end
@@ -28,6 +28,6 @@ Gem::Specification.new do |spec|
28
28
  spec.add_development_dependency "rake", "~> 11.0"
29
29
  spec.add_development_dependency "minitest", "~> 5.9"
30
30
  spec.add_development_dependency "m", "~> 1.4"
31
- spec.add_development_dependency "rubocop", "0.41.0"
31
+ spec.add_development_dependency "rubocop", "0.45.0"
32
32
  spec.add_development_dependency "timecop"
33
33
  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.4.3
4
+ version: 0.5.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: 2016-11-20 00:00:00.000000000 Z
11
+ date: 2016-11-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -106,14 +106,14 @@ dependencies:
106
106
  requirements:
107
107
  - - '='
108
108
  - !ruby/object:Gem::Version
109
- version: 0.41.0
109
+ version: 0.45.0
110
110
  type: :development
111
111
  prerelease: false
112
112
  version_requirements: !ruby/object:Gem::Requirement
113
113
  requirements:
114
114
  - - '='
115
115
  - !ruby/object:Gem::Version
116
- version: 0.41.0
116
+ version: 0.45.0
117
117
  - !ruby/object:Gem::Dependency
118
118
  name: timecop
119
119
  requirement: !ruby/object:Gem::Requirement
@@ -212,7 +212,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
212
212
  version: '0'
213
213
  requirements: []
214
214
  rubyforge_project:
215
- rubygems_version: 2.4.5.1
215
+ rubygems_version: 2.5.1
216
216
  signing_key:
217
217
  specification_version: 4
218
218
  summary: Recurring events in Ruby