periodoxical 0.6.2 → 0.7.2

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
  SHA256:
3
- metadata.gz: 1c90e3908492b944735cc23b5da95746309133de5b54413495d4ed49e0d2dce3
4
- data.tar.gz: 7fa63855a49e2c6feb9955007361aa6d8c9262f847e6fc0fc159c47f95ed1db5
3
+ metadata.gz: 01bdfec5ba5930f743cc1c4197e0964631c8dde5be1bb14c79082fe363bf72e1
4
+ data.tar.gz: 6aaaf40f368acbdd78632455889298e6eb6ced8c0ee80f4ed9e96387e7d05536
5
5
  SHA512:
6
- metadata.gz: 36ba01fb5173e0fae2dea0eeba563145b373860989891f2640b468ee688b0cdd5d78b7f5b44e7d52f8d137cd80940ebe8e9b75b997dc144d0d159982bd15aa87
7
- data.tar.gz: 5e678e7218811a7d5f0e779a2b766e2099742554414ee007078af87b0fc10f196900672c5fe0daa58b978ce1c4e31f9f08bfc21c0573f257f48823c211acdd74
6
+ metadata.gz: dc697912db1f3a5439ffd3cdf3371c4be7f4475bb2bbae1f01c2ac42887d6041c45763e1977eabf8f68e5f6ee7c7c0d728e80153796acd419d9741a7a3126d5f
7
+ data.tar.gz: 7a0c713a31ccca86c48b08bdcf1908056475a589c7adc0b93ed94dd13af0ebd35fe58151340fe2e88ff3da13d653673064c9c1d3500e9dbe61845528ecdbb207
data/README.md CHANGED
@@ -29,8 +29,8 @@ Or install it yourself as:
29
29
 
30
30
  ## Usage
31
31
 
32
- #### Example 1
33
- As a Ruby dev, I want to generate all the datetime blocks of **9:00AM - 10:30AM**for all days from May 23, 2024 to May 26, 2024 inclusive.
32
+ ### Example 1
33
+ As a Ruby dev, I want to generate all the datetime blocks of **9:00AM - 10:30AM** for all days from **May 23, 2024** to **May 26, 2024** inclusive.
34
34
 
35
35
  ```rb
36
36
  Periodoxical.generate(
@@ -65,7 +65,7 @@ Periodoxical.generate(
65
65
  ]
66
66
  ```
67
67
 
68
- #### Example 2
68
+ ### Example 2 - specify days of the week
69
69
  As a Ruby dev, I want to generate all the datetime blocks of **9:00AM - 10:30AM** and **2:00PM - 2:30PM**, on **Mondays**, **Wednesdays**, and **Thursdays**, between the dates of **May 23, 2024** and **June 12, 2024**, inclusive. This can be represented visually as:
70
70
 
71
71
  <div align="center">
@@ -115,9 +115,9 @@ Periodoxical.generate(
115
115
  ]
116
116
  ```
117
117
 
118
- #### Example 3 - using the `limit` key.
118
+ ### Example 3 - using the `limit` key.
119
119
 
120
- As a ruby dev, I want to generate the next 3 datetime blocks of **9:00AM - 10:30AM** and **2:00PM - 2:30PM** on **Sundays**, after **May 23, 2024** using the `limit` key.
120
+ As a ruby dev, I want to generate the next **3** datetime blocks of **9:00AM - 10:30AM** and **2:00PM - 2:30PM** on **Sundays**, after **May 23, 2024** using the `limit` key.
121
121
 
122
122
  ```rb
123
123
  Periodoxical.generate(
@@ -153,7 +153,7 @@ Periodoxical.generate(
153
153
  ]
154
154
  ```
155
155
 
156
- #### Example 4 - when time blocks vary between days
156
+ ### Example 4 - when time blocks vary between days
157
157
 
158
158
  As a ruby dev, I want to generate all the timeblocks between **May 23, 2024** and **June 12, 2024** where the time should be **8AM-9AM** on **Mondays**, but **10:45AM-12:00PM** and **2:00PM-4:00PM** on **Wednesdays**, and **2:30PM-4:15PM** on **Thursdays**.
159
159
 
@@ -184,9 +184,9 @@ Periodoxical.generate(
184
184
  )
185
185
  ```
186
186
 
187
- #### Example 5 - when specifying time blocks occur by day-of-month and/or and/or week-of-month and/or month.
187
+ ### Example 5 - when specifying time blocks using day-of-month and/or week-of-month and/or month.
188
188
 
189
- As a Ruby dev, I want to generate the next 3 slots for 8AM - 9AM for the 5th and 10th day of every month starting from June
189
+ As a Ruby dev, I want to generate the next 3 slots for **8AM - 9AM** for the **5th** and **10th** day of every month starting from **June**
190
190
 
191
191
  ```rb
192
192
  Periodoxical.generate(
@@ -201,21 +201,21 @@ Periodoxical.generate(
201
201
  #=>
202
202
  [
203
203
  {
204
- start: #<DateTime: 2024-06-05 08:00:00 -0700>,
205
- end: #<DateTime: 2024-06-05 09:00:00 -0700>,
204
+ start: #<DateTime: 2024-06-05T08:00:00-0700>,
205
+ end: #<DateTime: 2024-06-05T09:00:00-0700>,
206
206
  },
207
207
  {
208
- start: #<DateTime: 2024-06-10 08:00:00 -0700>,
209
- end: #<DateTime: 2024-06-10 09:00:00 -0700>,
208
+ start: #<DateTime: 2024-06-10T08:00:00-0700>,
209
+ end: #<DateTime: 2024-06-10T09:00:00-0700>,
210
210
  },
211
211
  {
212
- start: #<DateTime: 2024-07-05 08:00:00 -0700>,
213
- end: #<DateTime: 2024-07-05 09:00:00 -0700>,
212
+ start: #<DateTime: 2024-07-05T08:00:00-0700>,
213
+ end: #<DateTime: 2024-07-05T09:00:00-0700>,
214
214
  },
215
215
  ]
216
216
  ```
217
217
 
218
- As a Ruby dev, I want to generate 4 slots for 8AM - 9AM on Mondays but only in the first two weeks in the months of April, May, June
218
+ As a Ruby dev, I want to generate **4** slots for **8AM - 9AM** on **Mondays** but only in the **first two weeks** in the months of **April, May, and June**
219
219
 
220
220
  ```
221
221
  Periodoxical.generate(
@@ -232,26 +232,67 @@ Periodoxical.generate(
232
232
  #=>
233
233
  [
234
234
  {
235
- start_time: #<DateTime: 2024-04-01 08:00:00 -0700>,
236
- end_time: #<DateTime: 2024-04-01 09:00:00 -0700>,
235
+ start_time: #<DateTime: 2024-04-01T08:00:00-0700>,
236
+ end_time: #<DateTime: 2024-04-01T09:00:00-0700>,
237
237
  },
238
238
  {
239
- start_time: #<DateTime: 2024-04-08 08:00:00 -0700>,
240
- end_time: #<DateTime: 2024-04-08 09:00:00 -0700>,
239
+ start_time: #<DateTime: 2024-04-08T08:00:00-0700>,
240
+ end_time: #<DateTime: 2024-04-08T09:00:00-0700>,
241
241
  },
242
242
  {
243
- start_time: #<DateTime: 2024-05-06 08:00:00 -0700>,
244
- end_time: #<DateTime: 2024-05-06 09:00:00 -0700>,
243
+ start_time: #<DateTime: 2024-05-06T08:00:00-0700>,
244
+ end_time: #<DateTime: 2024-05-06T09:00:00-0700>,
245
245
  },
246
246
  {
247
- start_time: #<DateTime: 2024-06-03 08:00:00 -0700>,
248
- end_time: #<DateTime: 2024-06-03 09:00:00 -0700>,
247
+ start_time: #<DateTime: 2024-06-03T08:00:00-0700>,
248
+ end_time: #<DateTime: 2024-06-03T09:00:00-0700>,
249
249
  },
250
250
  ]
251
251
  ```
252
252
 
253
- #### Example 6 - Exclude time blocks using the `exclusion_dates` parameter
254
- As a Ruby dev, I want to generate slots for 8AM - 9AM on Mondays, except for the Monday of June 10, 2024.
253
+ ### Example 6 - Specify nth day-of-week in month (ie. first Monday of the Month, second Tuesday of the Month, last Friday of Month)
254
+ As a Ruby dev, I want to generate slots for **8AM - 9AM** on the **first and second Mondays** and **last Fridays** of every month starting in June 2024. I can do this with the `nth_day_of_week_in_month` param.
255
+
256
+ ```rb
257
+ Periodoxical.generate(
258
+ time_zone: 'America/Los_Angeles',
259
+ start_date: '2024-06-01',
260
+ limit: 5,
261
+ nth_day_of_week_in_month: {
262
+ mon: [1, 2], # valid values: -1,1,2,3,4,5
263
+ fri: [-1], # Use -1 to specify "last" of the month.
264
+ },
265
+ time_blocks: [
266
+ { start_time: '8:00AM', end_time: '9:00AM' },
267
+ ],
268
+ )
269
+ # =>
270
+ [
271
+ {
272
+ start: #<DateTime: 2024-06-03T08:00:00-0700>, # First Monday of June 2024
273
+ end: #<DateTime: 2024-06-03T09:00:00-0700>,
274
+ },
275
+ {
276
+ start: #<DateTime: 2024-06-10T08:00:00-0700>, # second Monday of June 2024
277
+ end: #<DateTime: 2024-06-10T09:00:00-0700>,
278
+ },
279
+ {
280
+ start: #<DateTime: 2024-06-28 08:00:00 -0700>, # last Friday of June 2024
281
+ end: #<DateTime: 2024-06-28 09:00:00 -0700>,
282
+ },
283
+ {
284
+ start: #<DateTime: 2024-07-01 08:00:00 -0700>, # First Monday of July 2024
285
+ end: #<DateTime: 2024-07-01 09:00:00 -0700>,
286
+ },
287
+ {
288
+ start: #<DateTime: 2024-07-08 08:00:00 -0700>, # Second Monday of July 2024
289
+ end: #<DateTime: 2024-07-08 09:00:00 -0700>,
290
+ },
291
+ ]
292
+ ```
293
+
294
+ ### Example 7 - Exclude time blocks using the `exclusion_dates` parameter
295
+ As a Ruby dev, I want to generate slots for **8AM - 9AM** on **Mondays**, except for the **Monday of June 10, 2024**.
255
296
 
256
297
  ```rb
257
298
  Periodoxical.generate(
@@ -287,6 +328,83 @@ Periodoxical.generate(
287
328
  ]
288
329
  ```
289
330
 
331
+ ### Having Some Fun
332
+
333
+ Generate all the Friday the 13ths ever since May 1980 (when the first Friday the 13th film was released).
334
+
335
+ ```rb
336
+ Periodoxical.generate(
337
+ time_zone: 'America/Los_Angeles',
338
+ start_date: '1980-05-01',
339
+ days_of_week: %w(fri),
340
+ days_of_month: [13],
341
+ limit: 100,
342
+ time_blocks: [
343
+ { start_time: '11:00PM', end_time: '12:00AM' },
344
+ ],
345
+ )
346
+ # =>
347
+ [
348
+ {
349
+ start: #<DateTime: 1980-06-13T23:00:00-0700>,
350
+ end: #<DateTime: 1980-06-13T00:00:00-0700>,
351
+ },
352
+ {
353
+ start: #<DateTime: 1981-02-13T23:00:00-0800>,
354
+ end: #<DateTime: 1981-02-13T00:00:00-0800>,
355
+ },
356
+ {
357
+ start: #<DateTime: 1981-03-13T23:00:00-0800>,
358
+ end: #<DateTime: 1981-03-13T00:00:00-0800>,
359
+ },
360
+ {
361
+ start: #<DateTime: 1981-11-13T23:00:00-0800>,
362
+ end: #<DateTime: 1981-11-13T00:00:00-0800>,
363
+ }
364
+ ...
365
+ ]
366
+ ```
367
+
368
+ Generate the next 10 Thanksgivings from now on (Thanksgivings is defined as the 4th Thursday in November).
369
+
370
+ ```rb
371
+ Periodoxical.generate(
372
+ time_zone: 'America/Los_Angeles',
373
+ start_date: '2024-05-01',
374
+ months: [11],
375
+ nth_day_of_week_in_month: {
376
+ thu: [4],
377
+ },
378
+ limit: 10,
379
+ time_blocks: [
380
+ { start_time: '5:00PM', end_time: '6:00PM' },
381
+ ],
382
+ )
383
+ #=>
384
+ [
385
+ {
386
+ start: #<DateTime: 2024-11-28T17:00:00-0800>,
387
+ end: #<DateTime: 2024-11-28T18:00:00-0800>,
388
+ },
389
+ {
390
+ start: #<DateTime: 2025-11-27T17:00:00-0800>,
391
+ end: #<DateTime: 2025-11-27T18:00:00-0800>,
392
+ },
393
+ {
394
+ start: #<DateTime: 2026-11-26T17:00:00-0800>,
395
+ end: #<DateTime: 2026-11-26T18:00:00-0800>,
396
+ },
397
+ {
398
+ start: #<DateTime: 2027-11-25T17:00:00-0800>,
399
+ end: #<DateTime: 2027-11-25T18:00:00-0800>,
400
+ },
401
+ {
402
+ start: #<DateTime: 2028-11-23T17:00:00-0800>,
403
+ end: #<DateTime: 2028-11-23T18:00:00-0800>,
404
+ }
405
+ ]
406
+ ```
407
+
290
408
  ## Development
291
409
 
292
410
  After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
@@ -1,3 +1,3 @@
1
1
  module Periodoxical
2
- VERSION = "0.6.2"
2
+ VERSION = "0.7.2"
3
3
  end
data/lib/periodoxical.rb CHANGED
@@ -59,6 +59,7 @@ module Periodoxical
59
59
  exclusion_dates: nil,
60
60
  time_zone: 'Etc/UTC',
61
61
  days_of_week: nil,
62
+ nth_day_of_week_in_month: nil,
62
63
  days_of_month: nil,
63
64
  weeks_of_month: nil,
64
65
  months: nil
@@ -66,6 +67,7 @@ module Periodoxical
66
67
 
67
68
  @time_zone = TZInfo::Timezone.get(time_zone)
68
69
  @days_of_week = days_of_week
70
+ @nth_day_of_week_in_month = nth_day_of_week_in_month
69
71
  @days_of_month = days_of_month
70
72
  @weeks_of_month = weeks_of_month
71
73
  @months = months
@@ -105,6 +107,10 @@ module Periodoxical
105
107
  raise "`day_of_week_time_blocks` or `time_blocks` need to be provided"
106
108
  end
107
109
 
110
+ if @days_of_week && @day_of_week_time_blocks
111
+ raise "`days_of_week` and `day_of_week_time_blocks` are both provided, which leads to ambiguity. Please use only one of these parameters."
112
+ end
113
+
108
114
  if @weeks_of_month
109
115
  @weeks_of_month.each do |wom|
110
116
  unless wom.is_a?(Integer) && wom.between?(1, 5)
@@ -122,6 +128,32 @@ module Periodoxical
122
128
  end
123
129
  end
124
130
 
131
+ if @nth_day_of_week_in_month
132
+ @nth_day_of_week_in_month.keys.each do |day|
133
+ unless VALID_DAYS_OF_WEEK.include?(day.to_s)
134
+ raise "#{day} is not valid day of week format. Must be: #{VALID_DAYS_OF_WEEK}"
135
+ end
136
+ end
137
+ @nth_day_of_week_in_month.each do |k,v|
138
+ unless v.is_a?(Array)
139
+ raise "nth_day_of_week_in_month parameter is invalid. Please look at the README for examples."
140
+ end
141
+ v.each do |num|
142
+ unless [-1,1,2,3,4,5].include?(num)
143
+ raise "nth_day_of_week_in_month parameter is invalid. Please look at the README for examples. "
144
+ end
145
+ end
146
+ end
147
+ end
148
+
149
+ if @days_of_week && @nth_day_of_week_in_month
150
+ # If both `days_of_week` and `nth_day_of_week_in_month` are provided for the same days-of-the-week, then it is ambiguous. (ie. I want this timeslot for every Monday, but also only for the first Mondays, well which one is it?)
151
+ overlapping_days = @days_of_week & @nth_day_of_week_in_month.keys.map(&:to_s)
152
+ unless overlapping_days.empty?
153
+ raise "#{overlapping_days} is specified in both `days_of_week` and also `nth_day_of_week_in_month`, which leads to ambiguity. Pleasee look at the README for examples."
154
+ end
155
+ end
156
+
125
157
  if @day_of_week_time_blocks
126
158
  @day_of_week_time_blocks.keys.each do |d|
127
159
  unless VALID_DAYS_OF_WEEK.include?(d.to_s)
@@ -254,20 +286,51 @@ module Periodoxical
254
286
  return false unless @days_of_month.include?(@current_date.day)
255
287
  end
256
288
 
289
+ # The following conditions depend on the day-of-week of current_date.
290
+ day_of_week = day_of_week_long_to_short(@current_date.strftime("%A"))
291
+
257
292
  # If days of week are specified, but current_date does not satisfy it,
258
293
  # return false
259
294
  if @days_of_week
260
- day_of_week = day_of_week_long_to_short(@current_date.strftime("%A"))
261
295
  return false unless @days_of_week.include?(day_of_week)
262
296
  end
263
297
 
264
298
  if @day_of_week_time_blocks
265
- day_of_week = day_of_week_long_to_short(@current_date.strftime("%A"))
266
299
  dowtb = @day_of_week_time_blocks[day_of_week.to_sym]
267
300
  return false if dowtb.nil?
268
301
  return false if dowtb.empty?
269
302
  end
270
303
 
304
+ if @nth_day_of_week_in_month
305
+ # If the day of week is specified in nth_day_of_week_in_month,
306
+ # we need to investigate it whether or not to exclude it.
307
+ if @nth_day_of_week_in_month[day_of_week.to_sym]
308
+ n_occurence_of_day_of_week_in_month = ((@current_date.day - 1) / 7) + 1
309
+ # -1 is a special case and requires extra-math
310
+ if @nth_day_of_week_in_month[day_of_week.to_sym].include?(-1)
311
+ # We basically want to convert the -1 into its 'positive-equivalent` in this month, and compare it with that.
312
+ # For example, in June 2024, the Last Friday is also the 4th Friday. So in that case, we convert the -1 into a 4.
313
+ positivized_indices = @nth_day_of_week_in_month[day_of_week.to_sym].map { |indx| positivize_index(indx, day_of_week) }
314
+ return positivized_indices.include?(n_occurence_of_day_of_week_in_month)
315
+ else
316
+ return @nth_day_of_week_in_month[day_of_week.to_sym].include?(n_occurence_of_day_of_week_in_month)
317
+ end
318
+ else
319
+ # if day-of-week was not specified in nth_day_of_week_in_month,
320
+ # it could have been specified in either `days_of_week` or `day_of_week_time_blocks and we unfortunately need to re-check those here. I cant think of a way to further DRY-it up.
321
+ return false unless @days_of_week && @day_of_week_time_blocks
322
+ if @day_of_week
323
+ return false unless @days_of_week.include?(day_of_week)
324
+ end
325
+
326
+ if @day_of_week_time_blocks
327
+ dowtb = @day_of_week_time_blocks[day_of_week.to_sym]
328
+ return false if dowtb.nil?
329
+ return false if dowtb.empty?
330
+ end
331
+ end
332
+ end
333
+
271
334
  # Otherwise, return true
272
335
  true
273
336
  end
@@ -289,5 +352,25 @@ module Periodoxical
289
352
  end
290
353
  end
291
354
  end
355
+
356
+ # What is the positive index of the last day-of-week for the given month-year?
357
+ # For example, the last Friday in June 2024 is also the nth Friday. What is this n?
358
+ # @return [Integer]
359
+ def positivize_index(indx, day_of_week)
360
+ # If index is already positive, just return it
361
+ return indx if indx > 0
362
+
363
+ # get last_day_of month
364
+ month = @current_date.month
365
+ year = @current_date.year
366
+ last_date = Date.new(year, month, -1)
367
+
368
+ # walk backwords until you get to the right day of the week
369
+ while day_of_week_long_to_short(last_date.strftime("%A")) != day_of_week
370
+ last_date = last_date - 1
371
+ end
372
+
373
+ ((last_date.day - 1) / 7) + 1
374
+ end
292
375
  end
293
376
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: periodoxical
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.2
4
+ version: 0.7.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Steven Li
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-06-01 00:00:00.000000000 Z
11
+ date: 2024-06-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: tzinfo