periodoxical 0.7.3 → 0.8.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: 63ed27b7ac9376b1cd35d505dbcf13c6637e47a8bee5983aa8697a389c2423ee
4
- data.tar.gz: df71967f5be8771b0bda25c4b6352f922a82bac39a546f2f66e04247a41ea3cb
3
+ metadata.gz: 78125f7c8e33a8588c933d7b0438daa5d0913394fe9c141e752dd73fc1968d88
4
+ data.tar.gz: 60acbba0684d36991ac7bffcb21dc8a97e27bfb313d1ab9641668463e73c718c
5
5
  SHA512:
6
- metadata.gz: ce019309287e5c8d065e77ce9cdc31348c12d867ba9140a43cfb2da2a086a647dcfd0ef0e1a801c11b41f3794fb0b056d695a7f079ad87e5e77e75bae1646bc2
7
- data.tar.gz: f8ae7b9cdc4e395618665f5b625624418bdc8d343fdc4e4dccd7aa8af9672671d2d02230d344193ac016ab1ada3468f3e9089b7fcc3b41a110f974d04c5f1486
6
+ metadata.gz: df7a8cc7abe1139af195aebe455feb7c66b75e27e3a8d43e4c862ec362111756a461b7293efe3c3fdcca50eb1f70d5fdb5de04e740014a4fc33fe3f324e8d6ec
7
+ data.tar.gz: '070876a087c2712b12f497054966f9cd98ac3d0e3eab1e4337e165e2ea42cef5ea4dd5b43b714393d92d14ddab2119b426128cf9d85f5de845b9e32959f72b1d'
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- periodoxical (0.7.3)
4
+ periodoxical (0.8.3)
5
5
  tzinfo (~> 2.0, >= 2.0.0)
6
6
  week_of_month (= 1.2.6)
7
7
 
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 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 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** slots for **8AM - 9AM** on **Mondays** but only in the **first two weeks** in the months of **April, May, and June**
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 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.
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(
@@ -292,7 +292,7 @@ Periodoxical.generate(
292
292
  ```
293
293
 
294
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**.
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,69 @@ Periodoxical.generate(
328
328
  ]
329
329
  ```
330
330
 
331
+ ### Example 8 - Every-other-nth day-of-week rules (ie. every other Tuesday, every 3rd Wednesday, every 10th Friday)
332
+
333
+ 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.
334
+
335
+ ```rb
336
+ Periodoxical.generate(
337
+ time_zone: 'America/Los_Angeles',
338
+ start_date: '2024-12-30',
339
+ days_of_week: {
340
+ mon: { every: true }, # every Monday (no skipping)
341
+ tue: { every_other_nth: 2 }, # every other Tuesday starting at first Tuesday from start date
342
+ wed: { every_other_nth: 3 }, # every 3rd Wednesday starting at first Wednesday from start date
343
+ },
344
+ limit: 10,
345
+ time_blocks: [
346
+ { start_time: '9:00AM', end_time: '10:00AM' },
347
+ ],
348
+ )
349
+ #=>
350
+ [
351
+ {
352
+ start: #<DateTime: 2024-12-30T09:00:00-0800>,
353
+ end: #<DateTime: 2024-12-30T10:00:00-0800>,
354
+ },
355
+ {
356
+ start: #<DateTime: 2024-12-31T09:00:00-0800>,
357
+ end: #<DateTime: 2024-12-31T10:00:00-0800>,
358
+ },
359
+ {
360
+ start: #<DateTime: 2025-01-01T09:00:00-0800>,
361
+ end: #<DateTime: 2025-01-01T10:00:00-0800>,
362
+ },
363
+ {
364
+ start: #<DateTime: 2025-01-06T09:00:00-0800>,
365
+ end: #<DateTime: 2025-01-06T10:00:00-0800>,
366
+ },
367
+ {
368
+ start: #<DateTime: 2025-01-13T09:00:00-0800>,
369
+ end: #<DateTime: 2025-01-13T10:00:00-0800>,
370
+ },
371
+ {
372
+ start: #<DateTime: 2025-01-14T09:00:00-0800>,
373
+ end: #<DateTime: 2025-01-14T10:00:00-0800>,
374
+ },
375
+ {
376
+ start: #<DateTime: 2025-01-20T09:00:00-0800>,
377
+ end: #<DateTime: 2025-01-20T10:00:00-0800>,
378
+ },
379
+ {
380
+ start: #<DateTime: 2025-01-22T09:00:00-0800>,
381
+ end: #<DateTime: 2025-01-22T10:00:00-0800>,
382
+ },
383
+ {
384
+ start: #<DateTime: 2025-01-27T09:00:00-0800>,
385
+ end: #<DateTime: 2025-01-27T10:00:00-0800>,
386
+ },
387
+ {
388
+ start: #<DateTime: 2025-01-28T09:00:00-0800>,
389
+ end: #<DateTime: 2025-01-28T10:00:00-0800>,
390
+ }
391
+ ]
392
+ ```
393
+
331
394
  ### Having Some Fun
332
395
 
333
396
  Generate all the Friday the 13ths ever since May 1980 (when the first Friday the 13th film was released).
@@ -0,0 +1,104 @@
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
+ end
103
+ end
104
+ end
@@ -1,3 +1,3 @@
1
1
  module Periodoxical
2
- VERSION = "0.7.3"
2
+ VERSION = "0.8.3"
3
3
  end
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
- VALID_DAYS_OF_WEEK = %w[mon tue wed thu fri sat sun].freeze
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)
@@ -66,7 +67,11 @@ module Periodoxical
66
67
  )
67
68
 
68
69
  @time_zone = TZInfo::Timezone.get(time_zone)
69
- @days_of_week = days_of_week
70
+ if days_of_week.is_a?(Array)
71
+ @days_of_week = days_of_week
72
+ elsif days_of_week.is_a?(Hash)
73
+ @days_of_week_with_alternations = days_of_week
74
+ end
70
75
  @nth_day_of_week_in_month = nth_day_of_week_in_month
71
76
  @days_of_month = days_of_month
72
77
  @weeks_of_month = weeks_of_month
@@ -106,83 +111,6 @@ module Periodoxical
106
111
 
107
112
  private
108
113
 
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
114
  def day_of_week_long_to_short(dow)
187
115
  {
188
116
  "Monday" => "mon",
@@ -231,6 +159,21 @@ module Periodoxical
231
159
  @current_day_of_week = day_of_week_long_to_short(@current_date.strftime("%A"))
232
160
  @current_count = 0
233
161
  @keep_generating = true
162
+ # When there are alternations in days of week
163
+ # (ie. every other Monday, every 3rd Thursday, etc).
164
+ # We keep running tally of day_of_week counts and use modulo-math to pick out
165
+ # every n-th one.
166
+ if @days_of_week_with_alternations
167
+ @days_of_week_running_tally = {
168
+ mon: 0,
169
+ tue: 0,
170
+ wed: 0,
171
+ thu: 0,
172
+ fri: 0,
173
+ sat: 0,
174
+ sun: 0,
175
+ }
176
+ end
234
177
  end
235
178
 
236
179
  # @param [Hash] time_block
@@ -256,6 +199,7 @@ module Periodoxical
256
199
 
257
200
  def advance_current_date_and_check_if_reached_end_date
258
201
  @current_date = @current_date + 1
202
+
259
203
  @current_day_of_week = day_of_week_long_to_short(@current_date.strftime("%A"))
260
204
 
261
205
  if @end_date && (@current_date > @end_date)
@@ -302,6 +246,28 @@ module Periodoxical
302
246
  return false unless @days_of_week.include?(@current_day_of_week)
303
247
  end
304
248
 
249
+ if @days_of_week_with_alternations
250
+ # current_date is not specified in days_of_week, so skip it
251
+ return false if @days_of_week_with_alternations[@current_day_of_week.to_sym].nil?
252
+
253
+ alternating_spec = @days_of_week_with_alternations[@current_day_of_week.to_sym]
254
+
255
+ # In the { every: true } case, we don't check the alternations logic, we just add it.
256
+ unless alternating_spec[:every]
257
+ # We are now specifying every other nth occurrence (ie. every 2nd Tuesday, every 3rd Wednesday)
258
+ alternating_frequency = alternating_spec[:every_other_nth]
259
+
260
+ unless (@days_of_week_running_tally[@current_day_of_week.to_sym] % alternating_frequency) == 0
261
+ # If day-of-week alternations are present, we need to keep track of day-of-weeks
262
+ # we have encountered and added or would have added so far.
263
+ update_days_of_week_running_tally!
264
+
265
+ return false
266
+ end
267
+ update_days_of_week_running_tally!
268
+ end
269
+ end
270
+
305
271
  if @day_of_week_time_blocks
306
272
  dowtb = @day_of_week_time_blocks[@current_day_of_week.to_sym]
307
273
  return false if dowtb.nil?
@@ -327,7 +293,20 @@ module Periodoxical
327
293
  end
328
294
  end
329
295
 
330
- # Otherwise, return true
296
+ # The default return true is really only needed to support this use-case:
297
+ # Periodoxical.generate(
298
+ # time_zone: 'America/Los_Angeles',
299
+ # time_blocks: [
300
+ # {
301
+ # start_time: '9:00AM',
302
+ # end_time: '10:30AM'
303
+ # },
304
+ # ],
305
+ # start_date: '2024-05-23',
306
+ # end_date: '2024-05-27',
307
+ # )
308
+ # where if we don't specify any date-of-week/month constraints, we return all consecutive dates.
309
+ # In the future, if we don't support this case, we can use `false` as the return value.
331
310
  true
332
311
  end
333
312
 
@@ -367,5 +346,9 @@ module Periodoxical
367
346
 
368
347
  ((last_date.day - 1) / 7) + 1
369
348
  end
349
+
350
+ def update_days_of_week_running_tally!
351
+ @days_of_week_running_tally[@current_day_of_week.to_sym] = @days_of_week_running_tally[@current_day_of_week.to_sym] + 1
352
+ end
370
353
  end
371
354
  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.7.3
4
+ version: 0.8.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-02 00:00:00.000000000 Z
11
+ date: 2024-06-03 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