periodoxical 0.7.3 → 0.9.3
Sign up to get free protection for your applications and to get access to all the features.
- 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
|