periodoxical 0.9.3 → 1.0.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 +4 -4
- data/CODE_OF_CONDUCT.md +1 -1
- data/Gemfile.lock +1 -1
- data/README.md +41 -17
- data/lib/periodoxical/validation.rb +2 -2
- data/lib/periodoxical/version.rb +1 -1
- data/lib/periodoxical.rb +60 -12
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 3836bd7c49ec987409e70592d93cb8c021a009ce3587e48a6419c12121af3b1d
|
|
4
|
+
data.tar.gz: 510bb79570482c8019e911c8900ec15ee5016b59c1d51677d38d53f7c632523f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 0afbe7ebba86480f7d04766a087020734135bb0075245fbe6ba03f75590eb0c74710991b14e5fad653779ba7d6a74811d11adaeb7cfa28ec194dc9eea05adfb9
|
|
7
|
+
data.tar.gz: ddc843c9de7c3ae536f939f0c7bfc6c6d5ce2a37d4bde07d2941bd2ef7f7e0a41328d347fe1e9d8d1c1e1ba9bf916648424bd18795ad8642531d37529841d913
|
data/CODE_OF_CONDUCT.md
CHANGED
|
@@ -55,7 +55,7 @@ further defined and clarified by project maintainers.
|
|
|
55
55
|
## Enforcement
|
|
56
56
|
|
|
57
57
|
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
|
58
|
-
reported by contacting the project team at
|
|
58
|
+
reported by contacting the project team at stevenJLi@gmail.com. All
|
|
59
59
|
complaints will be reviewed and investigated and will result in a response that
|
|
60
60
|
is deemed necessary and appropriate to the circumstances. The project team is
|
|
61
61
|
obligated to maintain confidentiality with regard to the reporter of an incident.
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
|
@@ -41,8 +41,8 @@ Periodoxical.generate(
|
|
|
41
41
|
end_time: '10:30AM'
|
|
42
42
|
},
|
|
43
43
|
],
|
|
44
|
-
|
|
45
|
-
|
|
44
|
+
starting_from: '2024-05-23',
|
|
45
|
+
ending_at: '2024-05-26',
|
|
46
46
|
)
|
|
47
47
|
#=>
|
|
48
48
|
[
|
|
@@ -65,6 +65,30 @@ Periodoxical.generate(
|
|
|
65
65
|
]
|
|
66
66
|
```
|
|
67
67
|
|
|
68
|
+
The `starting_from` and `ending_at` params can also accept datetimes in ISO 8601 format for more precision. This example generate all the datetime blocks of **9:00AM - 10:30AM** but starting from **May 23, 2024 at 9:30AM**.
|
|
69
|
+
|
|
70
|
+
```rb
|
|
71
|
+
Periodoxical.generate(
|
|
72
|
+
time_zone: 'America/Los_Angeles',
|
|
73
|
+
time_blocks: [
|
|
74
|
+
{
|
|
75
|
+
start_time: '9:00AM',
|
|
76
|
+
end_time: '10:30AM'
|
|
77
|
+
},
|
|
78
|
+
],
|
|
79
|
+
starting_from: '2024-05-23T09:30:00-07:00', # can be string in iso8601 format
|
|
80
|
+
ending_at: DateTime.parse('2024-05-26T17:00:00-07:00'), # or an instance of DateTime
|
|
81
|
+
)
|
|
82
|
+
#=> [
|
|
83
|
+
# 2024-05-23 was skipped because the 9AM timeslot was before the `starting_from` of '2024-05-23T09:30:00-07:00'
|
|
84
|
+
{
|
|
85
|
+
start_time: #<DateTime: 2024-05-24T09:00:00-0700>,
|
|
86
|
+
end_time: #<DateTime: 2024-05-24T10:30:00-0700>,
|
|
87
|
+
},
|
|
88
|
+
...
|
|
89
|
+
]
|
|
90
|
+
```
|
|
91
|
+
|
|
68
92
|
### Example 2 - specify days of the week
|
|
69
93
|
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
94
|
|
|
@@ -89,8 +113,8 @@ Periodoxical.generate(
|
|
|
89
113
|
end_time: '2:30PM'
|
|
90
114
|
}
|
|
91
115
|
],
|
|
92
|
-
|
|
93
|
-
|
|
116
|
+
starting_from: '2024-05-23',
|
|
117
|
+
ending_at: '2024-06-12',
|
|
94
118
|
)
|
|
95
119
|
# returns an array of hashes, each with :start and :end keys
|
|
96
120
|
#=>
|
|
@@ -133,7 +157,7 @@ Periodoxical.generate(
|
|
|
133
157
|
end_time: '2:30PM'
|
|
134
158
|
}
|
|
135
159
|
],
|
|
136
|
-
|
|
160
|
+
starting_from: Date.parse('2024-05-23'), # Can also pass in `Date` object.
|
|
137
161
|
limit: 3
|
|
138
162
|
)
|
|
139
163
|
# =>
|
|
@@ -167,8 +191,8 @@ As a ruby dev, I want to generate all the timeblocks between **May 23, 2024** an
|
|
|
167
191
|
```rb
|
|
168
192
|
Periodoxical.generate(
|
|
169
193
|
time_zone: 'America/Los_Angeles',
|
|
170
|
-
|
|
171
|
-
|
|
194
|
+
starting_from: Date.parse('2024-05-23'), # can also pass in Date objects
|
|
195
|
+
ending_at: Date.parse('2024-06-12'), # can also pass in Date objects,
|
|
172
196
|
day_of_week_time_blocks: {
|
|
173
197
|
mon: [
|
|
174
198
|
{ start_time: '8:00AM', end_time: '9:00AM' },
|
|
@@ -191,7 +215,7 @@ As a Ruby dev, I want to generate the next 3 timeblocks for **8AM - 9AM** for th
|
|
|
191
215
|
```rb
|
|
192
216
|
Periodoxical.generate(
|
|
193
217
|
time_zone: 'America/Los_Angeles',
|
|
194
|
-
|
|
218
|
+
starting_from: '2024-06-01',
|
|
195
219
|
limit: 3,
|
|
196
220
|
days_of_month: [5, 10],
|
|
197
221
|
time_blocks: [
|
|
@@ -220,7 +244,7 @@ As a Ruby dev, I want to generate **4** timeblocks for **8AM - 9AM** on **Monday
|
|
|
220
244
|
```
|
|
221
245
|
Periodoxical.generate(
|
|
222
246
|
time_zone: 'America/Los_Angeles',
|
|
223
|
-
|
|
247
|
+
starting_from: '2024-04-01',
|
|
224
248
|
limit: 4,
|
|
225
249
|
weeks_of_month: [1 2],
|
|
226
250
|
months: [4, 5, 6],
|
|
@@ -256,7 +280,7 @@ As a Ruby dev, I want to generate timeblocks for **8AM - 9AM** on the **first an
|
|
|
256
280
|
```rb
|
|
257
281
|
Periodoxical.generate(
|
|
258
282
|
time_zone: 'America/Los_Angeles',
|
|
259
|
-
|
|
283
|
+
starting_from: '2024-06-01',
|
|
260
284
|
limit: 5,
|
|
261
285
|
nth_day_of_week_in_month: {
|
|
262
286
|
mon: [1, 2], # valid values: -1,1,2,3,4,5
|
|
@@ -297,7 +321,7 @@ As a Ruby dev, I want to generate timeblocks for **8AM - 9AM** on **Mondays**, e
|
|
|
297
321
|
```rb
|
|
298
322
|
Periodoxical.generate(
|
|
299
323
|
time_zone: 'America/Los_Angeles',
|
|
300
|
-
|
|
324
|
+
starting_from: '2024-06-03',
|
|
301
325
|
limit: 4,
|
|
302
326
|
exclusion_dates: %w(2024-06-10),
|
|
303
327
|
day_of_week_time_blocks: {
|
|
@@ -333,7 +357,7 @@ As a Ruby dev, I want to generate timeblocks for **8AM - 9AM**, and **10AM - 11A
|
|
|
333
357
|
```rb
|
|
334
358
|
Periodoxical.generate(
|
|
335
359
|
time_zone: 'America/Los_Angeles',
|
|
336
|
-
|
|
360
|
+
starting_from: '2024-06-03',
|
|
337
361
|
limit: 4,
|
|
338
362
|
days_of_week: %(mon),
|
|
339
363
|
time_blocks: [
|
|
@@ -393,11 +417,11 @@ This can be visualized as:
|
|
|
393
417
|
```rb
|
|
394
418
|
Periodoxical.generate(
|
|
395
419
|
time_zone: 'America/Los_Angeles',
|
|
396
|
-
|
|
420
|
+
starting_from: '2024-12-30',
|
|
397
421
|
days_of_week: {
|
|
398
422
|
mon: { every: true }, # every Monday (no skipping)
|
|
399
|
-
tue: { every_other_nth: 2 }, # every other Tuesday starting at first Tuesday from
|
|
400
|
-
wed: { every_other_nth: 3 }, # every 3rd Wednesday starting at first Wednesday from
|
|
423
|
+
tue: { every_other_nth: 2 }, # every other Tuesday starting at first Tuesday from `starting_from` date
|
|
424
|
+
wed: { every_other_nth: 3 }, # every 3rd Wednesday starting at first Wednesday from `starting_from` date
|
|
401
425
|
},
|
|
402
426
|
limit: 10,
|
|
403
427
|
time_blocks: [
|
|
@@ -456,7 +480,7 @@ Generate all the Friday the 13ths ever since May 1980 (when the first Friday the
|
|
|
456
480
|
```rb
|
|
457
481
|
Periodoxical.generate(
|
|
458
482
|
time_zone: 'America/Los_Angeles',
|
|
459
|
-
|
|
483
|
+
starting_from: '1980-05-01',
|
|
460
484
|
days_of_week: %w(fri),
|
|
461
485
|
days_of_month: [13],
|
|
462
486
|
limit: 100,
|
|
@@ -491,7 +515,7 @@ Generate the next 10 Thanksgivings from now on (Thanksgivings is defined as the
|
|
|
491
515
|
```rb
|
|
492
516
|
Periodoxical.generate(
|
|
493
517
|
time_zone: 'America/Los_Angeles',
|
|
494
|
-
|
|
518
|
+
starting_from: '2024-05-01',
|
|
495
519
|
months: [11],
|
|
496
520
|
nth_day_of_week_in_month: {
|
|
497
521
|
thu: [4],
|
|
@@ -96,8 +96,8 @@ module Periodoxical
|
|
|
96
96
|
end
|
|
97
97
|
end
|
|
98
98
|
|
|
99
|
-
unless( @limit || @
|
|
100
|
-
raise "Either `limit` or `
|
|
99
|
+
unless( @limit || @ending_at)
|
|
100
|
+
raise "Either `limit` or `ending_at` must be provided"
|
|
101
101
|
end
|
|
102
102
|
|
|
103
103
|
if @exclusion_times
|
data/lib/periodoxical/version.rb
CHANGED
data/lib/periodoxical.rb
CHANGED
|
@@ -17,8 +17,8 @@ module Periodoxical
|
|
|
17
17
|
# @param [String] time_zone
|
|
18
18
|
# Ex: 'America/Los_Angeles', 'America/Chicago',
|
|
19
19
|
# TZInfo::DataTimezone#name from the tzinfo gem (https://github.com/tzinfo/tzinfo)
|
|
20
|
-
# @param [Date, String]
|
|
21
|
-
# @param [Date, String]
|
|
20
|
+
# @param [Date, String] starting_from
|
|
21
|
+
# @param [Date, String] ending_at
|
|
22
22
|
# @param [Array<Hash>] time_blocks
|
|
23
23
|
# Ex: [
|
|
24
24
|
# {
|
|
@@ -40,7 +40,7 @@ module Periodoxical
|
|
|
40
40
|
# @param [Array<Integer>, nil] months
|
|
41
41
|
# Months as integers, where 1 = Jan, 12 = Dec
|
|
42
42
|
# @param [Integer] limit
|
|
43
|
-
# How many date times to generate. To be used when `
|
|
43
|
+
# How many date times to generate. To be used when `ending_at` is nil.
|
|
44
44
|
# @param [Aray<String>] exclusion_dates
|
|
45
45
|
# Dates to be excluded when generating the time blocks
|
|
46
46
|
# Ex: ['2024-06-10', '2024-06-14']
|
|
@@ -65,8 +65,8 @@ module Periodoxical
|
|
|
65
65
|
# fri: { start_time: '7:00PM', end_time: '9:00PM' },
|
|
66
66
|
# }
|
|
67
67
|
def initialize(
|
|
68
|
-
|
|
69
|
-
|
|
68
|
+
starting_from:,
|
|
69
|
+
ending_at: nil,
|
|
70
70
|
time_blocks: nil,
|
|
71
71
|
day_of_week_time_blocks: nil,
|
|
72
72
|
limit: nil,
|
|
@@ -92,8 +92,8 @@ module Periodoxical
|
|
|
92
92
|
@months = months
|
|
93
93
|
@time_blocks = time_blocks
|
|
94
94
|
@day_of_week_time_blocks = day_of_week_time_blocks
|
|
95
|
-
@
|
|
96
|
-
@
|
|
95
|
+
@starting_from = date_object_from(starting_from)
|
|
96
|
+
@ending_at = date_object_from(ending_at)
|
|
97
97
|
@limit = limit
|
|
98
98
|
@exclusion_dates = if exclusion_dates && !exclusion_dates.empty?
|
|
99
99
|
exclusion_dates.map { |ed| Date.parse(ed) }
|
|
@@ -174,7 +174,11 @@ module Periodoxical
|
|
|
174
174
|
# Variables which manage flow of looping through time and generating slots
|
|
175
175
|
def initialize_looping_variables!
|
|
176
176
|
@output = []
|
|
177
|
-
|
|
177
|
+
if @starting_from.is_a?(DateTime)
|
|
178
|
+
@current_date = @starting_from.to_date
|
|
179
|
+
else
|
|
180
|
+
@current_date = @starting_from
|
|
181
|
+
end
|
|
178
182
|
@current_day_of_week = day_of_week_long_to_short(@current_date.strftime("%A"))
|
|
179
183
|
@current_count = 0
|
|
180
184
|
@keep_generating = true
|
|
@@ -205,6 +209,7 @@ module Periodoxical
|
|
|
205
209
|
def append_to_output_and_check_limit(time_block)
|
|
206
210
|
# Check if this particular time is conflicts with any times from `exclusion_times`.
|
|
207
211
|
return if overlaps_with_an_excluded_time?(time_block)
|
|
212
|
+
return if before_starting_from_or_after_ending_at?(time_block)
|
|
208
213
|
|
|
209
214
|
@output << {
|
|
210
215
|
start: time_str_to_object(@current_date, time_block[:start_time]),
|
|
@@ -224,7 +229,7 @@ module Periodoxical
|
|
|
224
229
|
|
|
225
230
|
@current_day_of_week = day_of_week_long_to_short(@current_date.strftime("%A"))
|
|
226
231
|
|
|
227
|
-
if @
|
|
232
|
+
if @ending_at && (@current_date > @ending_at)
|
|
228
233
|
@keep_generating = false
|
|
229
234
|
end
|
|
230
235
|
|
|
@@ -232,7 +237,7 @@ module Periodoxical
|
|
|
232
237
|
# there is bug, or poorly specified rules. If @current_date goes into
|
|
233
238
|
# 1000 years in the future, but still no dates have been generated yet, this is
|
|
234
239
|
# most likely an infinite loop situation, and needs to be killed.
|
|
235
|
-
if @limit && ((@current_date - @
|
|
240
|
+
if @limit && ((@current_date - @starting_from).to_i > 365000) && @output.empty?
|
|
236
241
|
raise "No end condition detected, causing infinite loop. Please check rules/conditions or raise github issue for potential bug fixed"
|
|
237
242
|
end
|
|
238
243
|
end
|
|
@@ -324,8 +329,8 @@ module Periodoxical
|
|
|
324
329
|
# end_time: '10:30AM'
|
|
325
330
|
# },
|
|
326
331
|
# ],
|
|
327
|
-
#
|
|
328
|
-
#
|
|
332
|
+
# starting_from: '2024-05-23',
|
|
333
|
+
# ending_at: '2024-05-27',
|
|
329
334
|
# )
|
|
330
335
|
# where if we don't specify any date-of-week/month constraints, we return all consecutive dates.
|
|
331
336
|
# In the future, if we don't support this case, we can use `false` as the return value.
|
|
@@ -373,6 +378,29 @@ module Periodoxical
|
|
|
373
378
|
@days_of_week_running_tally[@current_day_of_week.to_sym] = @days_of_week_running_tally[@current_day_of_week.to_sym] + 1
|
|
374
379
|
end
|
|
375
380
|
|
|
381
|
+
# @return [Boolean]
|
|
382
|
+
# Used only when `starting_from` and `ending_at` are instances of DateTime
|
|
383
|
+
# instead of Date, requiring more precision, calculation.
|
|
384
|
+
def before_starting_from_or_after_ending_at?(time_block)
|
|
385
|
+
return false unless @starting_from.is_a?(DateTime) || @ending_at.is_a?(DateTime)
|
|
386
|
+
|
|
387
|
+
if @starting_from.is_a?(DateTime)
|
|
388
|
+
start_time = time_str_to_object(@current_date, time_block[:start_time])
|
|
389
|
+
|
|
390
|
+
# If the candidate time block is starting earlier than @starting_from, we want to skip it
|
|
391
|
+
return true if start_time < @starting_from
|
|
392
|
+
end
|
|
393
|
+
|
|
394
|
+
if @ending_at.is_a?(DateTime)
|
|
395
|
+
end_time = time_str_to_object(@current_date, time_block[:end_time])
|
|
396
|
+
|
|
397
|
+
# If the candidate time block is ending after @ending_at, we want to skip it
|
|
398
|
+
return true if end_time > @ending_at
|
|
399
|
+
end
|
|
400
|
+
|
|
401
|
+
false
|
|
402
|
+
end
|
|
403
|
+
|
|
376
404
|
# @return [Boolean]
|
|
377
405
|
# Whether or not the given `time_block` in the @current_date and
|
|
378
406
|
# @time_zone overlaps with the times in `exclusion_times`.
|
|
@@ -410,5 +438,25 @@ module Periodoxical
|
|
|
410
438
|
|
|
411
439
|
false
|
|
412
440
|
end
|
|
441
|
+
|
|
442
|
+
def date_object_from(dt)
|
|
443
|
+
return unless dt
|
|
444
|
+
return dt if dt.is_a?(Date) || dt.is_a?(DateTime)
|
|
445
|
+
|
|
446
|
+
if dt.is_a?(String)
|
|
447
|
+
return Date.parse(dt) if /\A\d{4}-(0?[1-9]|1[0-2])-(0?[1-9]|[12]\d|3[01])\z/ =~ dt
|
|
448
|
+
|
|
449
|
+
if /\A\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])T([01]\d|2[0-3]):[0-5]\d:[0-5]\d(\.\d+)?(Z|[+-][01]\d:[0-5]\d)?\z/ =~ dt
|
|
450
|
+
# convert to DateTime object
|
|
451
|
+
dt = DateTime.parse(dt)
|
|
452
|
+
# convert to given time_zone
|
|
453
|
+
return dt.to_time.localtime(@time_zone.utc_offset).to_datetime
|
|
454
|
+
end
|
|
455
|
+
|
|
456
|
+
raise "Could not parse date/datetime string #{dt}. Please README for examples."
|
|
457
|
+
else
|
|
458
|
+
raise "Invalid argument: #{dt}"
|
|
459
|
+
end
|
|
460
|
+
end
|
|
413
461
|
end
|
|
414
462
|
end
|