periodoxical 0.6.2 → 0.7.3

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