periodoxical 0.7.3 → 0.8.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: 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