business 2.2.1 → 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: ecbb66616260ec96ece9868d9eb337e31fc3ffb791caf58b9f05010ebd7354dc
4
- data.tar.gz: 2f13155d38f7fad38db25cbe49378af3166b2c99886379dc2b6c8a2dce7282c6
3
+ metadata.gz: c7aab6453e38130641098c102e0b05f1d5c40cd957710c80f3b53f4670175ce6
4
+ data.tar.gz: 2ae585a9a460e340db0821c755a787795e7504914af8febb247c451beb8f37f2
5
5
  SHA512:
6
- metadata.gz: 13c43f3e262bac0402194d0c9bd98cbd5090b38ec2ed7cb25e66087c831127db1c313661672d74fdf4fe26f3073daab0d5377b345d8d71cfd5137173b2192b0d
7
- data.tar.gz: eb039def7aceb3e4936940daaac94e8c2f64c9be8ae92fc776e5c77f3c8f8ceb315a5c6720ecec92c9b5ae7b55ab19dc9752e403d6957bc9cc141ad8a06efad8
6
+ metadata.gz: ffe309242e862845535efb526d3f4ec95852bd685f026c97fdac4bcd4eec88051e47481f5d1354b98a9f34af47dd4858b417fe173b6f9498b52c226a6c491bac
7
+ data.tar.gz: 322e9a49e3fcec9078b6c5e1d78eb6ea0d4abc0bc47f95550cbb6069d72715c00d8c5868279bfdfc5e69776e7e85cea03fc3994441522ff7a84abb82411e867f
@@ -0,0 +1,7 @@
1
+ version: 2
2
+ updates:
3
+ - package-ecosystem: bundler
4
+ directory: "/"
5
+ schedule:
6
+ interval: daily
7
+ open-pull-requests-limit: 10
@@ -0,0 +1,36 @@
1
+ name: tests
2
+
3
+ on:
4
+ push:
5
+ branches:
6
+ - "master"
7
+ pull_request:
8
+
9
+ jobs:
10
+ rubocop:
11
+ runs-on: ubuntu-latest
12
+ steps:
13
+ - uses: actions/checkout@v7
14
+ - name: Set up Ruby
15
+ uses: ruby/setup-ruby@v1
16
+ with:
17
+ bundler-cache: true
18
+ # Maintain consistency with the TargetRubyVersion in .rubocop.yml
19
+ ruby-version: "3.3"
20
+ - name: Run rubocop
21
+ run: bundle exec rubocop --parallel --extra-details --display-style-guide
22
+
23
+ tests:
24
+ strategy:
25
+ matrix:
26
+ ruby-version: ["3.3", "3.4", "4.0"]
27
+ runs-on: ubuntu-latest
28
+ steps:
29
+ - uses: actions/checkout@v7
30
+ - name: Set up Ruby
31
+ uses: ruby/setup-ruby@v1
32
+ with:
33
+ bundler-cache: true
34
+ ruby-version: "${{ matrix.ruby-version }}"
35
+ - name: Run tests
36
+ run: bundle exec rspec
data/.rubocop.yml CHANGED
@@ -1,6 +1,10 @@
1
1
  inherit_gem:
2
2
  gc_ruboconfig: rubocop.yml
3
3
 
4
+ AllCops:
5
+ TargetRubyVersion: 3.3
6
+ NewCops: enable
7
+
4
8
  RSpec/NestedGroups:
5
9
  Exclude:
6
10
  - spec/business/calendar_spec.rb
@@ -11,3 +15,6 @@ Naming/AccessorMethodName:
11
15
 
12
16
  Gemspec/RequiredRubyVersion:
13
17
  Enabled: false
18
+
19
+ RSpec/IndexedLet:
20
+ Enabled: false
data/.ruby-version CHANGED
@@ -1 +1 @@
1
- 2.7.1
1
+ 4.0.6
data/CHANGELOG.md CHANGED
@@ -1,3 +1,15 @@
1
+ ## Upcoming
2
+
3
+ # 3.0.0 - Sept 29, 2026
4
+
5
+ - Removed support for Ruby 3.2 and earlier
6
+ - Prevent path traversal when loading calendar files #331
7
+
8
+ ## 2.3.0 - Jan 31, 2022
9
+
10
+ - Added permitted classes to YAML's `safe_load` #112 - thanks @attack
11
+ - Removed support for Ruby 2.4, 2.5
12
+
1
13
  ## 2.2.1 - March 9, 2021
2
14
 
3
15
  - Fix regression on `Calendar#new` #83 - thanks @ineu!
data/COMPATIBILITY.md ADDED
@@ -0,0 +1,21 @@
1
+ # Compatibility
2
+
3
+ ## v2.0.0 breaking changes
4
+
5
+ We have removed the bundled calendars as of version 2.0.0, if you need the calendars that were included:
6
+
7
+ - Download the calendars you wish to use from [v1.18.0](https://github.com/gocardless/business/tree/b12c186ca6fd4ffdac85175742ff7e4d0a705ef4/lib/business/data)
8
+ - Place them in a suitable directory in your project, typically `lib/calendars`
9
+ - Add this directory path to your instance of `Business::Calendar` using the `load_paths` method.dd the directory to where you placed the yml files before you load the calendar
10
+
11
+ ```ruby
12
+ Business::Calendar.load_paths = ["lib/calendars"] # your_project/lib/calendars/ contains bacs.yml
13
+ Business::Calendar.load("bacs")
14
+ ```
15
+
16
+ If you wish to stay on the last version that contained bundled calendars, pin `business` to `v1.18.0`
17
+
18
+ ```ruby
19
+ # Gemfile
20
+ gem "business", "v1.18.0"
21
+ ```
data/Gemfile CHANGED
@@ -3,3 +3,9 @@
3
3
  source 'https://rubygems.org'
4
4
 
5
5
  gemspec
6
+
7
+ group :test, :development do
8
+ gem "gc_ruboconfig", "~> 6"
9
+ gem "rspec", "~> 3.4"
10
+ gem "rubocop", "~> 1.91"
11
+ end
data/README.md CHANGED
@@ -1,39 +1,18 @@
1
1
  # Business
2
2
 
3
3
  [![Gem version](https://badge.fury.io/rb/business.svg)](http://badge.fury.io/rb/business)
4
- [![CircleCI](https://circleci.com/gh/gocardless/business.svg?style=svg)](https://circleci.com/gh/gocardless/business)
5
4
 
6
5
  Date calculations based on business calendars.
7
6
 
8
- - [v2.0.0 breaking changes](#v200-breaking-changes)
9
7
  - [Getting Started](#getting-started)
10
8
  - [Creating a calendar](#creating-a-calendar)
11
9
  - [Using a calendar file](#use-a-calendar-file)
12
10
  - [Checking for business days](#checking-for-business-days)
13
11
  - [Business day arithmetic](#business-day-arithmetic)
14
12
  - [But other libraries already do this](#but-other-libraries-already-do-this)
13
+ - [Compatibility](#compatibility)
15
14
  - [License & Contributing](#license--contributing)
16
15
 
17
- ## v2.0.0 breaking changes
18
-
19
- We have removed the bundled calendars as of version 2.0.0, if you need the calendars that were included:
20
-
21
- - Download the calendars you wish to use from [v1.18.0](https://github.com/gocardless/business/tree/b12c186ca6fd4ffdac85175742ff7e4d0a705ef4/lib/business/data)
22
- - Place them in a suitable directory in your project, typically `lib/calendars`
23
- - Add this directory path to your instance of `Business::Calendar` using the `load_paths` method.dd the directory to where you placed the yml files before you load the calendar
24
-
25
- ```ruby
26
- Business::Calendar.load_paths = ["lib/calendars"] # your_project/lib/calendars/ contains bacs.yml
27
- Business::Calendar.load("bacs")
28
- ```
29
-
30
- If you wish to stay on the last version that contained bundled calendars, pin `business` to `v1.18.0`
31
-
32
- ```ruby
33
- # Gemfile
34
- gem "business", "v1.18.0"
35
- ```
36
-
37
16
  ## Getting started
38
17
 
39
18
  To install business, simply:
@@ -45,7 +24,7 @@ gem install business
45
24
  If you are using a Gemfile:
46
25
 
47
26
  ```ruby
48
- gem "business", "~> 2.0"
27
+ gem "business", "~> 3.0"
49
28
  ```
50
29
 
51
30
  ### Creating a calendar
@@ -189,6 +168,12 @@ Secondly, business_time supports calculations on times as well as dates. For our
189
168
 
190
169
  <p align="center"><img src="http://3.bp.blogspot.com/-aq4iOz2OZzs/Ty8xaQwMhtI/AAAAAAAABrM/-vn4tcRA9-4/s1600/daily-morning-awesomeness-243.jpeg" alt="I'm late for business" width="250"/></p>
191
170
 
171
+ # Compatibility
172
+
173
+ ## v2.0.0 Breaking Changes
174
+
175
+ - See [COMPATIBILITY.md](COMPATIBILITY.md#v200-breaking-changes)
176
+
192
177
  ## License & Contributing
193
178
  - business is available as open source under the terms of the [MIT License](LICENSE).
194
179
  - Bug reports and pull requests are welcome on GitHub at https://github.com/gocardless/business.
data/business.gemspec CHANGED
@@ -8,7 +8,7 @@ require "business/version"
8
8
  Gem::Specification.new do |spec|
9
9
  spec.name = "business"
10
10
  spec.version = Business::VERSION
11
- spec.authors = ["Harry Marr"]
11
+ spec.authors = ["GoCcardless"]
12
12
  spec.email = ["developers@gocardless.com"]
13
13
  spec.summary = "Date calculations based on business calendars"
14
14
  spec.description = "Date calculations based on business calendars"
@@ -16,12 +16,7 @@ Gem::Specification.new do |spec|
16
16
  spec.licenses = ["MIT"]
17
17
 
18
18
  spec.files = `git ls-files`.split($INPUT_RECORD_SEPARATOR)
19
- spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
20
- spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
21
19
  spec.require_paths = ["lib"]
22
20
 
23
- spec.add_development_dependency "gc_ruboconfig", "~> 2.24.0"
24
- spec.add_development_dependency "rspec", "~> 3.1"
25
- spec.add_development_dependency "rspec_junit_formatter", "~> 0.4.1"
26
- spec.add_development_dependency "rubocop", "~> 1.10.0"
21
+ spec.metadata["rubygems_mfa_required"] = "true"
27
22
  end
@@ -2,11 +2,17 @@
2
2
 
3
3
  require "yaml"
4
4
  require "date"
5
+ require "pathname"
5
6
 
6
7
  module Business
7
8
  class Calendar
8
9
  VALID_KEYS = %w[holidays working_days extra_working_dates].freeze
9
10
 
11
+ # Calendar names are interpolated into a filesystem path, so restrict them to
12
+ # plain identifiers: anything containing path separators, "..", or an absolute
13
+ # path would otherwise load a file from outside the configured load_paths.
14
+ CALENDAR_NAME_FORMAT = /\A[a-zA-Z0-9_-]+\z/
15
+
10
16
  class << self
11
17
  attr_accessor :load_paths
12
18
  end
@@ -16,7 +22,6 @@ module Business
16
22
  end
17
23
  private_class_method :calendar_directories
18
24
 
19
- # rubocop:disable Metrics/MethodLength
20
25
  def self.load(calendar_name)
21
26
  data = find_calendar_data(calendar_name)
22
27
  raise "No such calendar '#{calendar_name}'" unless data
@@ -32,20 +37,31 @@ module Business
32
37
  extra_working_dates: data["extra_working_dates"],
33
38
  )
34
39
  end
35
- # rubocop:enable Metrics/MethodLength
36
40
 
37
41
  def self.find_calendar_data(calendar_name)
38
42
  calendar_directories.detect do |path|
39
43
  if path.is_a?(Hash)
40
44
  break path[calendar_name] if path[calendar_name]
41
45
  else
42
- next unless File.exist?(File.join(path, "#{calendar_name}.yml"))
46
+ calendar_path = calendar_path_for(path, calendar_name)
47
+ next unless calendar_path.exist?
43
48
 
44
- break YAML.load_file(File.join(path, "#{calendar_name}.yml"))
49
+ break YAML.safe_load(calendar_path.read, permitted_classes: [Date])
45
50
  end
46
51
  end
47
52
  end
48
53
 
54
+ # Only applied to directory load_paths; hash load_paths are a plain key
55
+ # lookup and cannot escape anywhere, so their keys stay unrestricted.
56
+ def self.calendar_path_for(directory, calendar_name)
57
+ unless calendar_name.to_s.match?(CALENDAR_NAME_FORMAT)
58
+ raise ArgumentError, "invalid calendar name: #{calendar_name.inspect}"
59
+ end
60
+
61
+ Pathname.new(directory).join("#{calendar_name}.yml")
62
+ end
63
+ private_class_method :calendar_path_for
64
+
49
65
  @lock = Mutex.new
50
66
  def self.load_cached(calendar)
51
67
  @lock.synchronize do
@@ -65,7 +81,7 @@ module Business
65
81
  set_working_days(working_days)
66
82
  set_holidays(holidays)
67
83
 
68
- unless (@holidays & @extra_working_dates).none?
84
+ if @holidays.intersect?(@extra_working_dates)
69
85
  raise ArgumentError, "Holidays cannot be extra working dates"
70
86
  end
71
87
  end
@@ -129,10 +145,7 @@ module Business
129
145
  def add_business_days(date, delta)
130
146
  date = roll_forward(date)
131
147
  delta.times do
132
- loop do
133
- date += day_interval_for(date)
134
- break date if business_day?(date)
135
- end
148
+ date = next_business_day(date)
136
149
  end
137
150
  date
138
151
  end
@@ -145,10 +158,7 @@ module Business
145
158
  def subtract_business_days(date, delta)
146
159
  date = roll_backward(date)
147
160
  delta.times do
148
- loop do
149
- date -= day_interval_for(date)
150
- break date if business_day?(date)
151
- end
161
+ date = previous_business_day(date)
152
162
  end
153
163
  date
154
164
  end
@@ -156,8 +166,6 @@ module Business
156
166
  # Count the number of business days between two dates.
157
167
  # This method counts from start of date1 to start of date2. So,
158
168
  # business_days_between(mon, weds) = 2 (assuming no holidays)
159
- # rubocop:disable Metrics/AbcSize
160
- # rubocop:disable Metrics/MethodLength
161
169
  def business_days_between(date1, date2)
162
170
  date1 = date1.to_date
163
171
  date2 = date2.to_date
@@ -186,12 +194,10 @@ module Business
186
194
  in_range && on_biz_day
187
195
  end
188
196
 
189
- remaining_range = (date2 - remaining_days...date2)
197
+ remaining_range = ((date2 - remaining_days)...date2)
190
198
  # Loop through each day in remaining_range and count if a business day
191
199
  num_biz_days + remaining_range.count { |a| business_day?(a) }
192
200
  end
193
- # rubocop:enable Metrics/AbcSize
194
- # rubocop:enable Metrics/MethodLength
195
201
 
196
202
  def day_interval_for(date)
197
203
  date.is_a?(Date) ? 1 : 3600 * 24
@@ -207,7 +213,7 @@ module Business
207
213
  extra_working_dates_names = @extra_working_dates.map do |d|
208
214
  d.strftime("%a").downcase
209
215
  end
210
- return if (extra_working_dates_names & @working_days).none?
216
+ return if !extra_working_dates_names.intersect?(@working_days)
211
217
 
212
218
  raise ArgumentError, "Extra working dates cannot be on working days"
213
219
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Business
4
- VERSION = "2.2.1"
4
+ VERSION = "3.0.0"
5
5
  end
@@ -2,6 +2,7 @@
2
2
 
3
3
  require "business/calendar"
4
4
  require "time"
5
+ require "fileutils"
5
6
 
6
7
  RSpec.configure do |config|
7
8
  config.mock_with(:rspec) { |mocks| mocks.verify_partial_doubles = true }
@@ -13,9 +14,9 @@ RSpec.describe Business::Calendar do
13
14
  subject(:load_calendar) { described_class.load(calendar) }
14
15
 
15
16
  let(:dummy_calendar) { { "working_days" => ["monday"] } }
17
+ let(:fixture_path) { File.join(File.dirname(__FILE__), "../fixtures", "calendars") }
16
18
 
17
19
  before do
18
- fixture_path = File.join(File.dirname(__FILE__), "../fixtures", "calendars")
19
20
  described_class.load_paths = [fixture_path, { "foobar" => dummy_calendar }]
20
21
  end
21
22
 
@@ -25,7 +26,10 @@ RSpec.describe Business::Calendar do
25
26
  after { described_class.load_paths = nil }
26
27
 
27
28
  it "loads the yaml file" do
28
- expect(YAML).to receive(:load_file).with(/ecb\.yml$/).and_return({})
29
+ path = Pathname.new(fixture_path).join("ecb.yml")
30
+ expect(YAML).to receive(:safe_load).
31
+ with(path.read, permitted_classes: [Date]).
32
+ and_return({})
29
33
 
30
34
  load_calendar
31
35
  end
@@ -54,6 +58,71 @@ RSpec.describe Business::Calendar do
54
58
  specify { expect { load_calendar }.to raise_error(/No such calendar/) }
55
59
  end
56
60
 
61
+ context "when given a calendar name that escapes the load path" do
62
+ before do
63
+ outside = File.join(File.dirname(__FILE__), "../fixtures")
64
+ FileUtils.mkdir_p(File.join(outside, "outside"))
65
+ File.write(
66
+ File.join(outside, "outside", "escaped.yml"),
67
+ { "working_days" => ["monday"] }.to_yaml,
68
+ )
69
+ end
70
+
71
+ after do
72
+ FileUtils.rm_rf(File.join(File.dirname(__FILE__), "../fixtures", "outside"))
73
+ end
74
+
75
+ context "with a relative traversal" do
76
+ let(:calendar) { "../outside/escaped" }
77
+
78
+ specify do
79
+ expect { load_calendar }.
80
+ to raise_error(ArgumentError, /invalid calendar name/)
81
+ end
82
+ end
83
+
84
+ context "with an absolute path" do
85
+ let(:calendar) do
86
+ File.join(File.dirname(__FILE__), "../fixtures", "outside", "escaped")
87
+ end
88
+
89
+ specify do
90
+ expect { load_calendar }.
91
+ to raise_error(ArgumentError, /invalid calendar name/)
92
+ end
93
+ end
94
+
95
+ context "with a bare path separator" do
96
+ let(:calendar) { "outside/escaped" }
97
+
98
+ specify do
99
+ expect { load_calendar }.
100
+ to raise_error(ArgumentError, /invalid calendar name/)
101
+ end
102
+ end
103
+
104
+ context "with a name that is only dots" do
105
+ let(:calendar) { ".." }
106
+
107
+ specify do
108
+ expect { load_calendar }.
109
+ to raise_error(ArgumentError, /invalid calendar name/)
110
+ end
111
+ end
112
+
113
+ context "when the escaping name is a key in a hash load path" do
114
+ before do
115
+ described_class.load_paths = [{ "../outside/escaped" => dummy_calendar }]
116
+ end
117
+
118
+ let(:calendar) { "../outside/escaped" }
119
+
120
+ it "still resolves, since hash lookups touch no filesystem" do
121
+ expect(load_calendar).to be_a described_class
122
+ end
123
+ end
124
+ end
125
+
57
126
  context "when given a calendar that has invalid keys" do
58
127
  let(:calendar) { "invalid-keys" }
59
128
 
@@ -337,7 +406,7 @@ RSpec.describe Business::Calendar do
337
406
  context "followed by another non-business day" do
338
407
  let(:date) { date_class.parse("Saturday 5th Jan, 2013") }
339
408
 
340
- it { is_expected.to eq(date + 2 * day_interval) }
409
+ it { is_expected.to eq(date + (2 * day_interval)) }
341
410
  end
342
411
  end
343
412
  end
@@ -365,7 +434,7 @@ RSpec.describe Business::Calendar do
365
434
  context "preceeded by another non-business day" do
366
435
  let(:date) { date_class.parse("Sunday 6th Jan, 2013") }
367
436
 
368
- it { is_expected.to eq(date - 2 * day_interval) }
437
+ it { is_expected.to eq(date - (2 * day_interval)) }
369
438
  end
370
439
  end
371
440
  end
@@ -393,7 +462,7 @@ RSpec.describe Business::Calendar do
393
462
  context "followed by another non-business day" do
394
463
  let(:date) { date_class.parse("Saturday 5th Jan, 2013") }
395
464
 
396
- it { is_expected.to eq(date + 2 * day_interval) }
465
+ it { is_expected.to eq(date + (2 * day_interval)) }
397
466
  end
398
467
  end
399
468
  end
@@ -421,7 +490,7 @@ RSpec.describe Business::Calendar do
421
490
  context "preceeded by another non-business day" do
422
491
  let(:date) { date_class.parse("Sunday 6th Jan, 2013") }
423
492
 
424
- it { is_expected.to eq(date - 2 * day_interval) }
493
+ it { is_expected.to eq(date - (2 * day_interval)) }
425
494
  end
426
495
  end
427
496
  end
@@ -441,33 +510,33 @@ RSpec.describe Business::Calendar do
441
510
  context "and a period that includes only business days" do
442
511
  let(:date) { date_class.parse("Wednesday 2nd Jan, 2013") }
443
512
 
444
- it { is_expected.to eq(date + delta * day_interval) }
513
+ it { is_expected.to eq(date + (delta * day_interval)) }
445
514
  end
446
515
 
447
516
  context "and a period that includes a weekend" do
448
517
  let(:date) { date_class.parse("Friday 4th Jan, 2013") }
449
518
 
450
- it { is_expected.to eq(date + (delta + 2) * day_interval) }
519
+ it { is_expected.to eq(date + ((delta + 2) * day_interval)) }
451
520
  end
452
521
 
453
522
  context "and a period that includes a working date weekend" do
454
523
  let(:extra_working_dates) { ["Sunday 6th Jan, 2013"] }
455
524
  let(:date) { date_class.parse("Friday 4th Jan, 2013") }
456
525
 
457
- it { is_expected.to eq(date + (delta + 1) * day_interval) }
526
+ it { is_expected.to eq(date + ((delta + 1) * day_interval)) }
458
527
  end
459
528
 
460
529
  context "and a period that includes a holiday day" do
461
530
  let(:date) { date_class.parse("Monday 31st Dec, 2012") }
462
531
 
463
- it { is_expected.to eq(date + (delta + 1) * day_interval) }
532
+ it { is_expected.to eq(date + ((delta + 1) * day_interval)) }
464
533
  end
465
534
  end
466
535
 
467
536
  context "given a non-business day" do
468
537
  let(:date) { date_class.parse("Tuesday 1st Jan, 2013") }
469
538
 
470
- it { is_expected.to eq(date + (delta + 1) * day_interval) }
539
+ it { is_expected.to eq(date + ((delta + 1) * day_interval)) }
471
540
  end
472
541
  end
473
542
 
@@ -486,33 +555,33 @@ RSpec.describe Business::Calendar do
486
555
  context "and a period that includes only business days" do
487
556
  let(:date) { date_class.parse("Wednesday 2nd Jan, 2013") }
488
557
 
489
- it { is_expected.to eq(date - delta * day_interval) }
558
+ it { is_expected.to eq(date - (delta * day_interval)) }
490
559
  end
491
560
 
492
561
  context "and a period that includes a weekend" do
493
562
  let(:date) { date_class.parse("Monday 31st Dec, 2012") }
494
563
 
495
- it { is_expected.to eq(date - (delta + 2) * day_interval) }
564
+ it { is_expected.to eq(date - ((delta + 2) * day_interval)) }
496
565
  end
497
566
 
498
567
  context "and a period that includes a working date weekend" do
499
568
  let(:extra_working_dates) { ["Saturday 29th Dec, 2012"] }
500
569
  let(:date) { date_class.parse("Monday 31st Dec, 2012") }
501
570
 
502
- it { is_expected.to eq(date - (delta + 1) * day_interval) }
571
+ it { is_expected.to eq(date - ((delta + 1) * day_interval)) }
503
572
  end
504
573
 
505
574
  context "and a period that includes a holiday day" do
506
575
  let(:date) { date_class.parse("Friday 4th Jan, 2013") }
507
576
 
508
- it { is_expected.to eq(date - (delta + 1) * day_interval) }
577
+ it { is_expected.to eq(date - ((delta + 1) * day_interval)) }
509
578
  end
510
579
  end
511
580
 
512
581
  context "given a non-business day" do
513
582
  let(:date) { date_class.parse("Thursday 3rd Jan, 2013") }
514
583
 
515
- it { is_expected.to eq(date - (delta + 1) * day_interval) }
584
+ it { is_expected.to eq(date - ((delta + 1) * day_interval)) }
516
585
  end
517
586
  end
518
587
 
@@ -6,7 +6,7 @@ working_days:
6
6
  - friday
7
7
 
8
8
  holidays:
9
- - January 1st, 2013
9
+ - 2013-01-01
10
10
  - March 29th, 2013
11
11
  - April 1st, 2013
12
12
  - May 1st, 2013
metadata CHANGED
@@ -1,71 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: business
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.2.1
4
+ version: 3.0.0
5
5
  platform: ruby
6
6
  authors:
7
- - Harry Marr
8
- autorequire:
7
+ - GoCcardless
9
8
  bindir: bin
10
9
  cert_chain: []
11
- date: 2021-03-09 00:00:00.000000000 Z
12
- dependencies:
13
- - !ruby/object:Gem::Dependency
14
- name: gc_ruboconfig
15
- requirement: !ruby/object:Gem::Requirement
16
- requirements:
17
- - - "~>"
18
- - !ruby/object:Gem::Version
19
- version: 2.24.0
20
- type: :development
21
- prerelease: false
22
- version_requirements: !ruby/object:Gem::Requirement
23
- requirements:
24
- - - "~>"
25
- - !ruby/object:Gem::Version
26
- version: 2.24.0
27
- - !ruby/object:Gem::Dependency
28
- name: rspec
29
- requirement: !ruby/object:Gem::Requirement
30
- requirements:
31
- - - "~>"
32
- - !ruby/object:Gem::Version
33
- version: '3.1'
34
- type: :development
35
- prerelease: false
36
- version_requirements: !ruby/object:Gem::Requirement
37
- requirements:
38
- - - "~>"
39
- - !ruby/object:Gem::Version
40
- version: '3.1'
41
- - !ruby/object:Gem::Dependency
42
- name: rspec_junit_formatter
43
- requirement: !ruby/object:Gem::Requirement
44
- requirements:
45
- - - "~>"
46
- - !ruby/object:Gem::Version
47
- version: 0.4.1
48
- type: :development
49
- prerelease: false
50
- version_requirements: !ruby/object:Gem::Requirement
51
- requirements:
52
- - - "~>"
53
- - !ruby/object:Gem::Version
54
- version: 0.4.1
55
- - !ruby/object:Gem::Dependency
56
- name: rubocop
57
- requirement: !ruby/object:Gem::Requirement
58
- requirements:
59
- - - "~>"
60
- - !ruby/object:Gem::Version
61
- version: 1.10.0
62
- type: :development
63
- prerelease: false
64
- version_requirements: !ruby/object:Gem::Requirement
65
- requirements:
66
- - - "~>"
67
- - !ruby/object:Gem::Version
68
- version: 1.10.0
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies: []
69
12
  description: Date calculations based on business calendars
70
13
  email:
71
14
  - developers@gocardless.com
@@ -73,11 +16,13 @@ executables: []
73
16
  extensions: []
74
17
  extra_rdoc_files: []
75
18
  files:
76
- - ".circleci/config.yml"
19
+ - ".github/dependabot.yml"
20
+ - ".github/workflows/tests.yml"
77
21
  - ".gitignore"
78
22
  - ".rubocop.yml"
79
23
  - ".ruby-version"
80
24
  - CHANGELOG.md
25
+ - COMPATIBILITY.md
81
26
  - Gemfile
82
27
  - LICENSE
83
28
  - README.md
@@ -92,8 +37,8 @@ files:
92
37
  homepage: https://github.com/gocardless/business
93
38
  licenses:
94
39
  - MIT
95
- metadata: {}
96
- post_install_message:
40
+ metadata:
41
+ rubygems_mfa_required: 'true'
97
42
  rdoc_options: []
98
43
  require_paths:
99
44
  - lib
@@ -108,12 +53,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
108
53
  - !ruby/object:Gem::Version
109
54
  version: '0'
110
55
  requirements: []
111
- rubygems_version: 3.1.2
112
- signing_key:
56
+ rubygems_version: 4.0.16
113
57
  specification_version: 4
114
58
  summary: Date calculations based on business calendars
115
- test_files:
116
- - spec/business/calendar_spec.rb
117
- - spec/fixtures/calendars/bacs.yml
118
- - spec/fixtures/calendars/ecb.yml
119
- - spec/fixtures/calendars/invalid-keys.yml
59
+ test_files: []
data/.circleci/config.yml DELETED
@@ -1,37 +0,0 @@
1
- version: 2.1
2
-
3
- jobs:
4
- test:
5
- docker:
6
- - image: circleci/ruby:<< parameters.ruby-version >>
7
- parameters:
8
- ruby-version:
9
- type: string
10
- steps:
11
- - checkout
12
- - restore_cache:
13
- keys:
14
- - bundle-v1-<< parameters.ruby-version >>-{{ checksum "business.gemspec" }}
15
- - bundle-v1-<< parameters.ruby-version >>-
16
- - run:
17
- name: Install dependencies
18
- command: bundle install --clean --no-cache --path vendor/bundle --jobs=4 --retry=3
19
- - save_cache:
20
- key: bundle-v1-<< parameters.ruby-version >>-{{ checksum "business.gemspec" }}
21
- paths:
22
- - vendor/bundle
23
- - run:
24
- name: Run tests
25
- command: bundle exec rspec
26
- - run:
27
- name: Run rubocop
28
- command: bundle exec rubocop --parallel --extra-details --display-style-guide
29
-
30
- workflows:
31
- default:
32
- jobs:
33
- - test:
34
- name: Ruby << matrix.ruby-version >>
35
- matrix:
36
- parameters:
37
- ruby-version: ["2.4.9", "2.5.8", "2.6.6", "2.7.1"]