banking_calendar 0.0.1 → 0.1.0

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: cb51632a085695d1691c98c6cb374298d267d49395058f8a0796be41a298a761
4
- data.tar.gz: 2b863ad87135df45c0b3bd13040bdd43399bfcea6af2d745fd1b28a7c0275a05
3
+ metadata.gz: 6fe25c53a1e7a771f43de415ee1195c06ff985a47ed297cd1e438688d64dbe06
4
+ data.tar.gz: ba475f817892c882ff8372c83e54c2df30a1df1c691cd1fec9ad3ed90c9eb0bf
5
5
  SHA512:
6
- metadata.gz: c359e64cbe4156d9824401039ce59d7cd72a74f9a1e34bfbdcec3ff77adfa7381bb59083b90567100b6b3755626d6dac36424758a2cbc5dca39daa50c06fce20
7
- data.tar.gz: 12a24e0a24314394b2eb20a6e4782a954fcb1b65f2f3155373e439bf31942d68f419e6a059be5a0131e08b0ce4aebba3a98e774ec4aaf2caf28d93f05e47897b
6
+ metadata.gz: 492881d3f120a304143e02a8cf491a74440470956944019e6bd4273196b33172cdfc348d10c8738ec3df0af1232625d7fb8e6cab9c59aa3fbab52353ddda6c00
7
+ data.tar.gz: be84c832399d0806fd4a4af9831eb30f2300eae20d79815dcca2cf9914d72e014089f6fc5bd8de41be03b2fd7570b896ee1a23b877d22b87a148a492ceb18625
data/CHANGELOG.md CHANGED
@@ -0,0 +1,9 @@
1
+ # v0.0.1 - May 8, 2020
2
+
3
+ - Initial release
4
+
5
+ # v0.1.0 - May 19, 2020
6
+
7
+ - Adds support for banking time and normalization of banking days
8
+ - Includes banking time in rollover calculations, if provided
9
+ - Extended default calendar library to include 2021 bank holidays
data/README.md CHANGED
@@ -1,11 +1,25 @@
1
1
  # Banking Calendar
2
2
 
3
+ [![Gem Version](https://badge.fury.io/rb/banking_calendar.svg)](https://badge.fury.io/rb/banking_calendar)
3
4
  [![CircleCI](https://circleci.com/gh/paymongo/banking_calendar.svg?style=svg)](https://circleci.com/gh/paymongo/banking_calendar)
4
5
 
5
- This Banking Calendar library provides a way to calculate days based on the banking calendar.
6
+ This Banking Calendar library provides a way to calculate days based on the banking calendar. This library supports dates with or without the time component. If the time component is provided, e.g. using `Time` or `DateTime` objects, the returned calculated date will normalize to the end of banking day based on the provided banking hours.
6
7
 
7
8
  # How to use
8
9
 
10
+ You may use the package by running:
11
+
12
+ ```sh
13
+ gem install banking_calendar
14
+ ```
15
+
16
+ If you are installing via the bundler, make sure to use the https rubygems source:
17
+
18
+ ```sh
19
+ source 'https://rubygems.org'
20
+
21
+ gem 'banking_calendar'
22
+ ```
9
23
  ## Basic usage
10
24
 
11
25
  You can create a banking calendar by creating an instance of the `Calendar` class and specifying
@@ -14,10 +28,13 @@ the `banking_days` and `bank_holidays`.
14
28
  ```ruby
15
29
  calendar = BankingCalendar::Calendar.new(
16
30
  banking_days: %w(monday tuesday wednesday thursday friday),
17
- banking_holidays: %w(2020-01-01 2020-01-25)
31
+ banking_holidays: %w(2020-01-01 2020-01-25),
32
+ banking_hours: (9..16).to_a
18
33
  )
19
34
  ```
20
- If `banking_days` value is not provided, then the default used is Monday to Friday.
35
+ If `banking_days` is not provided, then the default used is Monday to Friday.
36
+
37
+ Note that `banking_hours` is a list of hours, in 24-hours time integers, from opening to closing. It is exclusive of the banking hour at bank closing time. For example, if the banking hours are from 9 a.m. to 5 p.m., the list of banking hours provided must be equivalent to `[9, 10, 11, 12, 13, 14, 15, 16]`. If `banking_hours` is not provided, the default used is from 9 a.m. to 5 p.m. on a regular banking day.
21
38
 
22
39
  ## Using default calendars
23
40
 
@@ -25,7 +42,7 @@ Some few calendar configurations are provided. You may use them by calling `load
25
42
  the name of the calendar you want to load. You may refer to the list of available calendars below.
26
43
 
27
44
  ```ruby
28
- calendar = BankingCalendar::Calendar.load_calendar('bsp')
45
+ calendar = BankingCalendar::Calendar.load_calendar('bsp')
29
46
  ```
30
47
 
31
48
  ## Useful methods
@@ -44,6 +61,8 @@ calendar.banking_day?(Date.parse('15 April 2020'))
44
61
  Given a `date`, this method returns the date after `interval` number of business days. If the given
45
62
  `date` falls on a non-banking day, the calculation starts at the next possible banking day.
46
63
 
64
+ If banking hours are provided in the calendar configuration, the returned date and time will be normalized to the end of banking day. If given time falls after banking hours, counting starts from the next banking day.
65
+
47
66
  ```ruby
48
67
  # May 4, Monday is a banking day
49
68
  date = Date.parse('2020-05-04')
@@ -55,12 +74,22 @@ date = Date.parse('2020-05-01')
55
74
  # Next banking day is May 4, Monday
56
75
  calendar.banking_days_after(date, 2).strftime("%A, %B %d, %Y")
57
76
  # => Wednesday, May 06, 2020
77
+
78
+ date = DateTime.parse('2020-05-04 11:00')
79
+ calendar.banking_days_after(date, 2)
80
+ # => May 6, 2020 at 5 p.m.
81
+
82
+ date = DateTime.parse('2020-05-04 19:00')
83
+ calendar.banking_days_after(date, 2)
84
+ # => May 7, 2020 at 5 p.m.
58
85
  ```
59
86
 
60
87
  ### banking_days_before(date, interval)
61
88
  Given a `date`, this method returns the prior `interval` number of business days. If the given
62
89
  `date` falls on a non-banking day, the calculation starts at the first previous possible banking day.
63
90
 
91
+ If banking hours are provided in the calendar configuration, the returned date and time will be normalized to the end of banking day. If given time does not falls before banking hours, counting starts from the previous banking day.
92
+
64
93
  ```ruby
65
94
  # May 22, 2020 Friday is a banking day
66
95
  date = Date.parse('2020-05-22')
@@ -70,8 +99,16 @@ calendar.banking_days_before(date, 4).strftime("%A, %B %d, %Y")
70
99
  # May 1, 2020 Friday is a bank holiday
71
100
  date = Date.parse('2020-05-01')
72
101
  # Previous banking day is April 30, 2020 Thursday
73
- calendar.banking_days_after(date, 2).strftime("%A, %B %d, %Y")
102
+ calendar.banking_days_before(date, 2).strftime("%A, %B %d, %Y")
74
103
  # => Tuesday, April 28, 2020
104
+
105
+ date = DateTime.parse('2020-05-06 11:00')
106
+ calendar.banking_days_before(date, 2)
107
+ # => May 4, 2020 at 5 p.m.
108
+
109
+ date = DateTime.parse('2020-05-08 06:00')
110
+ calendar.banking_days_before(date, 2)
111
+ # => May 5, 2020 at 5 p.m.
75
112
  ```
76
113
 
77
114
  ### next_banking_day(date)
@@ -41,7 +41,13 @@ module BankingCalendar
41
41
  end
42
42
 
43
43
  DEFAULT_BANKING_DAYS = %w[mon tue wed thu fri].freeze
44
- VALID_CALENDAR_KEYS = %i[banking_days bank_holidays].freeze
44
+ DEFAULT_BANKING_HOURS = (9..16).to_a.freeze
45
+ VALID_CALENDAR_KEYS = %i[
46
+ banking_days
47
+ bank_holidays
48
+ banking_hours
49
+ ].freeze
50
+ VALID_BANKING_HOURS_KEYS = %i[start end].freeze
45
51
  VALID_DAYS = %w[sun mon tue wed thu fri sat].freeze
46
52
 
47
53
  @semaphore = Mutex.new
@@ -75,8 +81,18 @@ module BankingCalendar
75
81
  date
76
82
  end
77
83
 
84
+ # Given a date, add interval number of banking days.
85
+ #
86
+ # If the given date is not a banking day, counting starts from the
87
+ # next banking day.
88
+ #
89
+ # If banking hours are provided, returned date and time will be
90
+ # normalized to the end of banking day. If given date falls after
91
+ # banking hours, counting starts from the next banking day.
78
92
  def banking_days_after(date, interval)
93
+ date = normalize_date(date, :after) if with_banking_hours?
79
94
  date = next_banking_day(date) unless banking_day?(date)
95
+
80
96
  interval.times do
81
97
  date = next_banking_day(date)
82
98
  end
@@ -84,8 +100,18 @@ module BankingCalendar
84
100
  date
85
101
  end
86
102
 
103
+ # Given a date, subtract interval number of banking days.
104
+ #
105
+ # If the given date is not a banking day, counting starts from the
106
+ # previous banking day.
107
+ #
108
+ # If banking hours are provided, returned date and time will be
109
+ # normalized to the end of banking day. If given date falls before
110
+ # banking hours, counting starts from the prior banking day.
87
111
  def banking_days_before(date, interval)
112
+ date = normalize_date(date, :before) if with_banking_hours?
88
113
  date = previous_banking_day(date) unless banking_day?(date)
114
+
89
115
  interval.times do
90
116
  date = previous_banking_day(date)
91
117
  end
@@ -103,6 +129,41 @@ module BankingCalendar
103
129
  true
104
130
  end
105
131
 
132
+ def banking_hour?(date)
133
+ time_or_datetime?(date)
134
+
135
+ hour = date.hour
136
+
137
+ return false unless banking_day?(date)
138
+ return false unless banking_hours.include?(hour)
139
+
140
+ true
141
+ end
142
+
143
+ def before_banking_hours?(date)
144
+ time_or_datetime? date
145
+ return false unless banking_day?(date)
146
+
147
+ date.hour < banking_hours.min
148
+ end
149
+
150
+ def after_banking_hours?(date)
151
+ time_or_datetime? date
152
+ return true unless banking_day?(date)
153
+
154
+ date.hour > banking_hours.max
155
+ end
156
+
157
+ def end_of_banking_day(date)
158
+ date.class.new(
159
+ date.year,
160
+ date.month,
161
+ date.day,
162
+ banking_hours.max + 1,
163
+ 0
164
+ )
165
+ end
166
+
106
167
  private
107
168
 
108
169
  def duration_for(date, interval = 1)
@@ -115,6 +176,54 @@ module BankingCalendar
115
176
  end
116
177
  end
117
178
 
179
+ def time_or_datetime?(date)
180
+ unless date.is_a?(Time) || date.is_a?(DateTime)
181
+ raise "#{date} is #{date.class}. " \
182
+ 'Must be Time or DateTime if accounting for banking hours.'
183
+ end
184
+ end
185
+
186
+ def roll_forward(date)
187
+ if banking_day?(date) && after_banking_hours?(date)
188
+ date = next_banking_day(date)
189
+ end
190
+
191
+ date
192
+ end
193
+
194
+ def roll_backward(date)
195
+ if banking_day?(date) && before_banking_hours?(date)
196
+ date = previous_banking_day(date)
197
+ end
198
+
199
+ date
200
+ end
201
+
202
+ def normalize_date(date, rollover)
203
+ time_or_datetime? date
204
+
205
+ if rollover == :after
206
+ date = roll_forward(date)
207
+ elsif rollover == :before
208
+ date = roll_backward(date)
209
+ end
210
+ date = end_of_banking_day(date)
211
+
212
+ date
213
+ end
214
+
215
+ def banking_hours
216
+ @banking_hours ||= (@config[:banking_hours] || DEFAULT_BANKING_HOURS).map do |hour|
217
+ hour.tap do |h|
218
+ raise "#{h} is an invalid hour." if h > 24 || h.negative?
219
+ end
220
+ end
221
+ end
222
+
223
+ def with_banking_hours?
224
+ @config.key?(:banking_hours)
225
+ end
226
+
118
227
  def banking_days
119
228
  @banking_days ||= (@config[:banking_days] || DEFAULT_BANKING_DAYS).map do |day|
120
229
  day.downcase.strip[0..2].tap do |shortened_day|
@@ -5,6 +5,16 @@ banking_days:
5
5
  - thursday
6
6
  - friday
7
7
 
8
+ banking_hours:
9
+ - 9
10
+ - 10
11
+ - 11
12
+ - 12
13
+ - 13
14
+ - 14
15
+ - 15
16
+ - 16
17
+
8
18
  bank_holidays:
9
19
  - 2020-01-01
10
20
  - 2020-01-25
@@ -24,3 +34,23 @@ bank_holidays:
24
34
  - 2020-12-25
25
35
  - 2020-12-30
26
36
  - 2020-12-31
37
+ - 2021-01-01
38
+ - 2021-02-12
39
+ - 2021-02-25
40
+ - 2021-04-01
41
+ - 2021-04-02
42
+ - 2021-04-09
43
+ - 2021-05-01
44
+ - 2021-05-13
45
+ - 2021-06-12
46
+ - 2021-07-20
47
+ - 2021-08-21
48
+ - 2021-08-30
49
+ - 2021-11-01
50
+ - 2021-11-02
51
+ - 2021-11-30
52
+ - 2021-12-08
53
+ - 2021-12-24
54
+ - 2021-12-25
55
+ - 2021-12-30
56
+ - 2021-12-31
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module BankingCalendar
4
- VERSION = '0.0.1'
4
+ VERSION = '0.1.0'
5
5
  end
@@ -25,7 +25,7 @@ describe BankingCalendar::Calendar do
25
25
  subject { BankingCalendar::Calendar.load_calendar('invalid_calendar') }
26
26
  it 'raises an error' do
27
27
  expect { subject }.to raise_error(
28
- 'Only the following keys are valid: banking_days, bank_holidays'
28
+ 'Only the following keys are valid: banking_days, bank_holidays, banking_hours'
29
29
  )
30
30
  end
31
31
  end
@@ -39,21 +39,6 @@ describe BankingCalendar::Calendar do
39
39
  )
40
40
  end
41
41
  end
42
-
43
- context 'when calendar is from an additional directory' do
44
- after { BankingCalendar::Calendar.additional_load_paths = nil }
45
- subject { BankingCalendar::Calendar.load_calendar('valid_calendar') }
46
-
47
- it { is_expected.to be_a BankingCalendar::Calendar }
48
-
49
- context 'when also a default calendar' do
50
- subject { BankingCalendar::Calendar.load_calendar('bsp') }
51
-
52
- it 'uses the custom calendar' do
53
- expect(subject.banking_day?(Date.parse('2020-01-01'))).to eq(true)
54
- end
55
- end
56
- end
57
42
  end
58
43
 
59
44
  shared_examples 'shared' do
@@ -202,6 +187,382 @@ describe BankingCalendar::Calendar do
202
187
  end
203
188
  end
204
189
 
190
+ shared_examples 'with_time' do
191
+ context 'when providing date objects without time' do
192
+ let(:cal) do
193
+ BankingCalendar::Calendar.load_calendar('bsp')
194
+ end
195
+ subject { cal.banking_hour?(date) }
196
+
197
+ context 'when provided a valid date' do
198
+ let(:date) { date_class.parse('2020-01-10 10:00') }
199
+ it { is_expected.to be_truthy }
200
+ end
201
+
202
+ context 'when provided an invalid date' do
203
+ let(:date) { Date.parse('2020-01-10') }
204
+
205
+ it 'raises an error' do
206
+ expect { subject }.to raise_error(
207
+ '2020-01-10 is Date. ' \
208
+ 'Must be Time or DateTime if accounting for banking hours.'
209
+ )
210
+ end
211
+ end
212
+ end
213
+
214
+ describe '#banking_hour?' do
215
+ let(:cal) do
216
+ BankingCalendar::Calendar.load_calendar('bsp')
217
+ end
218
+ subject { cal.banking_hour?(date) }
219
+
220
+ context 'when it is a banking hour' do
221
+ context 'when it is a banking day' do
222
+ let(:date) { date_class.parse('2020-01-10 10:00') }
223
+ it { is_expected.to be_truthy }
224
+ end
225
+
226
+ context 'when it is a non-banking day' do
227
+ let(:date) { date_class.parse('2020-01-01 13:12') }
228
+ it { is_expected.to be_falsey }
229
+ end
230
+ end
231
+
232
+ context 'when it is not a banking hour' do
233
+ context 'when it is a banking day' do
234
+ let(:date) { date_class.parse('2020-01-10 06:22') }
235
+ it { is_expected.to be_falsey }
236
+ end
237
+
238
+ context 'when it is a non-banking day' do
239
+ let(:date) { date_class.parse('2020-01-01 18:55') }
240
+ it { is_expected.to be_falsey }
241
+ end
242
+ end
243
+ end
244
+
245
+ describe '#before_banking_hours?' do
246
+ let(:cal) do
247
+ BankingCalendar::Calendar.load_calendar('bsp')
248
+ end
249
+ subject { cal.before_banking_hours?(date) }
250
+
251
+ context 'when it is a banking day' do
252
+ context 'given before banking hours' do
253
+ let(:date) { date_class.parse('2020-01-10 06:22') }
254
+ it { is_expected.to be_truthy }
255
+ end
256
+
257
+ context 'given during banking hours' do
258
+ let(:date) { date_class.parse('2020-01-10 14:14') }
259
+ it { is_expected.to be_falsey }
260
+ end
261
+
262
+ context 'given after banking hours' do
263
+ let(:date) { date_class.parse('2020-01-10 17:15') }
264
+ it { is_expected.to be_falsey }
265
+ end
266
+ end
267
+
268
+ context 'when it is a non-banking day' do
269
+ context 'given before banking hours' do
270
+ let(:date) { date_class.parse('2020-05-02 06:22') }
271
+ it { is_expected.to be_falsey }
272
+ end
273
+
274
+ context 'given during banking hours' do
275
+ let(:date) { date_class.parse('2020-05-02 14:14') }
276
+ it { is_expected.to be_falsey }
277
+ end
278
+
279
+ context 'given after banking hours' do
280
+ let(:date) { date_class.parse('2020-05-02 17:15') }
281
+ it { is_expected.to be_falsey }
282
+ end
283
+ end
284
+
285
+ context 'when it is a holiday' do
286
+ context 'given before banking hours' do
287
+ let(:date) { date_class.parse('2020-01-01 06:22') }
288
+ it { is_expected.to be_falsey }
289
+ end
290
+
291
+ context 'given during banking hours' do
292
+ let(:date) { date_class.parse('2020-01-01 14:14') }
293
+ it { is_expected.to be_falsey }
294
+ end
295
+
296
+ context 'given after banking hours' do
297
+ let(:date) { date_class.parse('2020-01-01 17:15') }
298
+ it { is_expected.to be_falsey }
299
+ end
300
+ end
301
+ end
302
+
303
+ describe '#after_banking_hours?' do
304
+ let(:cal) do
305
+ BankingCalendar::Calendar.load_calendar('bsp')
306
+ end
307
+ subject { cal.after_banking_hours?(date) }
308
+
309
+ context 'when it is a banking day' do
310
+ context 'given before banking hours' do
311
+ let(:date) { date_class.parse('2020-01-10 06:22') }
312
+ it { is_expected.to be_falsey }
313
+ end
314
+
315
+ context 'given during banking hours' do
316
+ let(:date) { date_class.parse('2020-01-10 14:14') }
317
+ it { is_expected.to be_falsey }
318
+ end
319
+
320
+ context 'given before banking hours' do
321
+ let(:date) { date_class.parse('2020-01-10 17:15') }
322
+ it { is_expected.to be_truthy }
323
+ end
324
+ end
325
+
326
+ context 'when it is a non-banking day' do
327
+ context 'given before banking hours' do
328
+ let(:date) { date_class.parse('2020-05-02 06:22') }
329
+ it { is_expected.to be_truthy }
330
+ end
331
+
332
+ context 'given during banking hours' do
333
+ let(:date) { date_class.parse('2020-05-02 14:14') }
334
+ it { is_expected.to be_truthy }
335
+ end
336
+
337
+ context 'given before banking hours' do
338
+ let(:date) { date_class.parse('2020-05-02 17:15') }
339
+ it { is_expected.to be_truthy }
340
+ end
341
+ end
342
+
343
+ context 'when it is a holiday' do
344
+ context 'given before banking hours' do
345
+ let(:date) { date_class.parse('2020-01-01 06:22') }
346
+ it { is_expected.to be_truthy }
347
+ end
348
+
349
+ context 'given during banking hours' do
350
+ let(:date) { date_class.parse('2020-01-01 14:14') }
351
+ it { is_expected.to be_truthy }
352
+ end
353
+
354
+ context 'given before banking hours' do
355
+ let(:date) { date_class.parse('2020-01-01 17:15') }
356
+ it { is_expected.to be_truthy }
357
+ end
358
+ end
359
+ end
360
+
361
+ describe '#banking_days_after' do
362
+ let(:cal) do
363
+ BankingCalendar::Calendar.load_calendar('bsp')
364
+ end
365
+ subject { cal.banking_days_after(date, delta) }
366
+
367
+ context 'when it is a banking day' do
368
+ context 'followed only by banking days' do
369
+ context 'when it is before banking hours' do
370
+ let(:date) { date_class.parse('2020-01-13 04:00') }
371
+ let(:delta) { 3 }
372
+ it { is_expected.to eq(cal.end_of_banking_day(date + delta * interval)) }
373
+ end
374
+
375
+ context 'when it is during banking hours' do
376
+ let(:date) { date_class.parse('2020-01-13 11:00') }
377
+ let(:delta) { 3 }
378
+ it { is_expected.to eq(cal.end_of_banking_day(date + delta * interval)) }
379
+ end
380
+
381
+ context 'when it is after banking hours' do
382
+ let(:date) { date_class.parse('2020-01-13 18:00') }
383
+ let(:delta) { 3 }
384
+ it { is_expected.to eq(cal.end_of_banking_day(date + (delta + 1) * interval)) }
385
+ end
386
+ end
387
+
388
+ context 'followed by a weekend' do
389
+ context 'when it is before banking hours' do
390
+ let(:date) { date_class.parse('2020-05-15 04:00') }
391
+ let(:delta) { 3 }
392
+ it { is_expected.to eq(cal.end_of_banking_day(date + (delta + 2) * interval)) }
393
+ end
394
+
395
+ context 'when it is during banking hours' do
396
+ let(:date) { date_class.parse('2020-05-15 13:00') }
397
+ let(:delta) { 3 }
398
+ it { is_expected.to eq(cal.end_of_banking_day(date + (delta + 2) * interval)) }
399
+ end
400
+
401
+ context 'when it is after banking hours' do
402
+ let(:date) { date_class.parse('2020-05-15 18:15') }
403
+ let(:delta) { 3 }
404
+ it { is_expected.to eq(cal.end_of_banking_day(date + (delta + 3) * interval)) }
405
+ end
406
+ end
407
+
408
+ context 'followed by a holiday' do
409
+ context 'when it is before banking hours' do
410
+ let(:date) { date_class.parse('2020-02-24 04:00') }
411
+ let(:delta) { 3 }
412
+ it { is_expected.to eq(cal.end_of_banking_day(date + (delta + 1) * interval)) }
413
+ end
414
+
415
+ context 'when it is during banking hours' do
416
+ let(:date) { date_class.parse('2020-02-24 13:00') }
417
+ let(:delta) { 3 }
418
+ it { is_expected.to eq(cal.end_of_banking_day(date + (delta + 1) * interval)) }
419
+ end
420
+
421
+ context 'when it is after banking hours' do
422
+ let(:date) { date_class.parse('2020-02-24 18:15') }
423
+ let(:delta) { 3 }
424
+ it { is_expected.to eq(cal.end_of_banking_day(date + (delta + 4) * interval)) }
425
+ end
426
+ end
427
+ end
428
+
429
+ context 'when it is a non-banking day' do
430
+ context 'followed only by banking days' do
431
+ context 'when time is before banking hours' do
432
+ let(:date) { date_class.parse('2020-05-10 04:00') }
433
+ let(:delta) { 3 }
434
+ it { is_expected.to eq(cal.end_of_banking_day(date + (delta + 1) * interval)) }
435
+ end
436
+
437
+ context 'when time is during banking hours' do
438
+ let(:date) { date_class.parse('2020-05-10 11:00') }
439
+ let(:delta) { 3 }
440
+ it { is_expected.to eq(cal.end_of_banking_day(date + (delta + 1) * interval)) }
441
+ end
442
+
443
+ context 'when time is after banking hours' do
444
+ let(:date) { date_class.parse('2020-05-10 19:00') }
445
+ let(:delta) { 3 }
446
+ it { is_expected.to eq(cal.end_of_banking_day(date + (delta + 1) * interval)) }
447
+ end
448
+ end
449
+
450
+ context 'followed by another non-banking day' do
451
+ context 'when time is before banking hours' do
452
+ let(:date) { date_class.parse('2020-05-16 04:00') }
453
+ let(:delta) { 3 }
454
+ it { is_expected.to eq(cal.end_of_banking_day(date + (delta + 2) * interval)) }
455
+ end
456
+
457
+ context 'when time is during banking hours' do
458
+ let(:date) { date_class.parse('2020-05-16 11:00') }
459
+ let(:delta) { 3 }
460
+ it { is_expected.to eq(cal.end_of_banking_day(date + (delta + 2) * interval)) }
461
+ end
462
+
463
+ context 'when time is after banking hours' do
464
+ let(:date) { date_class.parse('2020-05-16 19:00') }
465
+ let(:delta) { 3 }
466
+ it { is_expected.to eq(cal.end_of_banking_day(date + (delta + 2) * interval)) }
467
+ end
468
+ end
469
+
470
+ context 'followed by a holiday' do
471
+ context 'when time is before banking hours' do
472
+ let(:date) { date_class.parse('2020-08-30 04:00') }
473
+ let(:delta) { 3 }
474
+ it { is_expected.to eq(cal.end_of_banking_day(date + (delta + 2) * interval)) }
475
+ end
476
+
477
+ context 'when time is during banking hours' do
478
+ let(:date) { date_class.parse('2020-08-30 11:00') }
479
+ let(:delta) { 3 }
480
+ it { is_expected.to eq(cal.end_of_banking_day(date + (delta + 2) * interval)) }
481
+ end
482
+
483
+ context 'when time is after banking hours' do
484
+ let(:date) { date_class.parse('2020-08-30 19:00') }
485
+ let(:delta) { 3 }
486
+ it { is_expected.to eq(cal.end_of_banking_day(date + (delta + 2) * interval)) }
487
+ end
488
+ end
489
+ end
490
+ end
491
+
492
+ describe '#banking_days_before' do
493
+ let(:cal) do
494
+ BankingCalendar::Calendar.load_calendar('bsp')
495
+ end
496
+ subject { cal.banking_days_before(date, delta) }
497
+
498
+ context 'when it is a banking day' do
499
+ context 'preceded only by banking days' do
500
+ context 'when time is before banking hours' do
501
+ let(:date) { date_class.parse('2020-01-16 08:00') }
502
+ let(:delta) { 2 }
503
+ it { is_expected.to eq(cal.end_of_banking_day(date - (delta + 1) * interval)) }
504
+ end
505
+
506
+ context 'when time is during banking hours' do
507
+ let(:date) { date_class.parse('2020-01-16 09:00') }
508
+ let(:delta) { 2 }
509
+ it { is_expected.to eq(cal.end_of_banking_day(date - delta * interval)) }
510
+ end
511
+
512
+ context 'when time is after banking hours' do
513
+ let(:date) { date_class.parse('2020-01-16 18:00') }
514
+ let(:delta) { 2 }
515
+ it { is_expected.to eq(cal.end_of_banking_day(date - delta * interval)) }
516
+ end
517
+ end
518
+
519
+ context 'preceded by a weekend' do
520
+ context 'when time is before banking hours' do
521
+ let(:date) { date_class.parse('2020-05-11 08:00') }
522
+ let(:delta) { 2 }
523
+ it { is_expected.to eq(cal.end_of_banking_day(date - (delta + 3) * interval)) }
524
+ end
525
+
526
+ context 'when time is during banking hours' do
527
+ let(:date) { date_class.parse('2020-05-11 09:00') }
528
+ let(:delta) { 2 }
529
+ it { is_expected.to eq(cal.end_of_banking_day(date - (delta + 2) * interval)) }
530
+ end
531
+
532
+ context 'when time is after banking hours' do
533
+ let(:date) { date_class.parse('2020-05-11 18:00') }
534
+ let(:delta) { 2 }
535
+ it { is_expected.to eq(cal.end_of_banking_day(date - (delta + 2) * interval)) }
536
+ end
537
+ end
538
+
539
+ context 'preceded by banking days and non-banking days' do
540
+ context 'when time is before banking hours' do
541
+ let(:date) { date_class.parse('2020-05-19 08:00') }
542
+ let(:delta) { 2 }
543
+ it { is_expected.to eq(cal.end_of_banking_day(date - (delta + 3) * interval)) }
544
+ end
545
+
546
+ context 'when time is during banking hours' do
547
+ let(:date) { date_class.parse('2020-05-19 09:00') }
548
+ let(:delta) { 2 }
549
+ it { is_expected.to eq(cal.end_of_banking_day(date - (delta + 2) * interval)) }
550
+ end
551
+
552
+ context 'when time is after banking hours' do
553
+ let(:date) { date_class.parse('2020-05-19 18:00') }
554
+ let(:delta) { 2 }
555
+ it { is_expected.to eq(cal.end_of_banking_day(date - (delta + 2) * interval)) }
556
+ end
557
+ end
558
+ end
559
+
560
+ context 'when it is a non-banking day' do
561
+
562
+ end
563
+ end
564
+ end
565
+
205
566
  context 'when using Date objects' do
206
567
  let(:date_class) { Date }
207
568
  let(:interval) { 1 }
@@ -209,10 +570,19 @@ describe BankingCalendar::Calendar do
209
570
  it_behaves_like 'shared'
210
571
  end
211
572
 
573
+ context 'when using DateTime objects' do
574
+ let(:date_class) { DateTime }
575
+ let(:interval) { 1 }
576
+
577
+ it_behaves_like 'shared'
578
+ it_behaves_like 'with_time'
579
+ end
580
+
212
581
  context 'when using Time objects' do
213
582
  let(:date_class) { Time }
214
583
  let(:interval) { 3_600 * 24 }
215
584
 
216
585
  it_behaves_like 'shared'
586
+ it_behaves_like 'with_time'
217
587
  end
218
588
  end
@@ -5,7 +5,18 @@ banking_days:
5
5
  - thursday
6
6
  - friday
7
7
 
8
+ banking_hours:
9
+ - 9
10
+ - 10
11
+ - 11
12
+ - 12
13
+ - 13
14
+ - 14
15
+ - 15
16
+ - 16
17
+
8
18
  bank_holidays:
19
+ - 2020-01-01
9
20
  - 2020-01-25
10
21
  - 2020-02-25
11
22
  - 2020-04-09
@@ -17,6 +28,7 @@ bank_holidays:
17
28
  - 2020-08-31
18
29
  - 2020-11-01
19
30
  - 2020-11-02
31
+ - 2020-11-03
20
32
  - 2020-11-30
21
33
  - 2020-12-08
22
34
  - 2020-12-24
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: banking_calendar
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - PayMongo
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-05-08 00:00:00.000000000 Z
11
+ date: 2020-05-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rspec