periodoxical 0.5.2 → 0.6.2

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: fa1a430d11ca7fdd0cea524011b7164f926f87bad37a49c4848bdbca934bdc79
4
- data.tar.gz: fea338a55f3b095b4ac6c1ff57f235e8490a9b6683ae48b9a573b0b1afba22f0
3
+ metadata.gz: 1c90e3908492b944735cc23b5da95746309133de5b54413495d4ed49e0d2dce3
4
+ data.tar.gz: 7fa63855a49e2c6feb9955007361aa6d8c9262f847e6fc0fc159c47f95ed1db5
5
5
  SHA512:
6
- metadata.gz: 74ec882770d5567ad292ad26c020777d9075cbb0bf77259f37fa0713ab239772dea1dd52fadb2d415ceb8820a10cbc2572ee4847ac181ae208d81d6d759b0517
7
- data.tar.gz: e2a7c7b00eec3522ce4d2bd15a999a7f86de8826245c464f0e0844b3f7cd42d7eb942d80e7cad2ecbc48d66399f4962fd9f7be7d8a9d4a025e51d79b477112e2
6
+ metadata.gz: 36ba01fb5173e0fae2dea0eeba563145b373860989891f2640b468ee688b0cdd5d78b7f5b44e7d52f8d137cd80940ebe8e9b75b997dc144d0d159982bd15aa87
7
+ data.tar.gz: 5e678e7218811a7d5f0e779a2b766e2099742554414ee007078af87b0fc10f196900672c5fe0daa58b978ce1c4e31f9f08bfc21c0573f257f48823c211acdd74
data/Gemfile.lock CHANGED
@@ -1,8 +1,9 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- periodoxical (0.5.2)
4
+ periodoxical (0.6.2)
5
5
  tzinfo (~> 2.0, >= 2.0.0)
6
+ week_of_month (= 1.2.6)
6
7
 
7
8
  GEM
8
9
  remote: https://rubygems.org/
@@ -40,6 +41,7 @@ GEM
40
41
  rspec-support (3.13.1)
41
42
  tzinfo (2.0.6)
42
43
  concurrent-ruby (~> 1.0)
44
+ week_of_month (1.2.6)
43
45
 
44
46
  PLATFORMS
45
47
  arm64-darwin-22
data/README.md CHANGED
@@ -30,7 +30,43 @@ Or install it yourself as:
30
30
  ## Usage
31
31
 
32
32
  #### Example 1
33
- As a Ruby dev, I want to generate all the datetimes 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:
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
+
35
+ ```rb
36
+ Periodoxical.generate(
37
+ time_zone: 'America/Los_Angeles',
38
+ time_blocks: [
39
+ {
40
+ start_time: '9:00AM',
41
+ end_time: '10:30AM'
42
+ },
43
+ ],
44
+ start_date: '2024-05-23',
45
+ end_date: '2024-05-27',
46
+ )
47
+ #=>
48
+ [
49
+ {
50
+ start_time: #<DateTime: 2024-05-23T09:00:00-0700>,
51
+ end_time: #<DateTime: 2024-05-23T10:30:00-0700>,
52
+ },
53
+ {
54
+ start_time: #<DateTime: 2024-05-24T09:00:00-0700>,
55
+ end_time: #<DateTime: 2024-05-24T10:30:00-0700>,
56
+ },
57
+ {
58
+ start_time: #<DateTime: 2024-05-25T09:00:00-0700>,
59
+ end_time: #<DateTime: 2024-05-25T10:30:00-0700>,
60
+ },
61
+ {
62
+ start_time: #<DateTime: 2024-05-26T09:00:00-0700>,
63
+ end_time: #<DateTime: 2024-05-26T10:30:00-0700>,
64
+ }
65
+ ]
66
+ ```
67
+
68
+ #### Example 2
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:
34
70
 
35
71
  <div align="center">
36
72
  <img width="558" alt="calendar_image_1" src="https://github.com/StevenJL/periodoxical/assets/2191808/e92fc6ff-03fd-44ed-a955-d3a0dd0f5d0a">
@@ -79,7 +115,7 @@ Periodoxical.generate(
79
115
  ]
80
116
  ```
81
117
 
82
- #### Example 2 - using the `limit` key.
118
+ #### Example 3 - using the `limit` key.
83
119
 
84
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.
85
121
 
@@ -117,7 +153,7 @@ Periodoxical.generate(
117
153
  ]
118
154
  ```
119
155
 
120
- #### Example 3 - when time blocks vary between days
156
+ #### Example 4 - when time blocks vary between days
121
157
 
122
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**.
123
159
 
@@ -148,7 +184,73 @@ Periodoxical.generate(
148
184
  )
149
185
  ```
150
186
 
151
- #### Example 4 - Exclude time blocks using the `exclusion_dates` parameter
187
+ #### Example 5 - when specifying time blocks occur by day-of-month and/or and/or week-of-month and/or month.
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
190
+
191
+ ```rb
192
+ Periodoxical.generate(
193
+ time_zone: 'America/Los_Angeles',
194
+ start_date: '2024-06-1',
195
+ limit: 3,
196
+ days_of_month: [5, 10],
197
+ time_blocks: [
198
+ { start_time: '8:00AM', end_time: '9:00AM' },
199
+ ],
200
+ )
201
+ #=>
202
+ [
203
+ {
204
+ start: #<DateTime: 2024-06-05 08:00:00 -0700>,
205
+ end: #<DateTime: 2024-06-05 09:00:00 -0700>,
206
+ },
207
+ {
208
+ start: #<DateTime: 2024-06-10 08:00:00 -0700>,
209
+ end: #<DateTime: 2024-06-10 09:00:00 -0700>,
210
+ },
211
+ {
212
+ start: #<DateTime: 2024-07-05 08:00:00 -0700>,
213
+ end: #<DateTime: 2024-07-05 09:00:00 -0700>,
214
+ },
215
+ ]
216
+ ```
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
219
+
220
+ ```
221
+ Periodoxical.generate(
222
+ time_zone: 'America/Los_Angeles',
223
+ start_date: '2024-04-1',
224
+ limit: 4,
225
+ weeks_of_month: [1 2],
226
+ months: [4, 5, 6],
227
+ days_of_week: %w(mon),
228
+ time_blocks: [
229
+ { start_time: '8:00AM', end_time: '9:00AM' },
230
+ ],
231
+ )
232
+ #=>
233
+ [
234
+ {
235
+ start_time: #<DateTime: 2024-04-01 08:00:00 -0700>,
236
+ end_time: #<DateTime: 2024-04-01 09:00:00 -0700>,
237
+ },
238
+ {
239
+ start_time: #<DateTime: 2024-04-08 08:00:00 -0700>,
240
+ end_time: #<DateTime: 2024-04-08 09:00:00 -0700>,
241
+ },
242
+ {
243
+ start_time: #<DateTime: 2024-05-06 08:00:00 -0700>,
244
+ end_time: #<DateTime: 2024-05-06 09:00:00 -0700>,
245
+ },
246
+ {
247
+ start_time: #<DateTime: 2024-06-03 08:00:00 -0700>,
248
+ end_time: #<DateTime: 2024-06-03 09:00:00 -0700>,
249
+ },
250
+ ]
251
+ ```
252
+
253
+ #### Example 6 - Exclude time blocks using the `exclusion_dates` parameter
152
254
  As a Ruby dev, I want to generate slots for 8AM - 9AM on Mondays, except for the Monday of June 10, 2024.
153
255
 
154
256
  ```rb
@@ -163,24 +265,24 @@ Periodoxical.generate(
163
265
  ],
164
266
  }
165
267
  )
166
- # Returns all Monday 8AM - 9AM blocks except for the june on June 10, 2024
268
+ # Returns all Monday 8AM - 9AM blocks except for the Monday on June 10, 2024
167
269
  # =>
168
270
  [
169
271
  {
170
- start: <#DateTime: 2024-06-03T08:00:00-0700>,
171
- end: <#DateTime: 2024-06-03T09:00:00-0700>,
272
+ start: #<DateTime: 2024-06-03T08:00:00-0700>,
273
+ end: #<DateTime: 2024-06-03T09:00:00-0700>,
172
274
  }
173
275
  {
174
- start: <#DateTime: 2024-06-17T08:00:00-0700>,
175
- end: <#DateTime: 2024-06-17T09:00:00-0700>,
276
+ start: #<DateTime: 2024-06-17T08:00:00-0700>,
277
+ end: #<DateTime: 2024-06-17T09:00:00-0700>,
176
278
  }
177
279
  {
178
- start: <#DateTime: 2024-06-24T08:00:00-0700>,
179
- end: <#DateTime: 2024-06-24T09:00:00-0700>,
280
+ start: #<DateTime: 2024-06-24T08:00:00-0700>,
281
+ end: #<DateTime: 2024-06-24T09:00:00-0700>,
180
282
  }
181
283
  {
182
- start: <#DateTime: 2024-07-01T08:00:00-0700>,
183
- end: <#DateTime: 2024-07-01T09:00:00-0700>,
284
+ start: #<DateTime: 2024-07-01T08:00:00-0700>,
285
+ end: #<DateTime: 2024-07-01T09:00:00-0700>,
184
286
  }
185
287
  ]
186
288
  ```
@@ -1,3 +1,3 @@
1
1
  module Periodoxical
2
- VERSION = "0.5.2"
2
+ VERSION = "0.6.2"
3
3
  end
data/lib/periodoxical.rb CHANGED
@@ -2,6 +2,7 @@ require "periodoxical/version"
2
2
  require "date"
3
3
  require "time"
4
4
  require "tzinfo"
5
+ require "week_of_month"
5
6
 
6
7
  module Periodoxical
7
8
  class << self
@@ -32,22 +33,42 @@ module Periodoxical
32
33
  # Days of the week to generate the times for, if nil, then times are generated
33
34
  # for every day.
34
35
  # Ex: %w(mon tue wed sat)
36
+ # @param [Array<Integer>, nil] days_of_month
37
+ # Days of month to generate times for.
38
+ # Ex: %w(5 10) - The 5th and 10th days of every month
39
+ # @param [Array<Integer>, nil] months
40
+ # Months as integers, where 1 = Jan, 12 = Dec
35
41
  # @param [Integer] limit
36
42
  # How many date times to generate. To be used when `end_date` is nil.
37
43
  # @param [Aray<String>] exclusion_dates
38
44
  # Dates to be excluded when generating the time blocks
39
45
  # Ex: ['2024-06-10', '2024-06-14']
40
- # @param [Hash<Hash>] day_of_week_time_blocks
46
+ # @param [Hash<Array<Hash>>] day_of_week_time_blocks
41
47
  # To be used when hours are different between days of the week
42
48
  # Ex: {
43
49
  # mon: [{ start_time: '10:15AM', end_time: '11:35AM' }, { start_time: '9:00AM' }, {end_time: '4:30PM'} ],
44
50
  # tue: { start_time: '11:30PM', end_time: '12:00AM' },
45
51
  # fri: { start_time: '7:00PM', end_time: '9:00PM' },
46
52
  # }
47
- def initialize(time_zone: 'Etc/UTC', days_of_week: nil,
48
- start_date:, end_date: nil, time_blocks: nil, day_of_week_time_blocks: nil, limit: nil, exclusion_dates: nil)
53
+ def initialize(
54
+ start_date:,
55
+ end_date: nil,
56
+ time_blocks: nil,
57
+ day_of_week_time_blocks: nil,
58
+ limit: nil,
59
+ exclusion_dates: nil,
60
+ time_zone: 'Etc/UTC',
61
+ days_of_week: nil,
62
+ days_of_month: nil,
63
+ weeks_of_month: nil,
64
+ months: nil
65
+ )
66
+
49
67
  @time_zone = TZInfo::Timezone.get(time_zone)
50
68
  @days_of_week = days_of_week
69
+ @days_of_month = days_of_month
70
+ @weeks_of_month = weeks_of_month
71
+ @months = months
51
72
  @time_blocks = time_blocks
52
73
  @day_of_week_time_blocks = day_of_week_time_blocks
53
74
  @start_date = start_date.is_a?(String) ? Date.parse(start_date) : start_date
@@ -67,74 +88,31 @@ module Periodoxical
67
88
  # }
68
89
  # ]
69
90
  def generate
70
- if @days_of_week && @time_blocks
71
- generate_when_same_time_blocks_for_all_days
72
- elsif @day_of_week_time_blocks
73
- generate_when_different_time_blocks_between_days
91
+ initialize_looping_variables!
92
+ while @keep_generating
93
+ if should_add_time_blocks_from_current_date?
94
+ add_time_blocks_from_current_date!
95
+ end
96
+ advance_current_date_and_check_if_reached_end_date
74
97
  end
98
+ @output
75
99
  end
76
100
 
77
101
  private
78
102
 
79
- def generate_when_different_time_blocks_between_days
80
- times_output = []
81
- current_date = @start_date
82
- current_count = 0
83
- keep_generating = true
84
- while keep_generating
85
- day_of_week = day_of_week_long_to_short(current_date.strftime("%A"))
86
- if @day_of_week_time_blocks[day_of_week.to_sym] && !excluded_date?(current_date)
87
- time_blocks = @day_of_week_time_blocks[day_of_week.to_sym]
88
- time_blocks.each do |tb|
89
- times_output << {
90
- start: time_str_to_object(current_date, tb[:start_time]),
91
- end: time_str_to_object(current_date, tb[:end_time])
92
- }
93
- current_count = current_count + 1
94
- if @limit && current_count == @limit
95
- keep_generating = false
96
- break
97
- end
98
- end
99
- end
100
- current_date = current_date + 1
101
- if @end_date && (current_date > @end_date)
102
- keep_generating = false
103
- end
103
+ def validate!
104
+ unless @day_of_week_time_blocks || @time_blocks
105
+ raise "`day_of_week_time_blocks` or `time_blocks` need to be provided"
104
106
  end
105
- times_output
106
- end
107
107
 
108
- def generate_when_same_time_blocks_for_all_days
109
- times_output = []
110
- current_date = @start_date
111
- current_count = 0
112
- keep_generating = true
113
- while keep_generating
114
- day_of_week = day_of_week_long_to_short(current_date.strftime("%A"))
115
- if @days_of_week.include?(day_of_week) && !excluded_date?(current_date)
116
- @time_blocks.each do |tb|
117
- times_output << {
118
- start: time_str_to_object(current_date, tb[:start_time]),
119
- end: time_str_to_object(current_date, tb[:end_time])
120
- }
121
- current_count = current_count + 1
122
- if @limit && current_count == @limit
123
- keep_generating = false
124
- break
125
- end
108
+ if @weeks_of_month
109
+ @weeks_of_month.each do |wom|
110
+ unless wom.is_a?(Integer) && wom.between?(1, 5)
111
+ raise "weeks_of_month must be an array of integers between 1 and 5"
126
112
  end
127
113
  end
128
- current_date = current_date + 1
129
-
130
- if @end_date && (current_date > @end_date)
131
- keep_generating = false
132
- end
133
114
  end
134
- times_output
135
- end
136
115
 
137
- def validate!
138
116
  # days of week are valid
139
117
  if @days_of_week
140
118
  @days_of_week.each do |day|
@@ -152,8 +130,20 @@ module Periodoxical
152
130
  end
153
131
  end
154
132
 
155
- unless (@days_of_week && @time_blocks) || (@day_of_week_time_blocks)
156
- raise "Need to provide either `days_of_week` and `time_blocks` or `day_of_week_time_blocks`"
133
+ if @days_of_month
134
+ @days_of_month.each do |dom|
135
+ unless dom.is_a?(Integer) && dom.between?(1,31)
136
+ raise 'days_of_months must be array of integers between 1 and 31'
137
+ end
138
+ end
139
+ end
140
+
141
+ if @months
142
+ @months.each do |mon|
143
+ unless mon.is_a?(Integer) && mon.between?(1, 12)
144
+ raise 'months must be array of integers between 1 and 12'
145
+ end
146
+ end
157
147
  end
158
148
 
159
149
  unless( @limit || @end_date)
@@ -189,17 +179,115 @@ module Periodoxical
189
179
  @time_zone.local_to_utc(date_time).new_offset(@time_zone.current_period.offset.utc_total_offset)
190
180
  end
191
181
 
192
- # @param [Date] current_date
182
+ # @param [Date] date
193
183
  # @return [Boolean]
194
184
  # Whether or not the date is excluded
195
- def excluded_date?(current_date)
185
+ def excluded_date?(date)
196
186
  return false unless @exclusion_dates
197
187
 
198
188
  @exclusion_dates.each do |ed|
199
- return true if current_date == ed
189
+ return true if date == ed
200
190
  end
201
191
 
202
192
  false
203
193
  end
194
+
195
+ # Variables which manage flow of looping through time and generating slots
196
+ def initialize_looping_variables!
197
+ @output = []
198
+ @current_date = @start_date
199
+ @current_count = 0
200
+ @keep_generating = true
201
+ end
202
+
203
+ # @param [Hash] time_block
204
+ # Ex:
205
+ # {
206
+ # start_time: "9:00AM",
207
+ # start_time: "10:00AM",
208
+ # }
209
+ # Generates time block but also checks if we should stop generating
210
+ def append_to_output_and_check_limit(time_block)
211
+ @output << {
212
+ start: time_str_to_object(@current_date, time_block[:start_time]),
213
+ end: time_str_to_object(@current_date, time_block[:end_time])
214
+ }
215
+
216
+ # increment count, if `limit` is used to stop generating
217
+ @current_count = @current_count + 1
218
+ if @limit && @current_count == @limit
219
+ @keep_generating = false
220
+ throw :done
221
+ end
222
+ end
223
+
224
+ def advance_current_date_and_check_if_reached_end_date
225
+ @current_date = @current_date + 1
226
+
227
+ if @end_date && (@current_date > @end_date)
228
+ @keep_generating = false
229
+ end
230
+ end
231
+
232
+ # @return [Boolean]
233
+ # Should time blocks be added based on the current_date?
234
+ def should_add_time_blocks_from_current_date?
235
+ # return false if current_date is explicitly excluded
236
+ if @exclusion_dates
237
+ return false if @exclusion_dates.include?(@current_date)
238
+ end
239
+
240
+ # If weeks_of_months are specified but not satisified, return false
241
+ if @weeks_of_month
242
+ return false unless @weeks_of_month.include?(@current_date.week_of_month)
243
+ end
244
+
245
+ # If months are specified, but current_date does not satisfy months,
246
+ # return false
247
+ if @months
248
+ return false unless @months.include?(@current_date.month)
249
+ end
250
+
251
+ # If days of months are specified, but current_date does not satisfy it,
252
+ # return false
253
+ if @days_of_month
254
+ return false unless @days_of_month.include?(@current_date.day)
255
+ end
256
+
257
+ # If days of week are specified, but current_date does not satisfy it,
258
+ # return false
259
+ 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)
262
+ end
263
+
264
+ 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]
267
+ return false if dowtb.nil?
268
+ return false if dowtb.empty?
269
+ end
270
+
271
+ # Otherwise, return true
272
+ true
273
+ end
274
+
275
+ def add_time_blocks_from_current_date!
276
+ 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]
279
+ catch :done do
280
+ time_blocks.each do |tb|
281
+ append_to_output_and_check_limit(tb)
282
+ end
283
+ end
284
+ elsif @time_blocks
285
+ catch :done do
286
+ @time_blocks.each do |tb|
287
+ append_to_output_and_check_limit(tb)
288
+ end
289
+ end
290
+ end
291
+ end
204
292
  end
205
293
  end
data/periodoxical.gemspec CHANGED
@@ -35,6 +35,7 @@ Gem::Specification.new do |spec|
35
35
  spec.require_paths = ["lib"]
36
36
 
37
37
  spec.add_dependency 'tzinfo', '~> 2.0', '>= 2.0.0'
38
+ spec.add_dependency 'week_of_month', '1.2.6'
38
39
 
39
40
  spec.add_development_dependency "bundler", "~> 2.4"
40
41
  spec.add_development_dependency "rake", "~> 12.3.3"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: periodoxical
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.2
4
+ version: 0.6.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Steven Li
@@ -30,6 +30,20 @@ dependencies:
30
30
  - - ">="
31
31
  - !ruby/object:Gem::Version
32
32
  version: 2.0.0
33
+ - !ruby/object:Gem::Dependency
34
+ name: week_of_month
35
+ requirement: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - '='
38
+ - !ruby/object:Gem::Version
39
+ version: 1.2.6
40
+ type: :runtime
41
+ prerelease: false
42
+ version_requirements: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - '='
45
+ - !ruby/object:Gem::Version
46
+ version: 1.2.6
33
47
  - !ruby/object:Gem::Dependency
34
48
  name: bundler
35
49
  requirement: !ruby/object:Gem::Requirement