periodoxical 0.4.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: 95c80ef1e3008c4279f58b029a0790353aae3b346d809390c38961b719c635a9
4
- data.tar.gz: 8531378c87459b8dc1945984da0c12b11238a6895668885751923779df3c1bd3
3
+ metadata.gz: 1c90e3908492b944735cc23b5da95746309133de5b54413495d4ed49e0d2dce3
4
+ data.tar.gz: 7fa63855a49e2c6feb9955007361aa6d8c9262f847e6fc0fc159c47f95ed1db5
5
5
  SHA512:
6
- metadata.gz: 899994a59c14f4c6dcf3e8534e7812496ebf53a8b902506e2efebd695d193abf9a93844ccd42389dc2be35c22f2ca829a0d19e3d1de44f7fa4b28d0f5baf3e79
7
- data.tar.gz: fdc25739ed5cf90e3aa1a71617c927382ed56dcbf7b2b292844bc3c3bbd5f4688fc9bf6c03ccddc58e8e930435835833d9fd092c30a31cdef0a243d19c1ad19d
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.4.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,6 +184,109 @@ Periodoxical.generate(
148
184
  )
149
185
  ```
150
186
 
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
254
+ As a Ruby dev, I want to generate slots for 8AM - 9AM on Mondays, except for the Monday of June 10, 2024.
255
+
256
+ ```rb
257
+ Periodoxical.generate(
258
+ time_zone: 'America/Los_Angeles',
259
+ start_date: '2024-06-3',
260
+ limit: 4,
261
+ exclusion_dates: %w(2024-06-10),
262
+ day_of_week_time_blocks: {
263
+ mon: [
264
+ { start_time: '8:00AM', end_time: '9:00AM' },
265
+ ],
266
+ }
267
+ )
268
+ # Returns all Monday 8AM - 9AM blocks except for the Monday on June 10, 2024
269
+ # =>
270
+ [
271
+ {
272
+ start: #<DateTime: 2024-06-03T08:00:00-0700>,
273
+ end: #<DateTime: 2024-06-03T09:00:00-0700>,
274
+ }
275
+ {
276
+ start: #<DateTime: 2024-06-17T08:00:00-0700>,
277
+ end: #<DateTime: 2024-06-17T09:00:00-0700>,
278
+ }
279
+ {
280
+ start: #<DateTime: 2024-06-24T08:00:00-0700>,
281
+ end: #<DateTime: 2024-06-24T09:00:00-0700>,
282
+ }
283
+ {
284
+ start: #<DateTime: 2024-07-01T08:00:00-0700>,
285
+ end: #<DateTime: 2024-07-01T09:00:00-0700>,
286
+ }
287
+ ]
288
+ ```
289
+
151
290
  ## Development
152
291
 
153
292
  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.4.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,24 +33,50 @@ 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
- # @param [Hash<Hash>] day_of_week_time_blocks
43
+ # @param [Aray<String>] exclusion_dates
44
+ # Dates to be excluded when generating the time blocks
45
+ # Ex: ['2024-06-10', '2024-06-14']
46
+ # @param [Hash<Array<Hash>>] day_of_week_time_blocks
38
47
  # To be used when hours are different between days of the week
39
48
  # Ex: {
40
49
  # mon: [{ start_time: '10:15AM', end_time: '11:35AM' }, { start_time: '9:00AM' }, {end_time: '4:30PM'} ],
41
50
  # tue: { start_time: '11:30PM', end_time: '12:00AM' },
42
51
  # fri: { start_time: '7:00PM', end_time: '9:00PM' },
43
52
  # }
44
- def initialize(time_zone: 'Etc/UTC', days_of_week: nil,
45
- start_date:, end_date: nil, time_blocks: nil, day_of_week_time_blocks: nil, limit: 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
+
46
67
  @time_zone = TZInfo::Timezone.get(time_zone)
47
68
  @days_of_week = days_of_week
69
+ @days_of_month = days_of_month
70
+ @weeks_of_month = weeks_of_month
71
+ @months = months
48
72
  @time_blocks = time_blocks
49
73
  @day_of_week_time_blocks = day_of_week_time_blocks
50
74
  @start_date = start_date.is_a?(String) ? Date.parse(start_date) : start_date
51
75
  @end_date = end_date.is_a?(String) ? Date.parse(end_date) : end_date
52
76
  @limit = limit
77
+ @exclusion_dates = if exclusion_dates && !exclusion_dates.empty?
78
+ exclusion_dates.map { |ed| Date.parse(ed) }
79
+ end
53
80
  validate!
54
81
  end
55
82
 
@@ -61,74 +88,31 @@ module Periodoxical
61
88
  # }
62
89
  # ]
63
90
  def generate
64
- if @days_of_week && @time_blocks
65
- generate_when_same_time_blocks_for_all_days
66
- elsif @day_of_week_time_blocks
67
- 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
68
97
  end
98
+ @output
69
99
  end
70
100
 
71
101
  private
72
102
 
73
- def generate_when_different_time_blocks_between_days
74
- times_output = []
75
- current_date = @start_date
76
- current_count = 0
77
- keep_generating = true
78
- while keep_generating
79
- day_of_week = day_of_week_long_to_short(current_date.strftime("%A"))
80
- if @day_of_week_time_blocks[day_of_week.to_sym]
81
- time_blocks = @day_of_week_time_blocks[day_of_week.to_sym]
82
- time_blocks.each do |tb|
83
- times_output << {
84
- start: time_str_to_object(current_date, tb[:start_time]),
85
- end: time_str_to_object(current_date, tb[:end_time])
86
- }
87
- current_count = current_count + 1
88
- if @limit && current_count == @limit
89
- keep_generating = false
90
- break
91
- end
92
- end
93
- end
94
- current_date = current_date + 1
95
- if @end_date && (current_date > @end_date)
96
- keep_generating = false
97
- 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"
98
106
  end
99
- times_output
100
- end
101
107
 
102
- def generate_when_same_time_blocks_for_all_days
103
- times_output = []
104
- current_date = @start_date
105
- current_count = 0
106
- keep_generating = true
107
- while keep_generating
108
- day_of_week = day_of_week_long_to_short(current_date.strftime("%A"))
109
- if @days_of_week.include?(day_of_week)
110
- @time_blocks.each do |tb|
111
- times_output << {
112
- start: time_str_to_object(current_date, tb[:start_time]),
113
- end: time_str_to_object(current_date, tb[:end_time])
114
- }
115
- current_count = current_count + 1
116
- if @limit && current_count == @limit
117
- keep_generating = false
118
- break
119
- 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"
120
112
  end
121
113
  end
122
- current_date = current_date + 1
123
-
124
- if @end_date && (current_date > @end_date)
125
- keep_generating = false
126
- end
127
114
  end
128
- times_output
129
- end
130
115
 
131
- def validate!
132
116
  # days of week are valid
133
117
  if @days_of_week
134
118
  @days_of_week.each do |day|
@@ -146,8 +130,20 @@ module Periodoxical
146
130
  end
147
131
  end
148
132
 
149
- unless (@days_of_week && @time_blocks) || (@day_of_week_time_blocks)
150
- 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
151
147
  end
152
148
 
153
149
  unless( @limit || @end_date)
@@ -182,5 +178,116 @@ module Periodoxical
182
178
  )
183
179
  @time_zone.local_to_utc(date_time).new_offset(@time_zone.current_period.offset.utc_total_offset)
184
180
  end
181
+
182
+ # @param [Date] date
183
+ # @return [Boolean]
184
+ # Whether or not the date is excluded
185
+ def excluded_date?(date)
186
+ return false unless @exclusion_dates
187
+
188
+ @exclusion_dates.each do |ed|
189
+ return true if date == ed
190
+ end
191
+
192
+ false
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
185
292
  end
186
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.4.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