business_calendar 1.0.0 → 3.0.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: f7e437b2a3580ac2cac07f883e3acc42dfa39e0bd3506e95e1e46a1441d14f7d
4
- data.tar.gz: 13769d2f539ae93f3579c1b46378c5da5708b039b63f186e910d9f528e4b5041
3
+ metadata.gz: beb100bc24dbbd90684a3c07a5201b9608534d9d2a3a26807e59d2b646eb4579
4
+ data.tar.gz: b456e4a40d119ebf60e822ecd2f07751c4971f5694e8dd0a2496fccc3226768b
5
5
  SHA512:
6
- metadata.gz: 625699922d3aa15522193ab57de09fa8ac5c0d13623afd47bfdbda65b2e2b2798d623ea316c0db5570b3e0c4c69de18cb4a47793167bcacf458642c516a2e0af
7
- data.tar.gz: de9860000e204fc1fb05a853c437277bda370033046cc2ff43d954af432b715d39bba09f8a9d085621876f2cf508e6434063ad8ea238e01cde8eea955b7b1f42
6
+ metadata.gz: 9b827ee58fe6bec07fc210d3928acea4f5aec538e059ad639cc07dc97dd969837b3c33f778c860a2a5445a89352bac2d706d3ecee6f1a2933016ddb460886ce5
7
+ data.tar.gz: abe89b5c64c4a84fa1aaa7b21533eed255983efa6da080ddc1911328cc45c83b8cf804dd71bf079a4c433195f3f0eb1665602ba6f9c46c9b3c5a1c0a8d759377
@@ -0,0 +1,30 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [ master ]
6
+ pull_request:
7
+ branches: [ master ]
8
+
9
+ jobs:
10
+ test:
11
+ runs-on: ubuntu-latest
12
+ continue-on-error: ${{ matrix.experimental }}
13
+ strategy:
14
+ matrix:
15
+ ruby-version: [2.3.1, 2.7.0]
16
+ experimental: [false]
17
+ include:
18
+ - ruby-version: head
19
+ experimental: true
20
+
21
+ steps:
22
+ - uses: actions/checkout@v2
23
+ - name: Set up Ruby ${{ matrix.ruby-version }}
24
+ uses: ruby/setup-ruby@v1
25
+ with:
26
+ ruby-version: ${{ matrix.ruby-version }}
27
+ - name: Install dependencies
28
+ run: bundle install
29
+ - name: Run tests
30
+ run: bundle exec rspec
@@ -0,0 +1,29 @@
1
+ name: Ruby Gem
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - '*'
7
+
8
+ jobs:
9
+ build:
10
+ name: Build + Publish
11
+ runs-on: ubuntu-latest
12
+
13
+ steps:
14
+ - uses: actions/checkout@v2
15
+ - name: Set up Ruby 2.7
16
+ uses: actions/setup-ruby@v1
17
+ with:
18
+ ruby-version: 2.7
19
+
20
+ - name: Publish to RubyGems
21
+ run: |
22
+ mkdir -p $HOME/.gem
23
+ touch $HOME/.gem/credentials
24
+ chmod 0600 $HOME/.gem/credentials
25
+ printf -- "---\n:rubygems_api_key: ${GEM_HOST_API_KEY}\n" > $HOME/.gem/credentials
26
+ gem build *.gemspec
27
+ gem push *.gem
28
+ env:
29
+ GEM_HOST_API_KEY: "${{secrets.RUBYGEMS_AUTH_TOKEN}}"
data/CHANGELOG.md CHANGED
@@ -1,5 +1,20 @@
1
1
  # business_calendar changes by version
2
2
 
3
+ 2.0.0
4
+ ---------
5
+
6
+ - Remove UK weekend holidays, replace with the actual observed holiday dates [#26]
7
+
8
+ 1.1.0
9
+ ---------
10
+
11
+ - Cache parsed responses from API endpoints for TTL (Time to Live) duration.
12
+ - Increase default TTL duration from 5 minutes to 1 day.
13
+ Holidays are not expected to frequently change.
14
+ - Allow disabling cache clearing by setting `ttl` to `false`.
15
+ - Set hard limit to size of memoized holidays cache,
16
+ since large quantities of user supplied dates could consume excessive memory / cause DoS.
17
+
3
18
  1.0.0
4
19
  ---------
5
20
 
data/Gemfile CHANGED
@@ -1,6 +1,4 @@
1
1
  source 'https://rubygems.org'
2
2
 
3
- gem 'coveralls'
4
-
5
3
  # Specify your gem's dependencies in business_calendar.gemspec
6
4
  gemspec
data/README.md CHANGED
@@ -1,8 +1,6 @@
1
1
  # BusinessCalendar
2
2
 
3
3
  [![Gem Version](https://badge.fury.io/rb/business_calendar.svg)](https://rubygems.org/gems/business_calendar)
4
- [![Build Status](https://travis-ci.org/enova/business_calendar.svg)](https://travis-ci.org/enova/business_calendar)
5
- [![Coverage Status](https://coveralls.io/repos/github/enova/business_calendar/badge.svg?branch=master)](https://coveralls.io/github/enova/business_calendar?branch=master)
6
4
  [![Dependency Status](https://gemnasium.com/enova/business_calendar.svg)](https://gemnasium.com/enova/business_calendar)
7
5
 
8
6
  Need to know what days you can actually debit a customer on in excruciating detail? Fed up with singleton-based gems
@@ -32,7 +30,7 @@ Instantiate a calendar object with:
32
30
  bc = BusinessCalendar.for(:US)
33
31
  ```
34
32
 
35
- This will automatically load holidays based on the US banking holiday schedule, as configured in `data/holidays.yml`.
33
+ This will automatically load holidays based on the US banking holiday schedule, as configured in `data/US.yml`.
36
34
  Currently, this gem supports `:GB` and `:US` regions.
37
35
 
38
36
  Now, you can use it:
@@ -10,7 +10,7 @@ Gem::Specification.new do |spec|
10
10
  spec.email = ["rnubel@enova.com"]
11
11
  spec.description = %q{Helper gem for dealing with business days and date adjustment in multiple countries.}
12
12
  spec.summary = %q{Country-aware business-date logic and handling.}
13
- spec.homepage = ""
13
+ spec.homepage = "https://github.com/enova/business_calendar"
14
14
  spec.license = "MIT"
15
15
 
16
16
  spec.files = `git ls-files`.split($/)
@@ -18,15 +18,11 @@ Gem::Specification.new do |spec|
18
18
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
19
  spec.require_paths = ["lib"]
20
20
 
21
-
22
21
  spec.add_dependency "holidays", "~> 1.0"
23
22
  spec.add_dependency "faraday"
24
- spec.add_dependency "faraday-conductivity"
25
23
 
26
- spec.add_development_dependency "bundler", "~> 1.3"
27
24
  spec.add_development_dependency "rake"
28
25
  spec.add_development_dependency "rspec", "~> 3.2"
29
26
  spec.add_development_dependency "webmock"
30
- spec.add_development_dependency "simplecov"
31
27
  spec.add_development_dependency "pry"
32
28
  end
data/data/GB.yml CHANGED
@@ -26,12 +26,12 @@ GB:
26
26
  - '2015-12-26'
27
27
  - '2016-12-25'
28
28
  - '2017-01-01'
29
- - '2020-12-26'
30
- - '2021-12-25'
31
- - '2021-12-26'
32
- - '2022-01-01'
33
- - '2022-12-25'
34
- - '2023-01-01'
29
+ - '2020-12-28'
30
+ - '2021-12-27'
31
+ - '2021-12-28'
32
+ - '2022-01-03'
33
+ - '2022-12-27'
34
+ - '2023-01-02'
35
35
  removals:
36
36
  - '2002-05-28'
37
37
  - '2007-04-08'
@@ -44,9 +44,9 @@ GB:
44
44
  - '2013-03-31'
45
45
  - '2014-04-20'
46
46
  - '2015-12-28'
47
- - '2020-12-28'
48
- - '2021-12-27'
49
- - '2021-12-28'
50
- - '2022-01-03'
51
- - '2022-12-27'
52
- - '2023-01-02'
47
+ - '2020-12-26'
48
+ - '2021-12-25'
49
+ - '2021-12-26'
50
+ - '2022-01-01'
51
+ - '2022-12-25'
52
+ - '2023-01-01'
data/data/US.yml CHANGED
@@ -182,7 +182,6 @@ US:
182
182
  - '2021-01-18'
183
183
  - '2021-02-15'
184
184
  - '2021-05-31'
185
- - '2021-07-04'
186
185
  - '2021-07-05'
187
186
  - '2021-09-06'
188
187
  - '2021-10-11'
@@ -193,18 +192,18 @@ US:
193
192
  - '2022-01-17'
194
193
  - '2022-02-21'
195
194
  - '2022-05-30'
195
+ - '2022-06-20'
196
196
  - '2022-07-04'
197
197
  - '2022-09-05'
198
198
  - '2022-10-10'
199
199
  - '2022-11-11'
200
200
  - '2022-11-24'
201
- - '2022-12-25'
202
201
  - '2022-12-26'
203
- - '2023-01-01'
204
202
  - '2023-01-02'
205
203
  - '2023-01-16'
206
204
  - '2023-02-20'
207
205
  - '2023-05-29'
206
+ - '2023-06-19'
208
207
  - '2023-07-04'
209
208
  - '2023-09-04'
210
209
  - '2023-10-09'
@@ -215,6 +214,7 @@ US:
215
214
  - '2024-01-15'
216
215
  - '2024-02-19'
217
216
  - '2024-05-27'
217
+ - '2024-06-19'
218
218
  - '2024-07-04'
219
219
  - '2024-09-02'
220
220
  - '2024-10-14'
@@ -225,6 +225,7 @@ US:
225
225
  - '2025-01-20'
226
226
  - '2025-02-17'
227
227
  - '2025-05-26'
228
+ - '2025-06-19'
228
229
  - '2025-07-04'
229
230
  - '2025-09-01'
230
231
  - '2025-10-13'
@@ -0,0 +1,6 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gem 'json', '< 2.3.0'
4
+
5
+ # Specify your gem's dependencies in business_calendar.gemspec
6
+ gemspec :path => '../'
@@ -1,5 +1,4 @@
1
1
  require 'faraday'
2
- require 'faraday/conductivity'
3
2
 
4
3
  module BusinessCalendar
5
4
  CountryNotSupported = Class.new(StandardError)
@@ -18,8 +17,7 @@ module BusinessCalendar
18
17
  end
19
18
 
20
19
  def for_endpoint(additions, removals, options = {})
21
- ttl = options["ttl"] || 300
22
- Calendar.new(holiday_determiner_for_endpoint(additions, removals, options), options.merge({"ttl" => ttl}))
20
+ Calendar.new(holiday_determiner_for_endpoint(additions, removals, options), options)
23
21
  end
24
22
 
25
23
  private
@@ -46,18 +44,22 @@ module BusinessCalendar
46
44
  :additions_only => cfg['additions_only'] )
47
45
  end
48
46
 
47
+ def holiday_dates_for_endpoint(client, endpoint)
48
+ Proc.new { JSON.parse(client.get(endpoint).body).fetch('holidays').map { |s| Date.parse s } }
49
+ end
50
+
49
51
  def holiday_determiner_for_endpoint(additions_endpoint, removals_endpoint, opts)
50
52
  client = Faraday.new do |conn|
51
- conn.response :selective_errors
53
+ conn.response :raise_error
52
54
  conn.adapter :net_http
53
55
  end
54
56
 
55
57
  additions = if additions_endpoint
56
- Proc.new { JSON.parse(client.get(additions_endpoint).body).fetch('holidays').map { |s| Date.parse s } }
58
+ holiday_dates_for_endpoint(client, additions_endpoint)
57
59
  end
58
60
 
59
61
  removals = if removals_endpoint
60
- Proc.new { JSON.parse(client.get(removals_endpoint).body).fetch('holidays').map { |s| Date.parse s } }
62
+ holiday_dates_for_endpoint(client, removals_endpoint)
61
63
  end
62
64
 
63
65
  HolidayDeterminer.new(
@@ -65,7 +67,8 @@ module BusinessCalendar
65
67
  opts["holiday_names"] || [],
66
68
  :additions => additions,
67
69
  :removals => removals,
68
- :additions_only => opts["additions_only"] || []
70
+ :additions_only => opts["additions_only"] || [],
71
+ :ttl => opts['ttl']
69
72
  )
70
73
  end
71
74
 
@@ -1,9 +1,12 @@
1
1
  class BusinessCalendar::Calendar
2
+ DEFAULT_TIME_TO_LIVE = 24 * 60 * 60
2
3
  attr_reader :holiday_determiner
3
4
 
4
5
  # @param [Proc[Date -> Boolean]] a proc which returns whether or not a date is a
5
6
  # holiday.
6
7
  def initialize(holiday_determiner, options = {})
8
+ ttl = options['ttl']
9
+ @time_to_live = ttl.nil? ? DEFAULT_TIME_TO_LIVE : ttl
7
10
  @options = options
8
11
  @holiday_cache = {}
9
12
  @holiday_determiner = holiday_determiner
@@ -14,7 +17,7 @@ class BusinessCalendar::Calendar
14
17
  def is_holiday?(date)
15
18
  date = date.send(:to_date) if date.respond_to?(:to_date, true)
16
19
 
17
- clear_cache if @options["ttl"]
20
+ clear_cache if should_clear_cache?
18
21
 
19
22
  @holiday_cache[date] ||= holiday_determiner.call(date)
20
23
  end
@@ -22,7 +25,7 @@ class BusinessCalendar::Calendar
22
25
  # @param [Date] date
23
26
  # @return [Boolean] Whether or not banking can be done on <date>.
24
27
  def is_business_day?(date)
25
- return false if !@options["business_weekends"] && (date.saturday? || date.sunday?)
28
+ return false if !@options['business_weekends'] && (date.saturday? || date.sunday?)
26
29
  return false if is_holiday?(date)
27
30
  true
28
31
  end
@@ -108,6 +111,19 @@ class BusinessCalendar::Calendar
108
111
  end
109
112
 
110
113
  private
114
+
115
+ def should_clear_cache?
116
+ return false unless @time_to_live
117
+
118
+ # limit size using a heuristic, to prevent cache growing arbitrarily large
119
+ !@last_cleared || (Time.now - @last_cleared) >= @time_to_live || @holiday_cache.size > 365 * 3
120
+ end
121
+
122
+ def clear_cache
123
+ @last_cleared = Time.now
124
+ @holiday_cache = {}
125
+ end
126
+
111
127
  def with_one_or_many(thing_or_things)
112
128
  if thing_or_things.is_a? Enumerable
113
129
  thing_or_things.collect do |thing|
@@ -117,11 +133,4 @@ class BusinessCalendar::Calendar
117
133
  yield thing_or_things
118
134
  end
119
135
  end
120
-
121
- def clear_cache
122
- if !@issued_at || (Time.now - @issued_at) >= @options["ttl"]
123
- @issued_at = Time.now
124
- @holiday_cache = {}
125
- end
126
- end
127
136
  end
@@ -1,9 +1,12 @@
1
1
  require 'holidays'
2
2
 
3
3
  class BusinessCalendar::HolidayDeterminer
4
+ DEFAULT_TIME_TO_LIVE = 24 * 60 * 60
4
5
  attr_reader :regions, :holiday_names, :additions, :removals, :additions_only
5
6
 
6
7
  def initialize(regions, holiday_names, opts = {})
8
+ ttl = opts[:ttl]
9
+ @time_to_live = ttl.nil? ? DEFAULT_TIME_TO_LIVE : ttl
7
10
  @regions = regions
8
11
  @holiday_names = holiday_names
9
12
  @additions = opts[:additions] || []
@@ -12,6 +15,8 @@ class BusinessCalendar::HolidayDeterminer
12
15
  end
13
16
 
14
17
  def call(date)
18
+ clear_cache if should_clear_cache?
19
+
15
20
  if additions.include? date
16
21
  true
17
22
  elsif removals.include? date
@@ -23,11 +28,24 @@ class BusinessCalendar::HolidayDeterminer
23
28
  end
24
29
 
25
30
  private
31
+
32
+ def should_clear_cache?
33
+ return false unless @time_to_live
34
+
35
+ !@last_cleared || (Time.now - @last_cleared) >= @time_to_live
36
+ end
37
+
38
+ def clear_cache
39
+ @last_cleared = Time.now
40
+ @additions_cache = nil
41
+ @removals_cache = nil
42
+ end
43
+
26
44
  def additions
27
- @additions.is_a?(Proc) ? @additions.call : @additions
45
+ @additions_cache ||= @additions.is_a?(Proc) ? @additions.call : @additions
28
46
  end
29
47
 
30
48
  def removals
31
- @removals.is_a?(Proc) ? @removals.call : @removals
49
+ @removals_cache ||= @removals.is_a?(Proc) ? @removals.call : @removals
32
50
  end
33
51
  end
@@ -1,3 +1,3 @@
1
1
  module BusinessCalendar
2
- VERSION = "1.0.0"
2
+ VERSION = "3.0.0"
3
3
  end
@@ -20,12 +20,12 @@ describe "GB bank holidays" do
20
20
  2015-12-26
21
21
  2016-12-25
22
22
  2017-01-01
23
- 2020-12-26
24
- 2021-12-25
25
- 2021-12-26
26
- 2022-01-01
27
- 2022-12-25
28
- 2023-01-01
23
+ 2020-12-28
24
+ 2021-12-27
25
+ 2021-12-28
26
+ 2022-01-03
27
+ 2022-12-27
28
+ 2023-01-02
29
29
  ).map { |x| Date.parse x }.each do |expected_holiday|
30
30
  it "treats #{expected_holiday} as a holiday" do
31
31
  expect(BusinessCalendar.for(:GB).is_holiday?(expected_holiday)).to be true
@@ -35,12 +35,12 @@ describe "GB bank holidays" do
35
35
  %w(
36
36
  2012-05-28
37
37
  2015-12-28
38
- 2020-12-28
39
- 2021-12-27
40
- 2021-12-28
41
- 2022-01-03
42
- 2022-12-27
43
- 2023-01-02
38
+ 2020-12-26
39
+ 2021-12-25
40
+ 2021-12-26
41
+ 2022-01-01
42
+ 2022-12-25
43
+ 2023-01-01
44
44
  ).map { |x| Date.parse x }.each do |date|
45
45
  it "treats #{date} as not a holiday" do
46
46
  expect(BusinessCalendar.for(:GB).is_holiday?(date)).to be false
@@ -175,7 +175,6 @@ describe "US holidays" do
175
175
  2021-01-18
176
176
  2021-02-15
177
177
  2021-05-31
178
- 2021-07-04
179
178
  2021-07-05
180
179
  2021-09-06
181
180
  2021-10-11
@@ -186,18 +185,18 @@ describe "US holidays" do
186
185
  2022-01-17
187
186
  2022-02-21
188
187
  2022-05-30
188
+ 2022-06-20
189
189
  2022-07-04
190
190
  2022-09-05
191
191
  2022-10-10
192
192
  2022-11-11
193
193
  2022-11-24
194
- 2022-12-25
195
194
  2022-12-26
196
- 2023-01-01
197
195
  2023-01-02
198
196
  2023-01-16
199
197
  2023-02-20
200
198
  2023-05-29
199
+ 2023-06-19
201
200
  2023-07-04
202
201
  2023-09-04
203
202
  2023-10-09
@@ -208,6 +207,7 @@ describe "US holidays" do
208
207
  2024-01-15
209
208
  2024-02-19
210
209
  2024-05-27
210
+ 2024-06-19
211
211
  2024-07-04
212
212
  2024-09-02
213
213
  2024-10-14
@@ -218,6 +218,7 @@ describe "US holidays" do
218
218
  2025-01-20
219
219
  2025-02-17
220
220
  2025-05-26
221
+ 2025-06-19
221
222
  2025-07-04
222
223
  2025-09-01
223
224
  2025-10-13
@@ -2,7 +2,7 @@ require 'spec_helper'
2
2
 
3
3
  describe BusinessCalendar::HolidayDeterminer do
4
4
  let(:regions) { [ :us ] }
5
- let(:opts) {{}}
5
+ let(:opts) { {} }
6
6
  subject { BusinessCalendar::HolidayDeterminer.new(regions, ["Independence Day"], opts) }
7
7
 
8
8
  it "initializes with list of regions and a list of accepted holidays" do
@@ -191,38 +191,182 @@ describe BusinessCalendar do
191
191
  it_behaves_like "weekends as business days"
192
192
  end
193
193
 
194
- it 'hits the configured endpoint for each call to an addition or removal' do
194
+ it 'hits the configured endpoint and then reuses the cached result' do
195
+ subject.is_business_day?('2014-07-03'.to_date)
195
196
  subject.is_business_day?('2014-07-03'.to_date)
196
197
  subject.is_business_day?('2014-07-04'.to_date)
197
198
  subject.is_holiday?('2014-07-06'.to_date)
199
+ subject.is_holiday?('2014-07-06'.to_date)
198
200
  subject.is_holiday?('2014-12-24'.to_date)
199
201
 
200
- expect(a_request(:get, additions)).to have_been_made.times(4)
201
- expect(a_request(:get, removals)).to have_been_made.times(3)
202
+ expect(a_request(:get, additions)).to have_been_made.times(1)
203
+ expect(a_request(:get, removals)).to have_been_made.times(1)
202
204
  end
203
205
 
204
- it 'caches holidays for 5 min' do
205
- start = Time.now
206
+ context 'after 24 hours without specifying a Time to Live override' do
207
+ subject { BusinessCalendar.for_endpoint(additions, removals) }
208
+ let!(:start) { Time.now }
209
+ let!(:one_day) { 86400 }
206
210
 
207
- allow(Time).to receive(:now) { start }
211
+ it 'expires the holidays cache' do
212
+ allow(Time).to receive(:now) { start }
208
213
 
209
- subject.is_business_day?('2014-07-04'.to_date)
210
- subject.is_business_day?('2014-07-04'.to_date)
214
+ subject.is_business_day?('2014-01-01'.to_date)
211
215
 
212
- expect(a_request(:get, additions)).to have_been_made.times(1)
216
+ # initial request was made
217
+ expect(a_request(:get, additions)).to have_been_made.times(1)
213
218
 
214
- allow(Time).to receive(:now) { start + 301 }
219
+ subject.is_business_day?('2014-07-04'.to_date)
220
+ subject.is_business_day?('2014-11-28'.to_date)
215
221
 
216
- subject.is_business_day?('2014-07-04'.to_date)
222
+ # cache from initial request was still used
223
+ expect(a_request(:get, additions)).to have_been_made.times(1)
224
+
225
+ # 24 hours + 1 second have passed
226
+ # cache should be cleared and fresh API request made
227
+ allow(Time).to receive(:now) { start + one_day + 1 }
228
+
229
+ subject.is_business_day?('2014-01-01'.to_date)
230
+
231
+ # 2nd request was made
232
+ expect(a_request(:get, additions)).to have_been_made.times(2)
233
+
234
+ subject.is_business_day?('2014-07-04'.to_date)
235
+ subject.is_business_day?('2014-11-28'.to_date)
236
+
237
+ # 2nd request is now cached, so no new request should have been issued
238
+ expect(a_request(:get, additions)).to have_been_made.times(2)
239
+ end
240
+ end
241
+
242
+ context 'turning off Time to Live functionality' do
243
+ subject { BusinessCalendar.for_endpoint(additions, removals, {'ttl' => false}) }
244
+ let!(:start) { Time.now }
245
+ let!(:one_day) { 86400 }
246
+
247
+ it 'will never clear the cache' do
248
+ allow(Time).to receive(:now) { start }
249
+
250
+ subject.is_business_day?('2014-01-01'.to_date)
251
+ expect(a_request(:get, additions)).to have_been_made.times(1)
252
+
253
+ allow(Time).to receive(:now) { start + 301 }
254
+ subject.is_business_day?('2014-01-01'.to_date)
255
+ subject.is_business_day?('2014-07-04'.to_date)
256
+ subject.is_business_day?('2014-07-04'.to_date)
257
+ subject.is_business_day?('2014-11-28'.to_date)
258
+ expect(a_request(:get, additions)).to have_been_made.times(1)
259
+
260
+ allow(Time).to receive(:now) { start + one_day + 1 }
261
+ subject.is_business_day?('2014-01-01'.to_date)
262
+ subject.is_business_day?('2014-07-04'.to_date)
263
+ subject.is_business_day?('2014-11-28'.to_date)
264
+ expect(a_request(:get, additions)).to have_been_made.times(1)
265
+ end
266
+ end
267
+
268
+ context 'setting Time to Live override to zero' do
269
+ subject { BusinessCalendar.for_endpoint(additions, removals, {'ttl' => 0}) }
270
+ let!(:start) { Time.now }
271
+
272
+ it 'will always clear the cache' do
273
+ allow(Time).to receive(:now) { start }
274
+
275
+ subject.is_holiday?('2014-01-01'.to_date)
276
+ expect(a_request(:get, additions)).to have_been_made.times(1)
277
+
278
+ allow(Time).to receive(:now) { start + 301 }
279
+
280
+ subject.is_holiday?('2014-01-01'.to_date)
281
+ subject.is_holiday?('2014-07-04'.to_date)
282
+ subject.is_holiday?('2014-07-04'.to_date)
283
+ subject.is_holiday?('2014-11-28'.to_date)
284
+
285
+ expect(a_request(:get, additions)).to have_been_made.times(5)
286
+
287
+ subject.is_holiday?('2014-01-01'.to_date)
288
+ subject.is_holiday?('2014-01-01'.to_date)
289
+ subject.is_holiday?('2014-07-04'.to_date)
290
+ subject.is_holiday?('2014-11-28'.to_date)
291
+ expect(a_request(:get, additions)).to have_been_made.times(9)
292
+ end
293
+ end
217
294
 
218
- expect(a_request(:get, additions)).to have_been_made.times(2)
295
+ context 'using a 5 minute Time to Live override' do
296
+ subject { BusinessCalendar.for_endpoint(additions, removals, {'ttl' => 300}) }
297
+ let!(:start) { Time.now }
298
+
299
+ it 'expires the holidays cache after the specified time has elapsed' do
300
+ allow(Time).to receive(:now) { start }
301
+
302
+ # initial request made and cached
303
+ subject.is_business_day?('2014-01-01'.to_date)
304
+ subject.is_business_day?('2014-07-04'.to_date)
305
+ subject.is_business_day?('2014-11-28'.to_date)
306
+ expect(a_request(:get, additions)).to have_been_made.times(1)
307
+
308
+ # 120 seconds pass, cache should still be used
309
+ allow(Time).to receive(:now) { start + 120 }
310
+ subject.is_business_day?('2014-01-01'.to_date)
311
+ subject.is_business_day?('2014-07-04'.to_date)
312
+ subject.is_business_day?('2014-11-28'.to_date)
313
+ expect(a_request(:get, additions)).to have_been_made.times(1)
314
+
315
+ # 301 seconds pass, cache is expired and 2nd request made
316
+ allow(Time).to receive(:now) { start + 301 }
317
+ subject.is_business_day?('2014-01-01'.to_date)
318
+ subject.is_business_day?('2014-07-04'.to_date)
319
+ subject.is_business_day?('2014-11-28'.to_date)
320
+ expect(a_request(:get, additions)).to have_been_made.times(2)
321
+
322
+ # 2nd request is now cached, so no new request should have been issued
323
+ subject.is_business_day?('2014-01-01'.to_date)
324
+ subject.is_business_day?('2014-07-04'.to_date)
325
+ subject.is_business_day?('2014-11-28'.to_date)
326
+ expect(a_request(:get, additions)).to have_been_made.times(2)
327
+ end
328
+ end
329
+
330
+ context 'with a few years in dates filling the cache' do
331
+ let!(:start) { Time.now }
332
+
333
+ # NOTE: this test cheats a bit to test class internals / implementation
334
+ it 'will clear out the holiday cache but keep the cached API result' do
335
+ allow(Time).to receive(:now) { start }
336
+ subject.is_business_day?('2014-07-04'.to_date)
337
+ expect(a_request(:get, additions)).to have_been_made.times(1)
338
+
339
+ expect(a_request(:get, additions)).to have_been_made.times(1)
340
+ (2014..2017).each do |year|
341
+ (1..12).each do |month|
342
+ (1..28).each do |day|
343
+ subject.is_business_day?("#{year}-#{month}-#{day}".to_date)
344
+ end
345
+ end
346
+ end
347
+
348
+ # about to reach cache size threshold
349
+ expect(subject.instance_variable_get('@holiday_cache').size).to be(960)
350
+
351
+ (1..7).each do |month|
352
+ (1..28).each do |day|
353
+ subject.is_business_day?("#2018-#{month}-#{day}".to_date)
354
+ end
355
+ end
356
+
357
+ # now holiday cache will have been cleared and should be small again
358
+ expect(subject.instance_variable_get('@holiday_cache').size).to be(4)
359
+
360
+ # and cached API response was still used
361
+ expect(a_request(:get, additions)).to have_been_made.times(1)
362
+ end
219
363
  end
220
364
 
221
365
  context 'http request fails' do
222
366
  before { stub_request(:get, additions).to_return(:status => 500) }
223
367
 
224
368
  it 'raises an error' do
225
- expect { subject.is_business_day?('2014-07-04'.to_date) }.to raise_error Faraday::ClientError
369
+ expect { subject.is_business_day?('2014-07-04'.to_date) }.to raise_error Faraday::Error
226
370
  end
227
371
  end
228
372
 
data/spec/spec_helper.rb CHANGED
@@ -1,8 +1,6 @@
1
1
  require 'bundler/setup'
2
2
  Bundler.setup
3
3
 
4
- require 'simplecov'
5
-
6
4
  require 'business_calendar'
7
5
  require 'date'
8
6
  require 'webmock/rspec'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: business_calendar
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 3.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Robert Nubel
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-11-12 00:00:00.000000000 Z
11
+ date: 2021-08-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: holidays
@@ -38,34 +38,6 @@ dependencies:
38
38
  - - ">="
39
39
  - !ruby/object:Gem::Version
40
40
  version: '0'
41
- - !ruby/object:Gem::Dependency
42
- name: faraday-conductivity
43
- requirement: !ruby/object:Gem::Requirement
44
- requirements:
45
- - - ">="
46
- - !ruby/object:Gem::Version
47
- version: '0'
48
- type: :runtime
49
- prerelease: false
50
- version_requirements: !ruby/object:Gem::Requirement
51
- requirements:
52
- - - ">="
53
- - !ruby/object:Gem::Version
54
- version: '0'
55
- - !ruby/object:Gem::Dependency
56
- name: bundler
57
- requirement: !ruby/object:Gem::Requirement
58
- requirements:
59
- - - "~>"
60
- - !ruby/object:Gem::Version
61
- version: '1.3'
62
- type: :development
63
- prerelease: false
64
- version_requirements: !ruby/object:Gem::Requirement
65
- requirements:
66
- - - "~>"
67
- - !ruby/object:Gem::Version
68
- version: '1.3'
69
41
  - !ruby/object:Gem::Dependency
70
42
  name: rake
71
43
  requirement: !ruby/object:Gem::Requirement
@@ -108,20 +80,6 @@ dependencies:
108
80
  - - ">="
109
81
  - !ruby/object:Gem::Version
110
82
  version: '0'
111
- - !ruby/object:Gem::Dependency
112
- name: simplecov
113
- requirement: !ruby/object:Gem::Requirement
114
- requirements:
115
- - - ">="
116
- - !ruby/object:Gem::Version
117
- version: '0'
118
- type: :development
119
- prerelease: false
120
- version_requirements: !ruby/object:Gem::Requirement
121
- requirements:
122
- - - ">="
123
- - !ruby/object:Gem::Version
124
- version: '0'
125
83
  - !ruby/object:Gem::Dependency
126
84
  name: pry
127
85
  requirement: !ruby/object:Gem::Requirement
@@ -144,10 +102,10 @@ executables: []
144
102
  extensions: []
145
103
  extra_rdoc_files: []
146
104
  files:
105
+ - ".github/workflows/ci.yml"
106
+ - ".github/workflows/gem-push.yml"
147
107
  - ".gitignore"
148
108
  - ".rspec"
149
- - ".simplecov"
150
- - ".travis.yml"
151
109
  - CHANGELOG.md
152
110
  - Gemfile
153
111
  - LICENSE.txt
@@ -160,6 +118,7 @@ files:
160
118
  - data/US.yml
161
119
  - data/org/captalys.yml
162
120
  - gemfiles/ree.gemfile
121
+ - gemfiles/ruby_1.9.3.gemfile
163
122
  - lib/business_calendar.rb
164
123
  - lib/business_calendar/calendar.rb
165
124
  - lib/business_calendar/holiday_determiner.rb
@@ -173,7 +132,7 @@ files:
173
132
  - spec/business_calendar/holiday_determiner_spec.rb
174
133
  - spec/business_calendar_spec.rb
175
134
  - spec/spec_helper.rb
176
- homepage: ''
135
+ homepage: https://github.com/enova/business_calendar
177
136
  licenses:
178
137
  - MIT
179
138
  metadata: {}
@@ -192,8 +151,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
192
151
  - !ruby/object:Gem::Version
193
152
  version: '0'
194
153
  requirements: []
195
- rubyforge_project:
196
- rubygems_version: 2.7.8
154
+ rubygems_version: 3.1.6
197
155
  signing_key:
198
156
  specification_version: 4
199
157
  summary: Country-aware business-date logic and handling.
data/.simplecov DELETED
@@ -1,8 +0,0 @@
1
- unless RUBY_VERSION == "1.8.7"
2
- require 'coveralls'
3
-
4
- Coveralls.wear! do
5
- add_filter "version.rb"
6
- add_filter "spec/"
7
- end
8
- end
data/.travis.yml DELETED
@@ -1,25 +0,0 @@
1
- sudo: false
2
- language: ruby
3
- cache: bundler
4
-
5
- matrix:
6
- include:
7
- - rmv: ree
8
- gemfile: gemfiles/ree.gemfile
9
- script: bundle exec rspec
10
- - rvm: 1.9.3
11
- script: bundle exec rspec
12
- - rvm: 2.3.1
13
- script: bundle exec rspec
14
-
15
- env:
16
- global:
17
- secure: klQ3BQNdKkGIq3Zfv8Sr6oZqaMg9GHx+LGhynQ5xt7A6SGvxbMuoXJlE9wW4me6u+B9S2D+q+pdKgGPE/+fHsVt/d0zDXRnrjVyo1eexT220AMfpjiFDAtya8sAcuuAICLhw8AmzTjns8yAWInv5U5vC6oejkLA71FXtHU//210=
18
-
19
- deploy:
20
- provider: rubygems
21
- api_key:
22
- secure: "c9QcNX+nk0Yzl22ZVknZtv+/G4fUeLph0TH9PS6hQ92zywM61wtXTJExZdBHulvKAaQbKaq20LcFjLw41qwJc6tvIEYXpOinfOaiemTKaGmKscDrIi3wUAxfjmTacXZAaTdEORQVqTOmppqsuKnP7cqmSNxTSI901Sz5Cy6Jpl0="
23
- on:
24
- tags: true
25
- repo: enova/business_calendar