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 +4 -4
- data/.github/dependabot.yml +7 -0
- data/.github/workflows/tests.yml +36 -0
- data/.rubocop.yml +7 -0
- data/.ruby-version +1 -1
- data/CHANGELOG.md +12 -0
- data/COMPATIBILITY.md +21 -0
- data/Gemfile +6 -0
- data/README.md +8 -23
- data/business.gemspec +2 -7
- data/lib/business/calendar.rb +25 -19
- data/lib/business/version.rb +1 -1
- data/spec/business/calendar_spec.rb +85 -16
- data/spec/fixtures/calendars/ecb.yml +1 -1
- metadata +11 -71
- data/.circleci/config.yml +0 -37
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: c7aab6453e38130641098c102e0b05f1d5c40cd957710c80f3b53f4670175ce6
|
|
4
|
+
data.tar.gz: 2ae585a9a460e340db0821c755a787795e7504914af8febb247c451beb8f37f2
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: ffe309242e862845535efb526d3f4ec95852bd685f026c97fdac4bcd4eec88051e47481f5d1354b98a9f34af47dd4858b417fe173b6f9498b52c226a6c491bac
|
|
7
|
+
data.tar.gz: 322e9a49e3fcec9078b6c5e1d78eb6ea0d4abc0bc47f95550cbb6069d72715c00d8c5868279bfdfc5e69776e7e85cea03fc3994441522ff7a84abb82411e867f
|
|
@@ -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
|
-
|
|
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
data/README.md
CHANGED
|
@@ -1,39 +1,18 @@
|
|
|
1
1
|
# Business
|
|
2
2
|
|
|
3
3
|
[](http://badge.fury.io/rb/business)
|
|
4
|
-
[](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", "~>
|
|
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 = ["
|
|
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.
|
|
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
|
data/lib/business/calendar.rb
CHANGED
|
@@ -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
|
-
|
|
46
|
+
calendar_path = calendar_path_for(path, calendar_name)
|
|
47
|
+
next unless calendar_path.exist?
|
|
43
48
|
|
|
44
|
-
break YAML.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 (
|
|
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
|
data/lib/business/version.rb
CHANGED
|
@@ -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
|
-
|
|
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
|
|
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:
|
|
4
|
+
version: 3.0.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
|
-
-
|
|
8
|
-
autorequire:
|
|
7
|
+
- GoCcardless
|
|
9
8
|
bindir: bin
|
|
10
9
|
cert_chain: []
|
|
11
|
-
date:
|
|
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
|
-
- ".
|
|
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
|
-
|
|
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:
|
|
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"]
|