periodoxical 0.7.3 → 0.9.3
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/Gemfile.lock +1 -1
- data/README.md +128 -6
- data/lib/periodoxical/validation.rb +112 -0
- data/lib/periodoxical/version.rb +1 -1
- data/lib/periodoxical.rb +123 -80
- metadata +3 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 1028e4261c9759e49e7446a1ff506fcb4d3b789b4bdd1ef40dbec0bd8df18ae8
|
|
4
|
+
data.tar.gz: c7dece06fabe86ede77b98203bea7716d0d1e039393f1e6e693c8a232615ef01
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: '08dc98ece1a24ed33564c60f32c31fcf76defddbab51fbf2c0c3df04b296759767fc1dde8a4ed744a24b700fe812ce97bcdc06c052c382e61872c55426116258'
|
|
7
|
+
data.tar.gz: c8edb8ea410b88c5888edda103752c27c149e4e06421b74fb7c4a29ed0f09ae32726536ed495702156b8e6e054da0a5140e1a3487bbbac4468abb94ed7cfb79f
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
|
@@ -186,7 +186,7 @@ Periodoxical.generate(
|
|
|
186
186
|
|
|
187
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
|
|
189
|
+
As a Ruby dev, I want to generate the next 3 timeblocks for **8AM - 9AM** for the **5th** and **10th** day of every month starting from **June**
|
|
190
190
|
|
|
191
191
|
```rb
|
|
192
192
|
Periodoxical.generate(
|
|
@@ -215,7 +215,7 @@ Periodoxical.generate(
|
|
|
215
215
|
]
|
|
216
216
|
```
|
|
217
217
|
|
|
218
|
-
As a Ruby dev, I want to generate **4**
|
|
218
|
+
As a Ruby dev, I want to generate **4** timeblocks 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(
|
|
@@ -251,7 +251,7 @@ Periodoxical.generate(
|
|
|
251
251
|
```
|
|
252
252
|
|
|
253
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
|
|
254
|
+
As a Ruby dev, I want to generate timeblocks 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
255
|
|
|
256
256
|
```rb
|
|
257
257
|
Periodoxical.generate(
|
|
@@ -291,8 +291,8 @@ Periodoxical.generate(
|
|
|
291
291
|
]
|
|
292
292
|
```
|
|
293
293
|
|
|
294
|
-
### Example 7 - Exclude time blocks using the `exclusion_dates`
|
|
295
|
-
As a Ruby dev, I want to generate
|
|
294
|
+
### Example 7 - Exclude time blocks using the `exclusion_dates` and `exclusion_times` parameters
|
|
295
|
+
As a Ruby dev, I want to generate timeblocks for **8AM - 9AM** on **Mondays**, except for the **Monday of June 10, 2024**.
|
|
296
296
|
|
|
297
297
|
```rb
|
|
298
298
|
Periodoxical.generate(
|
|
@@ -328,6 +328,127 @@ Periodoxical.generate(
|
|
|
328
328
|
]
|
|
329
329
|
```
|
|
330
330
|
|
|
331
|
+
As a Ruby dev, I want to generate timeblocks for **8AM - 9AM**, and **10AM - 11AM** on **Mondays**, except for those that conflict (meaning overlap) with the time block of **10:30AM - 11:30AM** on the **Monday of June 10, 2024**. I can skip the conflicting time blocks by using the `exclusion_times` parameter.
|
|
332
|
+
|
|
333
|
+
```rb
|
|
334
|
+
Periodoxical.generate(
|
|
335
|
+
time_zone: 'America/Los_Angeles',
|
|
336
|
+
start_date: '2024-06-3',
|
|
337
|
+
limit: 4,
|
|
338
|
+
days_of_week: %(mon),
|
|
339
|
+
time_blocks: [
|
|
340
|
+
{ start_time: '8:00AM', end_time: '9:00AM' },
|
|
341
|
+
{ start_time: '10:00AM', end_time: '11:00AM' },
|
|
342
|
+
],
|
|
343
|
+
exclusion_times: [
|
|
344
|
+
{
|
|
345
|
+
start: '2024-06-10T10:30:00-07:00',
|
|
346
|
+
end: '2024-06-10T11:30:00-07:00',
|
|
347
|
+
}
|
|
348
|
+
],
|
|
349
|
+
)
|
|
350
|
+
# =>
|
|
351
|
+
[
|
|
352
|
+
{
|
|
353
|
+
start: #<DateTime 2024-06-03T08:00:00-0700>,
|
|
354
|
+
end: #<DateTime 2024-06-03T09:00:00-0700>,
|
|
355
|
+
},
|
|
356
|
+
{
|
|
357
|
+
start: #<DateTime 2024-06-03T10:00:00-0700>,
|
|
358
|
+
end: #<DateTime 2024-06-03T11:00:00-0700>,
|
|
359
|
+
},
|
|
360
|
+
{
|
|
361
|
+
start: #<DateTime 2024-06-10T08:00:00-0700>,
|
|
362
|
+
end: #<DateTime 2024-06-10T09:00:00-0700>,
|
|
363
|
+
},
|
|
364
|
+
# The June 10 10AM - 11AM was skipped because it overlapped with the June 10 10:30AM - 11:30AM exclusion time.
|
|
365
|
+
{
|
|
366
|
+
start: #<DateTime 2024-06-17T08:00:00-0700>,
|
|
367
|
+
end: #<DateTime 2024-06-17T09:00:00-0700>,
|
|
368
|
+
},
|
|
369
|
+
{
|
|
370
|
+
start: #<DateTime 2024-06-17T10:00:00-0700>,
|
|
371
|
+
end: #<DateTime 2024-06-17T11:00:00-0700>,
|
|
372
|
+
},
|
|
373
|
+
{
|
|
374
|
+
start: #<DateTime 2024-06-24T08:00:00-0700>,
|
|
375
|
+
end: #<DateTime 2024-06-24T09:00:00-0700>,
|
|
376
|
+
},
|
|
377
|
+
]
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
### Example 8 - Every-other-nth day-of-week rules (ie. every other Tuesday, every 3rd Wednesday, every 10th Friday)
|
|
381
|
+
|
|
382
|
+
As a Ruby dev, I want to generate timeblocks for **9AM- 10AM** on **every Monday**, but **every other Tuesday**, and **every other 3rd Wednesday**. I can do this using the `days_of_week` parameter, but also using the `every` and `every_other_nth` keys to specify the every-other-nth-rules.
|
|
383
|
+
|
|
384
|
+
This can be visualized as:
|
|
385
|
+
|
|
386
|
+
<div align="center">
|
|
387
|
+
<img width="600" alt="alt_google_cal_image" src="https://github.com/StevenJL/periodoxical/assets/2191808/d663da17-a94a-4715-886a-8223b129dd60">
|
|
388
|
+
<p><i>(image courtesy of calendar.google.com)</i></p>
|
|
389
|
+
</div>
|
|
390
|
+
|
|
391
|
+
<br>
|
|
392
|
+
|
|
393
|
+
```rb
|
|
394
|
+
Periodoxical.generate(
|
|
395
|
+
time_zone: 'America/Los_Angeles',
|
|
396
|
+
start_date: '2024-12-30',
|
|
397
|
+
days_of_week: {
|
|
398
|
+
mon: { every: true }, # every Monday (no skipping)
|
|
399
|
+
tue: { every_other_nth: 2 }, # every other Tuesday starting at first Tuesday from start date
|
|
400
|
+
wed: { every_other_nth: 3 }, # every 3rd Wednesday starting at first Wednesday from start date
|
|
401
|
+
},
|
|
402
|
+
limit: 10,
|
|
403
|
+
time_blocks: [
|
|
404
|
+
{ start_time: '9:00AM', end_time: '10:00AM' },
|
|
405
|
+
],
|
|
406
|
+
)
|
|
407
|
+
#=>
|
|
408
|
+
[
|
|
409
|
+
{
|
|
410
|
+
start: #<DateTime: 2024-12-30T09:00:00-0800>,
|
|
411
|
+
end: #<DateTime: 2024-12-30T10:00:00-0800>,
|
|
412
|
+
},
|
|
413
|
+
{
|
|
414
|
+
start: #<DateTime: 2024-12-31T09:00:00-0800>,
|
|
415
|
+
end: #<DateTime: 2024-12-31T10:00:00-0800>,
|
|
416
|
+
},
|
|
417
|
+
{
|
|
418
|
+
start: #<DateTime: 2025-01-01T09:00:00-0800>,
|
|
419
|
+
end: #<DateTime: 2025-01-01T10:00:00-0800>,
|
|
420
|
+
},
|
|
421
|
+
{
|
|
422
|
+
start: #<DateTime: 2025-01-06T09:00:00-0800>,
|
|
423
|
+
end: #<DateTime: 2025-01-06T10:00:00-0800>,
|
|
424
|
+
},
|
|
425
|
+
{
|
|
426
|
+
start: #<DateTime: 2025-01-13T09:00:00-0800>,
|
|
427
|
+
end: #<DateTime: 2025-01-13T10:00:00-0800>,
|
|
428
|
+
},
|
|
429
|
+
{
|
|
430
|
+
start: #<DateTime: 2025-01-14T09:00:00-0800>,
|
|
431
|
+
end: #<DateTime: 2025-01-14T10:00:00-0800>,
|
|
432
|
+
},
|
|
433
|
+
{
|
|
434
|
+
start: #<DateTime: 2025-01-20T09:00:00-0800>,
|
|
435
|
+
end: #<DateTime: 2025-01-20T10:00:00-0800>,
|
|
436
|
+
},
|
|
437
|
+
{
|
|
438
|
+
start: #<DateTime: 2025-01-22T09:00:00-0800>,
|
|
439
|
+
end: #<DateTime: 2025-01-22T10:00:00-0800>,
|
|
440
|
+
},
|
|
441
|
+
{
|
|
442
|
+
start: #<DateTime: 2025-01-27T09:00:00-0800>,
|
|
443
|
+
end: #<DateTime: 2025-01-27T10:00:00-0800>,
|
|
444
|
+
},
|
|
445
|
+
{
|
|
446
|
+
start: #<DateTime: 2025-01-28T09:00:00-0800>,
|
|
447
|
+
end: #<DateTime: 2025-01-28T10:00:00-0800>,
|
|
448
|
+
}
|
|
449
|
+
]
|
|
450
|
+
```
|
|
451
|
+
|
|
331
452
|
### Having Some Fun
|
|
332
453
|
|
|
333
454
|
Generate all the Friday the 13ths ever since May 1980 (when the first Friday the 13th film was released).
|
|
@@ -401,7 +522,8 @@ Periodoxical.generate(
|
|
|
401
522
|
{
|
|
402
523
|
start: #<DateTime: 2028-11-23T17:00:00-0800>,
|
|
403
524
|
end: #<DateTime: 2028-11-23T18:00:00-0800>,
|
|
404
|
-
}
|
|
525
|
+
},
|
|
526
|
+
...
|
|
405
527
|
]
|
|
406
528
|
```
|
|
407
529
|
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
module Periodoxical
|
|
2
|
+
module Validation
|
|
3
|
+
VALID_DAYS_OF_WEEK = %w[mon tue wed thu fri sat sun].freeze
|
|
4
|
+
def validate!
|
|
5
|
+
unless @day_of_week_time_blocks || @time_blocks
|
|
6
|
+
raise "`day_of_week_time_blocks` or `time_blocks` need to be provided"
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
if (@days_of_week || @days_of_week_with_alternations) && @day_of_week_time_blocks
|
|
10
|
+
raise "`days_of_week` and `day_of_week_time_blocks` are both provided, which leads to ambiguity. Please use only one of these parameters."
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
if @weeks_of_month
|
|
14
|
+
@weeks_of_month.each do |wom|
|
|
15
|
+
unless wom.is_a?(Integer) && wom.between?(1, 5)
|
|
16
|
+
raise "weeks_of_month must be an array of integers between 1 and 5"
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# days of week are valid
|
|
22
|
+
if @days_of_week
|
|
23
|
+
@days_of_week.each do |day|
|
|
24
|
+
unless VALID_DAYS_OF_WEEK.include?(day.to_s)
|
|
25
|
+
raise "#{day} is not valid day of week format. Must be: #{VALID_DAYS_OF_WEEK}"
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
if @days_of_week_with_alternations
|
|
31
|
+
@days_of_week_with_alternations.each do |dow, every_other|
|
|
32
|
+
unless VALID_DAYS_OF_WEEK.include?(dow.to_s)
|
|
33
|
+
raise "#{dow} is not valid day of week format. Must be: #{VALID_DAYS_OF_WEEK}"
|
|
34
|
+
end
|
|
35
|
+
unless every_other.is_a?(Hash)
|
|
36
|
+
raise "days_of_week parameter is not used correctly. Please look at examples in README."
|
|
37
|
+
end
|
|
38
|
+
unless every_other[:every] || every_other[:every_other_nth]
|
|
39
|
+
raise "days_of_week parameter is not used correctly. Please look at examples in README."
|
|
40
|
+
end
|
|
41
|
+
if every_other[:every_other_nth]
|
|
42
|
+
unless every_other[:every_other_nth].is_a?(Integer)
|
|
43
|
+
raise "days_of_week parameter is not used correctly. Please look at examples in README."
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
unless every_other[:every_other_nth] > 1
|
|
47
|
+
raise "days_of_week parameter is not used correctly. Please look at examples in README."
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
if @nth_day_of_week_in_month
|
|
54
|
+
@nth_day_of_week_in_month.keys.each do |day|
|
|
55
|
+
unless VALID_DAYS_OF_WEEK.include?(day.to_s)
|
|
56
|
+
raise "#{day} is not valid day of week format. Must be: #{VALID_DAYS_OF_WEEK}"
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
@nth_day_of_week_in_month.each do |k,v|
|
|
60
|
+
unless v.is_a?(Array)
|
|
61
|
+
raise "nth_day_of_week_in_month parameter is invalid. Please look at the README for examples."
|
|
62
|
+
end
|
|
63
|
+
v.each do |num|
|
|
64
|
+
unless [-1,1,2,3,4,5].include?(num)
|
|
65
|
+
raise "nth_day_of_week_in_month parameter is invalid. Please look at the README for examples. "
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
if @nth_day_of_week_in_month && (@days_of_week || @days_of_week_with_alternations || @day_of_week_time_blocks)
|
|
72
|
+
raise "nth_day_of_week_in_month parameter cannot be used in combination with `days_of_week` or `day_of_week_time_blocks`. Please look at the README for examples."
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
if @day_of_week_time_blocks
|
|
76
|
+
@day_of_week_time_blocks.keys.each do |d|
|
|
77
|
+
unless VALID_DAYS_OF_WEEK.include?(d.to_s)
|
|
78
|
+
raise "#{d} is not a valid day of week format. Must be #{VALID_DAYS_OF_WEEK}"
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
if @days_of_month
|
|
84
|
+
@days_of_month.each do |dom|
|
|
85
|
+
unless dom.is_a?(Integer) && dom.between?(1,31)
|
|
86
|
+
raise 'days_of_months must be array of integers between 1 and 31'
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
if @months
|
|
92
|
+
@months.each do |mon|
|
|
93
|
+
unless mon.is_a?(Integer) && mon.between?(1, 12)
|
|
94
|
+
raise 'months must be array of integers between 1 and 12'
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
unless( @limit || @end_date)
|
|
100
|
+
raise "Either `limit` or `end_date` must be provided"
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
if @exclusion_times
|
|
104
|
+
@exclusion_times.each do |tb|
|
|
105
|
+
unless tb[:start] < tb[:end]
|
|
106
|
+
raise "Exclusion times must have `start` before `end`. #{tb[:start]} not before #{tb[:end]}"
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|
data/lib/periodoxical/version.rb
CHANGED
data/lib/periodoxical.rb
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
require "periodoxical/version"
|
|
2
|
+
require "periodoxical/validation"
|
|
2
3
|
require "date"
|
|
3
4
|
require "time"
|
|
4
5
|
require "tzinfo"
|
|
@@ -12,7 +13,7 @@ module Periodoxical
|
|
|
12
13
|
end
|
|
13
14
|
|
|
14
15
|
class Core
|
|
15
|
-
|
|
16
|
+
include Periodoxical::Validation
|
|
16
17
|
# @param [String] time_zone
|
|
17
18
|
# Ex: 'America/Los_Angeles', 'America/Chicago',
|
|
18
19
|
# TZInfo::DataTimezone#name from the tzinfo gem (https://github.com/tzinfo/tzinfo)
|
|
@@ -43,6 +44,19 @@ module Periodoxical
|
|
|
43
44
|
# @param [Aray<String>] exclusion_dates
|
|
44
45
|
# Dates to be excluded when generating the time blocks
|
|
45
46
|
# Ex: ['2024-06-10', '2024-06-14']
|
|
47
|
+
# @param [Aray<Hash>] exclusion_times
|
|
48
|
+
# Timeblocks to be excluded when generating the time blocks if there is conflict (ie. overlap)
|
|
49
|
+
# Ex: [
|
|
50
|
+
# {
|
|
51
|
+
# start: '2024-06-10T10:30:00-07:00',
|
|
52
|
+
# end: '2024-06-10T11:30:00-07:00'
|
|
53
|
+
# },
|
|
54
|
+
# {
|
|
55
|
+
# start: '2024-06-10T14:30:00-07:00',
|
|
56
|
+
# end: '2024-06-10T15:30:00-07:00'
|
|
57
|
+
# },
|
|
58
|
+
# ]
|
|
59
|
+
#
|
|
46
60
|
# @param [Hash<Array<Hash>>] day_of_week_time_blocks
|
|
47
61
|
# To be used when hours are different between days of the week
|
|
48
62
|
# Ex: {
|
|
@@ -57,6 +71,7 @@ module Periodoxical
|
|
|
57
71
|
day_of_week_time_blocks: nil,
|
|
58
72
|
limit: nil,
|
|
59
73
|
exclusion_dates: nil,
|
|
74
|
+
exclusion_times: nil,
|
|
60
75
|
time_zone: 'Etc/UTC',
|
|
61
76
|
days_of_week: nil,
|
|
62
77
|
nth_day_of_week_in_month: nil,
|
|
@@ -66,7 +81,11 @@ module Periodoxical
|
|
|
66
81
|
)
|
|
67
82
|
|
|
68
83
|
@time_zone = TZInfo::Timezone.get(time_zone)
|
|
69
|
-
|
|
84
|
+
if days_of_week.is_a?(Array)
|
|
85
|
+
@days_of_week = days_of_week
|
|
86
|
+
elsif days_of_week.is_a?(Hash)
|
|
87
|
+
@days_of_week_with_alternations = days_of_week
|
|
88
|
+
end
|
|
70
89
|
@nth_day_of_week_in_month = nth_day_of_week_in_month
|
|
71
90
|
@days_of_month = days_of_month
|
|
72
91
|
@weeks_of_month = weeks_of_month
|
|
@@ -79,6 +98,11 @@ module Periodoxical
|
|
|
79
98
|
@exclusion_dates = if exclusion_dates && !exclusion_dates.empty?
|
|
80
99
|
exclusion_dates.map { |ed| Date.parse(ed) }
|
|
81
100
|
end
|
|
101
|
+
@exclusion_times = if exclusion_times
|
|
102
|
+
exclusion_times.map do |et|
|
|
103
|
+
{ start: DateTime.parse(et[:start]), end: DateTime.parse(et[:end]) }
|
|
104
|
+
end
|
|
105
|
+
end
|
|
82
106
|
validate!
|
|
83
107
|
end
|
|
84
108
|
|
|
@@ -106,83 +130,6 @@ module Periodoxical
|
|
|
106
130
|
|
|
107
131
|
private
|
|
108
132
|
|
|
109
|
-
def validate!
|
|
110
|
-
unless @day_of_week_time_blocks || @time_blocks
|
|
111
|
-
raise "`day_of_week_time_blocks` or `time_blocks` need to be provided"
|
|
112
|
-
end
|
|
113
|
-
|
|
114
|
-
if @days_of_week && @day_of_week_time_blocks
|
|
115
|
-
raise "`days_of_week` and `day_of_week_time_blocks` are both provided, which leads to ambiguity. Please use only one of these parameters."
|
|
116
|
-
end
|
|
117
|
-
|
|
118
|
-
if @weeks_of_month
|
|
119
|
-
@weeks_of_month.each do |wom|
|
|
120
|
-
unless wom.is_a?(Integer) && wom.between?(1, 5)
|
|
121
|
-
raise "weeks_of_month must be an array of integers between 1 and 5"
|
|
122
|
-
end
|
|
123
|
-
end
|
|
124
|
-
end
|
|
125
|
-
|
|
126
|
-
# days of week are valid
|
|
127
|
-
if @days_of_week
|
|
128
|
-
@days_of_week.each do |day|
|
|
129
|
-
unless VALID_DAYS_OF_WEEK.include?(day.to_s)
|
|
130
|
-
raise "#{day} is not valid day of week format. Must be: #{VALID_DAYS_OF_WEEK}"
|
|
131
|
-
end
|
|
132
|
-
end
|
|
133
|
-
end
|
|
134
|
-
|
|
135
|
-
if @nth_day_of_week_in_month
|
|
136
|
-
@nth_day_of_week_in_month.keys.each do |day|
|
|
137
|
-
unless VALID_DAYS_OF_WEEK.include?(day.to_s)
|
|
138
|
-
raise "#{day} is not valid day of week format. Must be: #{VALID_DAYS_OF_WEEK}"
|
|
139
|
-
end
|
|
140
|
-
end
|
|
141
|
-
@nth_day_of_week_in_month.each do |k,v|
|
|
142
|
-
unless v.is_a?(Array)
|
|
143
|
-
raise "nth_day_of_week_in_month parameter is invalid. Please look at the README for examples."
|
|
144
|
-
end
|
|
145
|
-
v.each do |num|
|
|
146
|
-
unless [-1,1,2,3,4,5].include?(num)
|
|
147
|
-
raise "nth_day_of_week_in_month parameter is invalid. Please look at the README for examples. "
|
|
148
|
-
end
|
|
149
|
-
end
|
|
150
|
-
end
|
|
151
|
-
end
|
|
152
|
-
|
|
153
|
-
if @nth_day_of_week_in_month && (@days_of_week || @day_of_week_time_blocks)
|
|
154
|
-
raise "nth_day_of_week_in_month parameter cannot be used in combination with `days_of_week` or `day_of_week_time_blocks`. Please look at the README for examples."
|
|
155
|
-
end
|
|
156
|
-
|
|
157
|
-
if @day_of_week_time_blocks
|
|
158
|
-
@day_of_week_time_blocks.keys.each do |d|
|
|
159
|
-
unless VALID_DAYS_OF_WEEK.include?(d.to_s)
|
|
160
|
-
raise "#{d} is not a valid day of week format. Must be #{VALID_DAYS_OF_WEEK}"
|
|
161
|
-
end
|
|
162
|
-
end
|
|
163
|
-
end
|
|
164
|
-
|
|
165
|
-
if @days_of_month
|
|
166
|
-
@days_of_month.each do |dom|
|
|
167
|
-
unless dom.is_a?(Integer) && dom.between?(1,31)
|
|
168
|
-
raise 'days_of_months must be array of integers between 1 and 31'
|
|
169
|
-
end
|
|
170
|
-
end
|
|
171
|
-
end
|
|
172
|
-
|
|
173
|
-
if @months
|
|
174
|
-
@months.each do |mon|
|
|
175
|
-
unless mon.is_a?(Integer) && mon.between?(1, 12)
|
|
176
|
-
raise 'months must be array of integers between 1 and 12'
|
|
177
|
-
end
|
|
178
|
-
end
|
|
179
|
-
end
|
|
180
|
-
|
|
181
|
-
unless( @limit || @end_date)
|
|
182
|
-
raise "Either `limit` or `end_date` must be provided"
|
|
183
|
-
end
|
|
184
|
-
end
|
|
185
|
-
|
|
186
133
|
def day_of_week_long_to_short(dow)
|
|
187
134
|
{
|
|
188
135
|
"Monday" => "mon",
|
|
@@ -231,6 +178,21 @@ module Periodoxical
|
|
|
231
178
|
@current_day_of_week = day_of_week_long_to_short(@current_date.strftime("%A"))
|
|
232
179
|
@current_count = 0
|
|
233
180
|
@keep_generating = true
|
|
181
|
+
# When there are alternations in days of week
|
|
182
|
+
# (ie. every other Monday, every 3rd Thursday, etc).
|
|
183
|
+
# We keep running tally of day_of_week counts and use modulo-math to pick out
|
|
184
|
+
# every n-th one.
|
|
185
|
+
if @days_of_week_with_alternations
|
|
186
|
+
@days_of_week_running_tally = {
|
|
187
|
+
mon: 0,
|
|
188
|
+
tue: 0,
|
|
189
|
+
wed: 0,
|
|
190
|
+
thu: 0,
|
|
191
|
+
fri: 0,
|
|
192
|
+
sat: 0,
|
|
193
|
+
sun: 0,
|
|
194
|
+
}
|
|
195
|
+
end
|
|
234
196
|
end
|
|
235
197
|
|
|
236
198
|
# @param [Hash] time_block
|
|
@@ -241,6 +203,9 @@ module Periodoxical
|
|
|
241
203
|
# }
|
|
242
204
|
# Generates time block but also checks if we should stop generating
|
|
243
205
|
def append_to_output_and_check_limit(time_block)
|
|
206
|
+
# Check if this particular time is conflicts with any times from `exclusion_times`.
|
|
207
|
+
return if overlaps_with_an_excluded_time?(time_block)
|
|
208
|
+
|
|
244
209
|
@output << {
|
|
245
210
|
start: time_str_to_object(@current_date, time_block[:start_time]),
|
|
246
211
|
end: time_str_to_object(@current_date, time_block[:end_time])
|
|
@@ -256,6 +221,7 @@ module Periodoxical
|
|
|
256
221
|
|
|
257
222
|
def advance_current_date_and_check_if_reached_end_date
|
|
258
223
|
@current_date = @current_date + 1
|
|
224
|
+
|
|
259
225
|
@current_day_of_week = day_of_week_long_to_short(@current_date.strftime("%A"))
|
|
260
226
|
|
|
261
227
|
if @end_date && (@current_date > @end_date)
|
|
@@ -302,6 +268,28 @@ module Periodoxical
|
|
|
302
268
|
return false unless @days_of_week.include?(@current_day_of_week)
|
|
303
269
|
end
|
|
304
270
|
|
|
271
|
+
if @days_of_week_with_alternations
|
|
272
|
+
# current_date is not specified in days_of_week, so skip it
|
|
273
|
+
return false if @days_of_week_with_alternations[@current_day_of_week.to_sym].nil?
|
|
274
|
+
|
|
275
|
+
alternating_spec = @days_of_week_with_alternations[@current_day_of_week.to_sym]
|
|
276
|
+
|
|
277
|
+
# In the { every: true } case, we don't check the alternations logic, we just add it.
|
|
278
|
+
unless alternating_spec[:every]
|
|
279
|
+
# We are now specifying every other nth occurrence (ie. every 2nd Tuesday, every 3rd Wednesday)
|
|
280
|
+
alternating_frequency = alternating_spec[:every_other_nth]
|
|
281
|
+
|
|
282
|
+
unless (@days_of_week_running_tally[@current_day_of_week.to_sym] % alternating_frequency) == 0
|
|
283
|
+
# If day-of-week alternations are present, we need to keep track of day-of-weeks
|
|
284
|
+
# we have encountered and added or would have added so far.
|
|
285
|
+
update_days_of_week_running_tally!
|
|
286
|
+
|
|
287
|
+
return false
|
|
288
|
+
end
|
|
289
|
+
update_days_of_week_running_tally!
|
|
290
|
+
end
|
|
291
|
+
end
|
|
292
|
+
|
|
305
293
|
if @day_of_week_time_blocks
|
|
306
294
|
dowtb = @day_of_week_time_blocks[@current_day_of_week.to_sym]
|
|
307
295
|
return false if dowtb.nil?
|
|
@@ -327,7 +315,20 @@ module Periodoxical
|
|
|
327
315
|
end
|
|
328
316
|
end
|
|
329
317
|
|
|
330
|
-
#
|
|
318
|
+
# The default return true is really only needed to support this use-case:
|
|
319
|
+
# Periodoxical.generate(
|
|
320
|
+
# time_zone: 'America/Los_Angeles',
|
|
321
|
+
# time_blocks: [
|
|
322
|
+
# {
|
|
323
|
+
# start_time: '9:00AM',
|
|
324
|
+
# end_time: '10:30AM'
|
|
325
|
+
# },
|
|
326
|
+
# ],
|
|
327
|
+
# start_date: '2024-05-23',
|
|
328
|
+
# end_date: '2024-05-27',
|
|
329
|
+
# )
|
|
330
|
+
# where if we don't specify any date-of-week/month constraints, we return all consecutive dates.
|
|
331
|
+
# In the future, if we don't support this case, we can use `false` as the return value.
|
|
331
332
|
true
|
|
332
333
|
end
|
|
333
334
|
|
|
@@ -367,5 +368,47 @@ module Periodoxical
|
|
|
367
368
|
|
|
368
369
|
((last_date.day - 1) / 7) + 1
|
|
369
370
|
end
|
|
371
|
+
|
|
372
|
+
def update_days_of_week_running_tally!
|
|
373
|
+
@days_of_week_running_tally[@current_day_of_week.to_sym] = @days_of_week_running_tally[@current_day_of_week.to_sym] + 1
|
|
374
|
+
end
|
|
375
|
+
|
|
376
|
+
# @return [Boolean]
|
|
377
|
+
# Whether or not the given `time_block` in the @current_date and
|
|
378
|
+
# @time_zone overlaps with the times in `exclusion_times`.
|
|
379
|
+
def overlaps_with_an_excluded_time?(time_block)
|
|
380
|
+
return false unless @exclusion_times
|
|
381
|
+
|
|
382
|
+
@exclusion_times.each do |exclusion_timeblock|
|
|
383
|
+
return true if overlap?(
|
|
384
|
+
exclusion_timeblock,
|
|
385
|
+
{
|
|
386
|
+
start: time_str_to_object(@current_date, time_block[:start_time]),
|
|
387
|
+
end: time_str_to_object(@current_date, time_block[:end_time]),
|
|
388
|
+
}
|
|
389
|
+
)
|
|
390
|
+
end
|
|
391
|
+
|
|
392
|
+
false
|
|
393
|
+
end
|
|
394
|
+
|
|
395
|
+
# @param [Hash] time_block_1, time_block_2
|
|
396
|
+
# Ex: {
|
|
397
|
+
# start: #<DateTime>,
|
|
398
|
+
# end: #<DateTime>,
|
|
399
|
+
# }
|
|
400
|
+
def overlap?(time_block_1, time_block_2)
|
|
401
|
+
tb_1_start = time_block_1[:start]
|
|
402
|
+
tb_1_end = time_block_1[:end]
|
|
403
|
+
tb_2_start = time_block_2[:start]
|
|
404
|
+
tb_2_end = time_block_2[:end]
|
|
405
|
+
|
|
406
|
+
# Basicall overlap is when one starts before the other has ended
|
|
407
|
+
return true if tb_1_end > tb_2_start && tb_1_end < tb_2_end
|
|
408
|
+
# By symmetry
|
|
409
|
+
return true if tb_2_end > tb_1_start && tb_2_end < tb_1_end
|
|
410
|
+
|
|
411
|
+
false
|
|
412
|
+
end
|
|
370
413
|
end
|
|
371
414
|
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.
|
|
4
|
+
version: 0.9.3
|
|
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-
|
|
11
|
+
date: 2024-06-04 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: tzinfo
|
|
@@ -135,6 +135,7 @@ files:
|
|
|
135
135
|
- bin/console
|
|
136
136
|
- bin/setup
|
|
137
137
|
- lib/periodoxical.rb
|
|
138
|
+
- lib/periodoxical/validation.rb
|
|
138
139
|
- lib/periodoxical/version.rb
|
|
139
140
|
- periodoxical.gemspec
|
|
140
141
|
homepage: https://github.com/StevenJL/periodoxical
|