business_calendar 1.0.0 → 3.0.0

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