business 2.3.0 → 3.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/tests.yml +36 -0
- data/.rubocop.yml +5 -1
- data/.ruby-version +1 -1
- data/CHANGELOG.md +9 -0
- data/COMPATIBILITY.md +21 -0
- data/Gemfile +6 -0
- data/README.md +19 -24
- data/business.gemspec +2 -7
- data/lib/business/calendar.rb +31 -12
- data/lib/business/version.rb +1 -1
- data/spec/business/calendar_spec.rb +122 -29
- metadata +10 -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: 9f04f06e8df7909e07f17eb5ef5066935eea815e7139260bc1542d785f999d3b
|
|
4
|
+
data.tar.gz: 68df07bcc51b18a1406badfaeeeb78c05412d726a506be589a7bdbd139105877
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 95d2bf588e43841cc35e48e416d610466a18738a777c812d7c7dff1d121d715d270247e5a2895753c3bc1742400f58e924a626dbe152e0d0ed5a9803241070d4
|
|
7
|
+
data.tar.gz: c7e583aba5f2870a3ddb0ec5e91ef6e64e03dfb5cf5de1a6d246bea345ef9cccb3304dc6d21edb3f5508ae62646b1f7dd8330578a1457b96eaf3bd46fa2a6faa
|
|
@@ -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
|
|
@@ -12,5 +16,5 @@ Naming/AccessorMethodName:
|
|
|
12
16
|
Gemspec/RequiredRubyVersion:
|
|
13
17
|
Enabled: false
|
|
14
18
|
|
|
15
|
-
|
|
19
|
+
RSpec/IndexedLet:
|
|
16
20
|
Enabled: false
|
data/.ruby-version
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
4.0.6
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
## Upcoming
|
|
2
2
|
|
|
3
|
+
## 3.1.0 - Sept 28, 2026
|
|
4
|
+
|
|
5
|
+
- Fix `business_days_between` not counting extra working dates in full weeks #34 - thanks @anatoliliotych!
|
|
6
|
+
|
|
7
|
+
# 3.0.0 - Sept 29, 2026
|
|
8
|
+
|
|
9
|
+
- Removed support for Ruby 3.2 and earlier
|
|
10
|
+
- Prevent path traversal when loading calendar files #331
|
|
11
|
+
|
|
3
12
|
## 2.3.0 - Jan 31, 2022
|
|
4
13
|
|
|
5
14
|
- Added permitted classes to YAML's `safe_load` #112 - thanks @attack
|
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
|
|
@@ -67,7 +46,7 @@ calendar = Business::Calendar.new(
|
|
|
67
46
|
|
|
68
47
|
Defining a calendar as a Ruby object may not be convenient, so we provide a way of defining these calendars as YAML. Below we will walk through the necessary [steps](#example-calendar) to build your first calendar. All keys are optional and will default to the following:
|
|
69
48
|
|
|
70
|
-
Note: Elements of `holidays` and `extra_working_dates` may be either strings that `Date.parse()` [can understand](https://ruby-doc.org/stdlib-2.7.1/libdoc/date/rdoc/Date.html#method-c-parse), or `YYYY-MM-DD`
|
|
49
|
+
Note: Elements of `holidays` and `extra_working_dates` may be either strings that `Date.parse()` [can understand](https://ruby-doc.org/stdlib-2.7.1/libdoc/date/rdoc/Date.html#method-c-parse), or `YYYY-MM-DD` [which is considered as a Date by Ruby YAML itself](https://github.com/ruby/psych/blob/6ec6e475e8afcf7868b0407fc08014aed886ecf1/lib/psych/scalar_scanner.rb#L60).
|
|
71
50
|
|
|
72
51
|
#### YAML file Structure
|
|
73
52
|
|
|
@@ -160,6 +139,16 @@ calendar.subtract_business_days(date, 4).strftime("%A, %d %B %Y")
|
|
|
160
139
|
# => "Friday, 06 June 2014"
|
|
161
140
|
```
|
|
162
141
|
|
|
142
|
+
For getting the next or previous business day, instead of passing `1` to `add_business_days` or `subtract_business_days`, `next_business_day` and `previous_business_day` could be used respectively:
|
|
143
|
+
|
|
144
|
+
```ruby
|
|
145
|
+
date = Date.parse("Friday, 13 June 2014")
|
|
146
|
+
calendar.next_business_day(date).strftime("%A, %d %B %Y")
|
|
147
|
+
# => "Monday, 16 June 2014"
|
|
148
|
+
calendar.previous_business_day(date).strftime("%A, %d %B %Y")
|
|
149
|
+
# => "Thursday, 12 June 2014"
|
|
150
|
+
```
|
|
151
|
+
|
|
163
152
|
The `roll_forward` and `roll_backward` methods snap a date to a nearby business day. If provided with a business day, they will return that date. Otherwise, they will advance (forward for `roll_forward` and backward for `roll_backward`) until a business day is found.
|
|
164
153
|
|
|
165
154
|
```ruby
|
|
@@ -189,6 +178,12 @@ Secondly, business_time supports calculations on times as well as dates. For our
|
|
|
189
178
|
|
|
190
179
|
<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
180
|
|
|
181
|
+
# Compatibility
|
|
182
|
+
|
|
183
|
+
## v2.0.0 Breaking Changes
|
|
184
|
+
|
|
185
|
+
- See [COMPATIBILITY.md](COMPATIBILITY.md#v200-breaking-changes)
|
|
186
|
+
|
|
192
187
|
## License & Contributing
|
|
193
188
|
- business is available as open source under the terms of the [MIT License](LICENSE).
|
|
194
189
|
- 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.5.1"
|
|
26
|
-
spec.add_development_dependency "rubocop", "~> 1.25.0"
|
|
21
|
+
spec.metadata["rubygems_mfa_required"] = "true"
|
|
27
22
|
end
|
data/lib/business/calendar.rb
CHANGED
|
@@ -8,6 +8,11 @@ module Business
|
|
|
8
8
|
class Calendar
|
|
9
9
|
VALID_KEYS = %w[holidays working_days extra_working_dates].freeze
|
|
10
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
|
+
|
|
11
16
|
class << self
|
|
12
17
|
attr_accessor :load_paths
|
|
13
18
|
end
|
|
@@ -38,7 +43,7 @@ module Business
|
|
|
38
43
|
if path.is_a?(Hash)
|
|
39
44
|
break path[calendar_name] if path[calendar_name]
|
|
40
45
|
else
|
|
41
|
-
calendar_path =
|
|
46
|
+
calendar_path = calendar_path_for(path, calendar_name)
|
|
42
47
|
next unless calendar_path.exist?
|
|
43
48
|
|
|
44
49
|
break YAML.safe_load(calendar_path.read, permitted_classes: [Date])
|
|
@@ -46,6 +51,17 @@ module Business
|
|
|
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
|
|
@@ -184,7 +194,16 @@ module Business
|
|
|
184
194
|
in_range && on_biz_day
|
|
185
195
|
end
|
|
186
196
|
|
|
187
|
-
|
|
197
|
+
num_biz_days += extra_working_dates.count do |extra_day|
|
|
198
|
+
in_range = full_weeks_range.cover?(extra_day)
|
|
199
|
+
# Only pick an extra working date if it's on a non-working day (e.g., a
|
|
200
|
+
# weekend) and not a holiday, as the setters bypass that validation
|
|
201
|
+
on_non_working_day =
|
|
202
|
+
!working_days.include?(extra_day.strftime("%a").downcase)
|
|
203
|
+
in_range && on_non_working_day && !holiday?(extra_day)
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
remaining_range = ((date2 - remaining_days)...date2)
|
|
188
207
|
# Loop through each day in remaining_range and count if a business day
|
|
189
208
|
num_biz_days + remaining_range.count { |a| business_day?(a) }
|
|
190
209
|
end
|
|
@@ -203,7 +222,7 @@ module Business
|
|
|
203
222
|
extra_working_dates_names = @extra_working_dates.map do |d|
|
|
204
223
|
d.strftime("%a").downcase
|
|
205
224
|
end
|
|
206
|
-
return if (
|
|
225
|
+
return if !extra_working_dates_names.intersect?(@working_days)
|
|
207
226
|
|
|
208
227
|
raise ArgumentError, "Extra working dates cannot be on working days"
|
|
209
228
|
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 }
|
|
@@ -57,6 +58,71 @@ RSpec.describe Business::Calendar do
|
|
|
57
58
|
specify { expect { load_calendar }.to raise_error(/No such calendar/) }
|
|
58
59
|
end
|
|
59
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
|
+
|
|
60
126
|
context "when given a calendar that has invalid keys" do
|
|
61
127
|
let(:calendar) { "invalid-keys" }
|
|
62
128
|
|
|
@@ -340,7 +406,7 @@ RSpec.describe Business::Calendar do
|
|
|
340
406
|
context "followed by another non-business day" do
|
|
341
407
|
let(:date) { date_class.parse("Saturday 5th Jan, 2013") }
|
|
342
408
|
|
|
343
|
-
it { is_expected.to eq(date + 2 * day_interval) }
|
|
409
|
+
it { is_expected.to eq(date + (2 * day_interval)) }
|
|
344
410
|
end
|
|
345
411
|
end
|
|
346
412
|
end
|
|
@@ -368,7 +434,7 @@ RSpec.describe Business::Calendar do
|
|
|
368
434
|
context "preceeded by another non-business day" do
|
|
369
435
|
let(:date) { date_class.parse("Sunday 6th Jan, 2013") }
|
|
370
436
|
|
|
371
|
-
it { is_expected.to eq(date - 2 * day_interval) }
|
|
437
|
+
it { is_expected.to eq(date - (2 * day_interval)) }
|
|
372
438
|
end
|
|
373
439
|
end
|
|
374
440
|
end
|
|
@@ -396,7 +462,7 @@ RSpec.describe Business::Calendar do
|
|
|
396
462
|
context "followed by another non-business day" do
|
|
397
463
|
let(:date) { date_class.parse("Saturday 5th Jan, 2013") }
|
|
398
464
|
|
|
399
|
-
it { is_expected.to eq(date + 2 * day_interval) }
|
|
465
|
+
it { is_expected.to eq(date + (2 * day_interval)) }
|
|
400
466
|
end
|
|
401
467
|
end
|
|
402
468
|
end
|
|
@@ -424,7 +490,7 @@ RSpec.describe Business::Calendar do
|
|
|
424
490
|
context "preceeded by another non-business day" do
|
|
425
491
|
let(:date) { date_class.parse("Sunday 6th Jan, 2013") }
|
|
426
492
|
|
|
427
|
-
it { is_expected.to eq(date - 2 * day_interval) }
|
|
493
|
+
it { is_expected.to eq(date - (2 * day_interval)) }
|
|
428
494
|
end
|
|
429
495
|
end
|
|
430
496
|
end
|
|
@@ -444,33 +510,33 @@ RSpec.describe Business::Calendar do
|
|
|
444
510
|
context "and a period that includes only business days" do
|
|
445
511
|
let(:date) { date_class.parse("Wednesday 2nd Jan, 2013") }
|
|
446
512
|
|
|
447
|
-
it { is_expected.to eq(date + delta * day_interval) }
|
|
513
|
+
it { is_expected.to eq(date + (delta * day_interval)) }
|
|
448
514
|
end
|
|
449
515
|
|
|
450
516
|
context "and a period that includes a weekend" do
|
|
451
517
|
let(:date) { date_class.parse("Friday 4th Jan, 2013") }
|
|
452
518
|
|
|
453
|
-
it { is_expected.to eq(date + (delta + 2) * day_interval) }
|
|
519
|
+
it { is_expected.to eq(date + ((delta + 2) * day_interval)) }
|
|
454
520
|
end
|
|
455
521
|
|
|
456
522
|
context "and a period that includes a working date weekend" do
|
|
457
523
|
let(:extra_working_dates) { ["Sunday 6th Jan, 2013"] }
|
|
458
524
|
let(:date) { date_class.parse("Friday 4th Jan, 2013") }
|
|
459
525
|
|
|
460
|
-
it { is_expected.to eq(date + (delta + 1) * day_interval) }
|
|
526
|
+
it { is_expected.to eq(date + ((delta + 1) * day_interval)) }
|
|
461
527
|
end
|
|
462
528
|
|
|
463
529
|
context "and a period that includes a holiday day" do
|
|
464
530
|
let(:date) { date_class.parse("Monday 31st Dec, 2012") }
|
|
465
531
|
|
|
466
|
-
it { is_expected.to eq(date + (delta + 1) * day_interval) }
|
|
532
|
+
it { is_expected.to eq(date + ((delta + 1) * day_interval)) }
|
|
467
533
|
end
|
|
468
534
|
end
|
|
469
535
|
|
|
470
536
|
context "given a non-business day" do
|
|
471
537
|
let(:date) { date_class.parse("Tuesday 1st Jan, 2013") }
|
|
472
538
|
|
|
473
|
-
it { is_expected.to eq(date + (delta + 1) * day_interval) }
|
|
539
|
+
it { is_expected.to eq(date + ((delta + 1) * day_interval)) }
|
|
474
540
|
end
|
|
475
541
|
end
|
|
476
542
|
|
|
@@ -489,33 +555,33 @@ RSpec.describe Business::Calendar do
|
|
|
489
555
|
context "and a period that includes only business days" do
|
|
490
556
|
let(:date) { date_class.parse("Wednesday 2nd Jan, 2013") }
|
|
491
557
|
|
|
492
|
-
it { is_expected.to eq(date - delta * day_interval) }
|
|
558
|
+
it { is_expected.to eq(date - (delta * day_interval)) }
|
|
493
559
|
end
|
|
494
560
|
|
|
495
561
|
context "and a period that includes a weekend" do
|
|
496
562
|
let(:date) { date_class.parse("Monday 31st Dec, 2012") }
|
|
497
563
|
|
|
498
|
-
it { is_expected.to eq(date - (delta + 2) * day_interval) }
|
|
564
|
+
it { is_expected.to eq(date - ((delta + 2) * day_interval)) }
|
|
499
565
|
end
|
|
500
566
|
|
|
501
567
|
context "and a period that includes a working date weekend" do
|
|
502
568
|
let(:extra_working_dates) { ["Saturday 29th Dec, 2012"] }
|
|
503
569
|
let(:date) { date_class.parse("Monday 31st Dec, 2012") }
|
|
504
570
|
|
|
505
|
-
it { is_expected.to eq(date - (delta + 1) * day_interval) }
|
|
571
|
+
it { is_expected.to eq(date - ((delta + 1) * day_interval)) }
|
|
506
572
|
end
|
|
507
573
|
|
|
508
574
|
context "and a period that includes a holiday day" do
|
|
509
575
|
let(:date) { date_class.parse("Friday 4th Jan, 2013") }
|
|
510
576
|
|
|
511
|
-
it { is_expected.to eq(date - (delta + 1) * day_interval) }
|
|
577
|
+
it { is_expected.to eq(date - ((delta + 1) * day_interval)) }
|
|
512
578
|
end
|
|
513
579
|
end
|
|
514
580
|
|
|
515
581
|
context "given a non-business day" do
|
|
516
582
|
let(:date) { date_class.parse("Thursday 3rd Jan, 2013") }
|
|
517
583
|
|
|
518
|
-
it { is_expected.to eq(date - (delta + 1) * day_interval) }
|
|
584
|
+
it { is_expected.to eq(date - ((delta + 1) * day_interval)) }
|
|
519
585
|
end
|
|
520
586
|
end
|
|
521
587
|
|
|
@@ -628,7 +694,7 @@ RSpec.describe Business::Calendar do
|
|
|
628
694
|
let(:date_1) { "Wed 28/5/2014" }
|
|
629
695
|
let(:date_2) { "Thu 12/6/2014" }
|
|
630
696
|
|
|
631
|
-
it { is_expected.to eq(
|
|
697
|
+
it { is_expected.to eq(12) }
|
|
632
698
|
end
|
|
633
699
|
end
|
|
634
700
|
|
|
@@ -651,7 +717,7 @@ RSpec.describe Business::Calendar do
|
|
|
651
717
|
let(:date_1) { "Wed 25/6/2014" }
|
|
652
718
|
let(:date_2) { "Tue 8/7/2014" }
|
|
653
719
|
|
|
654
|
-
it { is_expected.to eq(
|
|
720
|
+
it { is_expected.to eq(9) }
|
|
655
721
|
end
|
|
656
722
|
end
|
|
657
723
|
end
|
|
@@ -683,7 +749,7 @@ RSpec.describe Business::Calendar do
|
|
|
683
749
|
let(:date_1) { "Sat 31/5/2014" }
|
|
684
750
|
let(:date_2) { "Fri 13/6/2014" }
|
|
685
751
|
|
|
686
|
-
it { is_expected.to eq(
|
|
752
|
+
it { is_expected.to eq(9) }
|
|
687
753
|
end
|
|
688
754
|
end
|
|
689
755
|
|
|
@@ -698,7 +764,7 @@ RSpec.describe Business::Calendar do
|
|
|
698
764
|
let(:date_1) { "Sat 31/5/2014" }
|
|
699
765
|
let(:date_2) { "Sun 8/6/2014" }
|
|
700
766
|
|
|
701
|
-
it { is_expected.to
|
|
767
|
+
it { is_expected.to eq(6) }
|
|
702
768
|
end
|
|
703
769
|
|
|
704
770
|
context "including business, weekend days, and holidays" do
|
|
@@ -709,9 +775,9 @@ RSpec.describe Business::Calendar do
|
|
|
709
775
|
|
|
710
776
|
context "including business, weekend, holiday days & working date" do
|
|
711
777
|
let(:date_1) { "Sat 31/5/2014" }
|
|
712
|
-
let(:date_2) { "Sun
|
|
778
|
+
let(:date_2) { "Sun 15/6/2014" }
|
|
713
779
|
|
|
714
|
-
it { is_expected.to be(
|
|
780
|
+
it { is_expected.to be(10) }
|
|
715
781
|
end
|
|
716
782
|
end
|
|
717
783
|
|
|
@@ -726,7 +792,7 @@ RSpec.describe Business::Calendar do
|
|
|
726
792
|
let(:date_1) { "Sat 31/5/2014" }
|
|
727
793
|
let(:date_2) { "Thu 12/6/2014" }
|
|
728
794
|
|
|
729
|
-
it { is_expected.to eq(
|
|
795
|
+
it { is_expected.to eq(9) }
|
|
730
796
|
end
|
|
731
797
|
end
|
|
732
798
|
|
|
@@ -746,9 +812,9 @@ RSpec.describe Business::Calendar do
|
|
|
746
812
|
end
|
|
747
813
|
|
|
748
814
|
context "including business, weekend days, holidays & working date" do
|
|
749
|
-
let(:date_2) { "
|
|
815
|
+
let(:date_2) { "Fri 13/6/2014" }
|
|
750
816
|
|
|
751
|
-
it { is_expected.to eq(
|
|
817
|
+
it { is_expected.to eq(9) }
|
|
752
818
|
end
|
|
753
819
|
end
|
|
754
820
|
end
|
|
@@ -807,10 +873,10 @@ RSpec.describe Business::Calendar do
|
|
|
807
873
|
end
|
|
808
874
|
|
|
809
875
|
context "including business/weekend days, holidays & working date" do
|
|
810
|
-
let(:date_1) { "27/5/2014" }
|
|
876
|
+
let(:date_1) { "Tue 27/5/2014" }
|
|
811
877
|
let(:date_2) { "Thu 12/6/2014" }
|
|
812
878
|
|
|
813
|
-
it { is_expected.to eq(
|
|
879
|
+
it { is_expected.to eq(12) }
|
|
814
880
|
end
|
|
815
881
|
end
|
|
816
882
|
|
|
@@ -850,13 +916,13 @@ RSpec.describe Business::Calendar do
|
|
|
850
916
|
context "including working date, working & weekend days" do
|
|
851
917
|
let(:date_2) { "Tue 10/6/2014" }
|
|
852
918
|
|
|
853
|
-
it { is_expected.to eq(
|
|
919
|
+
it { is_expected.to eq(7) }
|
|
854
920
|
end
|
|
855
921
|
|
|
856
922
|
context "including working date, working & weekend days & holiday" do
|
|
857
|
-
let(:date_2) { "
|
|
923
|
+
let(:date_2) { "Fri 13/6/2014" }
|
|
858
924
|
|
|
859
|
-
it { is_expected.to eq(
|
|
925
|
+
it { is_expected.to eq(9) }
|
|
860
926
|
end
|
|
861
927
|
end
|
|
862
928
|
|
|
@@ -913,7 +979,7 @@ RSpec.describe Business::Calendar do
|
|
|
913
979
|
let(:date_1) { "Sat 28/6/2014" }
|
|
914
980
|
let(:date_2) { "Sat 5/7/2014" }
|
|
915
981
|
|
|
916
|
-
it { is_expected.to eq(
|
|
982
|
+
it { is_expected.to eq(5) }
|
|
917
983
|
end
|
|
918
984
|
end
|
|
919
985
|
end
|
|
@@ -933,6 +999,33 @@ RSpec.describe Business::Calendar do
|
|
|
933
999
|
it { is_expected.to eq(4) }
|
|
934
1000
|
end
|
|
935
1001
|
end
|
|
1002
|
+
|
|
1003
|
+
context "if a calendar has an extra working date" do
|
|
1004
|
+
context "for a range less than a week long" do
|
|
1005
|
+
let(:date_1) { "Fri 30/5/2014" }
|
|
1006
|
+
let(:date_2) { "Tue 3/6/2014" }
|
|
1007
|
+
|
|
1008
|
+
it { is_expected.to eq(3) }
|
|
1009
|
+
end
|
|
1010
|
+
|
|
1011
|
+
context "for a range more than a week long" do
|
|
1012
|
+
let(:date_1) { "Mon 26/5/2014" }
|
|
1013
|
+
let(:date_2) { "Mon 9/6/2014" }
|
|
1014
|
+
|
|
1015
|
+
it { is_expected.to eq(10) }
|
|
1016
|
+
end
|
|
1017
|
+
|
|
1018
|
+
context "set after initialisation on a working day and a holiday" do
|
|
1019
|
+
before do
|
|
1020
|
+
calendar.set_extra_working_dates(["Wed 4/6/2014", "Sun 22/6/2014"])
|
|
1021
|
+
end
|
|
1022
|
+
|
|
1023
|
+
let(:date_1) { "Mon 2/6/2014" }
|
|
1024
|
+
let(:date_2) { "Mon 30/6/2014" }
|
|
1025
|
+
|
|
1026
|
+
it { is_expected.to eq(16) }
|
|
1027
|
+
end
|
|
1028
|
+
end
|
|
936
1029
|
end
|
|
937
1030
|
end
|
|
938
1031
|
|
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.1.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.31.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.31.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.5.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.5.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.25.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.25.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,12 +16,13 @@ executables: []
|
|
|
73
16
|
extensions: []
|
|
74
17
|
extra_rdoc_files: []
|
|
75
18
|
files:
|
|
76
|
-
- ".circleci/config.yml"
|
|
77
19
|
- ".github/dependabot.yml"
|
|
20
|
+
- ".github/workflows/tests.yml"
|
|
78
21
|
- ".gitignore"
|
|
79
22
|
- ".rubocop.yml"
|
|
80
23
|
- ".ruby-version"
|
|
81
24
|
- CHANGELOG.md
|
|
25
|
+
- COMPATIBILITY.md
|
|
82
26
|
- Gemfile
|
|
83
27
|
- LICENSE
|
|
84
28
|
- README.md
|
|
@@ -93,8 +37,8 @@ files:
|
|
|
93
37
|
homepage: https://github.com/gocardless/business
|
|
94
38
|
licenses:
|
|
95
39
|
- MIT
|
|
96
|
-
metadata:
|
|
97
|
-
|
|
40
|
+
metadata:
|
|
41
|
+
rubygems_mfa_required: 'true'
|
|
98
42
|
rdoc_options: []
|
|
99
43
|
require_paths:
|
|
100
44
|
- lib
|
|
@@ -109,12 +53,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
109
53
|
- !ruby/object:Gem::Version
|
|
110
54
|
version: '0'
|
|
111
55
|
requirements: []
|
|
112
|
-
rubygems_version:
|
|
113
|
-
signing_key:
|
|
56
|
+
rubygems_version: 4.0.16
|
|
114
57
|
specification_version: 4
|
|
115
58
|
summary: Date calculations based on business calendars
|
|
116
|
-
test_files:
|
|
117
|
-
- spec/business/calendar_spec.rb
|
|
118
|
-
- spec/fixtures/calendars/bacs.yml
|
|
119
|
-
- spec/fixtures/calendars/ecb.yml
|
|
120
|
-
- 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: cimg/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.6.7", "2.7.3", "3.0.1", "3.1.0"]
|