periodoxical 0.6.2 → 0.7.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1c90e3908492b944735cc23b5da95746309133de5b54413495d4ed49e0d2dce3
4
- data.tar.gz: 7fa63855a49e2c6feb9955007361aa6d8c9262f847e6fc0fc159c47f95ed1db5
3
+ metadata.gz: 63ed27b7ac9376b1cd35d505dbcf13c6637e47a8bee5983aa8697a389c2423ee
4
+ data.tar.gz: df71967f5be8771b0bda25c4b6352f922a82bac39a546f2f66e04247a41ea3cb
5
5
  SHA512:
6
- metadata.gz: 36ba01fb5173e0fae2dea0eeba563145b373860989891f2640b468ee688b0cdd5d78b7f5b44e7d52f8d137cd80940ebe8e9b75b997dc144d0d159982bd15aa87
7
- data.tar.gz: 5e678e7218811a7d5f0e779a2b766e2099742554414ee007078af87b0fc10f196900672c5fe0daa58b978ce1c4e31f9f08bfc21c0573f257f48823c211acdd74
6
+ metadata.gz: ce019309287e5c8d065e77ce9cdc31348c12d867ba9140a43cfb2da2a086a647dcfd0ef0e1a801c11b41f3794fb0b056d695a7f079ad87e5e77e75bae1646bc2
7
+ data.tar.gz: f8ae7b9cdc4e395618665f5b625624418bdc8d343fdc4e4dccd7aa8af9672671d2d02230d344193ac016ab1ada3468f3e9089b7fcc3b41a110f974d04c5f1486
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- periodoxical (0.6.2)
4
+ periodoxical (0.7.3)
5
5
  tzinfo (~> 2.0, >= 2.0.0)
6
6
  week_of_month (= 1.2.6)
7
7
 
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.3"
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
@@ -85,7 +87,11 @@ module Periodoxical
85
87
  # {
86
88
  # start: #<DateTime>,
87
89
  # end: #<DateTime>,
88
- # }
90
+ # },
91
+ # {
92
+ # start: #<DateTime>,
93
+ # end: #<DateTime>,
94
+ # },
89
95
  # ]
90
96
  def generate
91
97
  initialize_looping_variables!
@@ -105,6 +111,10 @@ module Periodoxical
105
111
  raise "`day_of_week_time_blocks` or `time_blocks` need to be provided"
106
112
  end
107
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
+
108
118
  if @weeks_of_month
109
119
  @weeks_of_month.each do |wom|
110
120
  unless wom.is_a?(Integer) && wom.between?(1, 5)
@@ -122,6 +132,28 @@ module Periodoxical
122
132
  end
123
133
  end
124
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
+
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)
@@ -196,6 +228,7 @@ module Periodoxical
196
228
  def initialize_looping_variables!
197
229
  @output = []
198
230
  @current_date = @start_date
231
+ @current_day_of_week = day_of_week_long_to_short(@current_date.strftime("%A"))
199
232
  @current_count = 0
200
233
  @keep_generating = true
201
234
  end
@@ -223,10 +256,19 @@ module Periodoxical
223
256
 
224
257
  def advance_current_date_and_check_if_reached_end_date
225
258
  @current_date = @current_date + 1
259
+ @current_day_of_week = day_of_week_long_to_short(@current_date.strftime("%A"))
226
260
 
227
261
  if @end_date && (@current_date > @end_date)
228
262
  @keep_generating = false
229
263
  end
264
+
265
+ # kill switch to stop infinite loop when `limit` is used but
266
+ # there is bug, or poorly specified rules. If @current_date goes into
267
+ # 1000 years in the future, but still no dates have been generated yet, this is
268
+ # most likely an infinite loop situation, and needs to be killed.
269
+ if @limit && ((@current_date - @start_date).to_i > 365000) && @output.empty?
270
+ raise "No end condition detected, causing infinite loop. Please check rules/conditions or raise github issue for potential bug fixed"
271
+ end
230
272
  end
231
273
 
232
274
  # @return [Boolean]
@@ -257,25 +299,41 @@ module Periodoxical
257
299
  # If days of week are specified, but current_date does not satisfy it,
258
300
  # return false
259
301
  if @days_of_week
260
- day_of_week = day_of_week_long_to_short(@current_date.strftime("%A"))
261
- return false unless @days_of_week.include?(day_of_week)
302
+ return false unless @days_of_week.include?(@current_day_of_week)
262
303
  end
263
304
 
264
305
  if @day_of_week_time_blocks
265
- day_of_week = day_of_week_long_to_short(@current_date.strftime("%A"))
266
- dowtb = @day_of_week_time_blocks[day_of_week.to_sym]
306
+ dowtb = @day_of_week_time_blocks[@current_day_of_week.to_sym]
267
307
  return false if dowtb.nil?
268
308
  return false if dowtb.empty?
269
309
  end
270
310
 
311
+ if @nth_day_of_week_in_month
312
+ # If the day of week is specified in nth_day_of_week_in_month,
313
+ # we need to investigate it whether or not to exclude it.
314
+ if @nth_day_of_week_in_month[@current_day_of_week.to_sym]
315
+ n_occurence_of_day_of_week_in_month = ((@current_date.day - 1) / 7) + 1
316
+ # -1 is a special case and requires extra-math
317
+ if @nth_day_of_week_in_month[@current_day_of_week.to_sym].include?(-1)
318
+ # We basically want to convert the -1 into its 'positive-equivalent` in this month, and compare it with that.
319
+ # For example, in June 2024, the Last Friday is also the 4th Friday. So in that case, we convert the -1 into a 4.
320
+ positivized_indices = @nth_day_of_week_in_month[@current_day_of_week.to_sym].map { |indx| positivize_index(indx, @current_day_of_week) }
321
+ return positivized_indices.include?(n_occurence_of_day_of_week_in_month)
322
+ else
323
+ return @nth_day_of_week_in_month[@current_day_of_week.to_sym].include?(n_occurence_of_day_of_week_in_month)
324
+ end
325
+ else
326
+ return false
327
+ end
328
+ end
329
+
271
330
  # Otherwise, return true
272
331
  true
273
332
  end
274
333
 
275
334
  def add_time_blocks_from_current_date!
276
335
  if @day_of_week_time_blocks
277
- day_of_week = day_of_week_long_to_short(@current_date.strftime("%A"))
278
- time_blocks = @day_of_week_time_blocks[day_of_week.to_sym]
336
+ time_blocks = @day_of_week_time_blocks[@current_day_of_week.to_sym]
279
337
  catch :done do
280
338
  time_blocks.each do |tb|
281
339
  append_to_output_and_check_limit(tb)
@@ -289,5 +347,25 @@ module Periodoxical
289
347
  end
290
348
  end
291
349
  end
350
+
351
+ # What is the positive index of the last day-of-week for the given month-year?
352
+ # For example, the last Friday in June 2024 is also the nth Friday. What is this n?
353
+ # @return [Integer]
354
+ def positivize_index(indx, day_of_week)
355
+ # If index is already positive, just return it
356
+ return indx if indx > 0
357
+
358
+ # get last_day_of month
359
+ month = @current_date.month
360
+ year = @current_date.year
361
+ last_date = Date.new(year, month, -1)
362
+
363
+ # walk backwords until you get to the right day of the week
364
+ while day_of_week_long_to_short(last_date.strftime("%A")) != day_of_week
365
+ last_date = last_date - 1
366
+ end
367
+
368
+ ((last_date.day - 1) / 7) + 1
369
+ end
292
370
  end
293
371
  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.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-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