periodoxical 0.5.2 → 0.6.2

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: 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