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