business 0.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 +7 -0
- data/.gitignore +17 -0
- data/Gemfile +4 -0
- data/README.md +13 -0
- data/business.gemspec +22 -0
- data/lib/business.rb +1 -0
- data/lib/business/calendar.rb +156 -0
- data/lib/business/data/bacs.yml +25 -0
- data/lib/business/data/sepa.yml +35 -0
- data/lib/business/data/weekdays.yml +6 -0
- data/lib/business/version.rb +3 -0
- data/spec/calendar_spec.rb +408 -0
- metadata +83 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 5c69d99c21ef0905de463bc82315c5f3829f6c23
|
4
|
+
data.tar.gz: 2d333b66d2d26a1cd9664b7415033012ab898339
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: a020144ad1d875fe40df7dfd91d2cec5fe5623134921a40cc45669202bc0c76a43f98cde0f9cf1da3f8334eaabbb245823f8f542ac3a6985b3055b869e8c663e
|
7
|
+
data.tar.gz: c0ce8a9993aad1a8be8378ae9bdeb77368979be818ca1cca84ccf9ed2eddd0ba2811c81d03dbdfb072da3134e63c9abe75ce9a7ff6e96138105f606439725db7
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/README.md
ADDED
data/business.gemspec
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'business/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "business"
|
8
|
+
spec.version = Business::VERSION
|
9
|
+
spec.authors = ["Harry Marr"]
|
10
|
+
spec.email = ["engineering@gocardless.com"]
|
11
|
+
spec.description = %q{Date calculations based on business calendars}
|
12
|
+
spec.summary = %q{Date calculations based on business calendars}
|
13
|
+
spec.homepage = "https://github.com/gocardless/business"
|
14
|
+
|
15
|
+
spec.files = `git ls-files`.split($/)
|
16
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
17
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
18
|
+
spec.require_paths = ["lib"]
|
19
|
+
|
20
|
+
spec.add_development_dependency "bundler", "~> 1.3"
|
21
|
+
spec.add_development_dependency "rspec", "~> 2.14.1"
|
22
|
+
end
|
data/lib/business.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'business/calendar'
|
@@ -0,0 +1,156 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
|
3
|
+
module Business
|
4
|
+
class Calendar
|
5
|
+
def self.load(calendar)
|
6
|
+
path = File.join(File.dirname(__FILE__), 'data', "#{calendar}.yml")
|
7
|
+
raise "No such calendar '#{calendar}'" unless File.exists?(path)
|
8
|
+
yaml = YAML.load_file(path)
|
9
|
+
self.new(holidays: yaml['holidays'], business_days: yaml['business_days'])
|
10
|
+
end
|
11
|
+
|
12
|
+
@lock = Mutex.new
|
13
|
+
def self.load_cached(calendar)
|
14
|
+
@lock.synchronize do
|
15
|
+
@cache ||= { }
|
16
|
+
unless @cache.include?(calendar)
|
17
|
+
@cache[calendar] = self.load(calendar)
|
18
|
+
end
|
19
|
+
@cache[calendar]
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
DAY_NAMES = %( mon tue wed thu fri sat sun )
|
24
|
+
|
25
|
+
attr_reader :business_days, :holidays
|
26
|
+
|
27
|
+
def initialize(config)
|
28
|
+
set_business_days(config[:business_days])
|
29
|
+
set_holidays(config[:holidays])
|
30
|
+
end
|
31
|
+
|
32
|
+
# Return true if the date given is a business day (typically that means a
|
33
|
+
# non-weekend day) and not a holiday.
|
34
|
+
def business_day?(date)
|
35
|
+
date = date.to_date
|
36
|
+
return false unless business_days.include?(date.strftime('%a').downcase)
|
37
|
+
return false if holidays.include?(date)
|
38
|
+
true
|
39
|
+
end
|
40
|
+
|
41
|
+
# Roll forward to the next business day. If the date given is a business
|
42
|
+
# day, that day will be returned. If the day given is a holiday or
|
43
|
+
# non-working day, the next non-holiday working day will be returned.
|
44
|
+
def roll_forward(date)
|
45
|
+
interval = date.is_a?(Date) ? 1 : 3600 * 24
|
46
|
+
date += interval until business_day?(date)
|
47
|
+
date
|
48
|
+
end
|
49
|
+
|
50
|
+
# Roll backward to the previous business day. If the date given is a
|
51
|
+
# business day, that day will be returned. If the day given is a holiday or
|
52
|
+
# non-working day, the previous non-holiday working day will be returned.
|
53
|
+
def roll_backward(date)
|
54
|
+
date -= day_interval_for(date) until business_day?(date)
|
55
|
+
date
|
56
|
+
end
|
57
|
+
|
58
|
+
# Roll forward to the next business day regardless of whether the given
|
59
|
+
# date is a business day or not.
|
60
|
+
def next_business_day(date)
|
61
|
+
begin
|
62
|
+
date += day_interval_for(date)
|
63
|
+
end until business_day?(date)
|
64
|
+
date
|
65
|
+
end
|
66
|
+
|
67
|
+
# Add a number of business days to a date. If a non-business day is given,
|
68
|
+
# counting will start from the next business day. So,
|
69
|
+
# monday + 1 = tuesday
|
70
|
+
# friday + 1 = monday
|
71
|
+
# sunday + 1 = tuesday
|
72
|
+
def add_business_days(date, delta)
|
73
|
+
date = roll_forward(date)
|
74
|
+
delta.times do
|
75
|
+
begin
|
76
|
+
date += day_interval_for(date)
|
77
|
+
end until business_day?(date)
|
78
|
+
end
|
79
|
+
date
|
80
|
+
end
|
81
|
+
|
82
|
+
# Subtract a number of business days to a date. If a non-business day is
|
83
|
+
# given, counting will start from the previous business day. So,
|
84
|
+
# friday - 1 = thursday
|
85
|
+
# monday - 1 = friday
|
86
|
+
# sunday - 1 = thursday
|
87
|
+
def subtract_business_days(date, delta)
|
88
|
+
date = roll_backward(date)
|
89
|
+
delta.times do
|
90
|
+
begin
|
91
|
+
date -= day_interval_for(date)
|
92
|
+
end until business_day?(date)
|
93
|
+
end
|
94
|
+
date
|
95
|
+
end
|
96
|
+
|
97
|
+
# Count the number of business days between two dates.
|
98
|
+
# This method counts from start of date1 to start of date2. So,
|
99
|
+
# business_days_between(mon, weds) = 2 (assuming no holidays)
|
100
|
+
def business_days_between(date1, date2)
|
101
|
+
date1, date2 = date1.to_date, date2.to_date
|
102
|
+
|
103
|
+
# To optimise this method we split the range into full weeks and a
|
104
|
+
# remaining period.
|
105
|
+
#
|
106
|
+
# We then calculate business days in the full weeks period by
|
107
|
+
# multiplying number of weeks by number of working days in a week and
|
108
|
+
# removing holidays one by one.
|
109
|
+
|
110
|
+
# For the remaining period, we just loop through each day and check
|
111
|
+
# whether it is a business day.
|
112
|
+
|
113
|
+
# Calculate number of full weeks and remaining days
|
114
|
+
num_full_weeks, remaining_days = (date2 - date1).to_i.divmod(7)
|
115
|
+
|
116
|
+
# First estimate for full week range based on # biz days in a week
|
117
|
+
num_biz_days = num_full_weeks * business_days.length
|
118
|
+
|
119
|
+
full_weeks_range = (date1...(date2 - remaining_days))
|
120
|
+
num_biz_days -= holidays.count do |holiday|
|
121
|
+
in_range = full_weeks_range.cover?(holiday)
|
122
|
+
# Only pick a holiday if its on a working day (e.g., not a weekend)
|
123
|
+
on_biz_day = business_days.include?(holiday.strftime('%a').downcase)
|
124
|
+
in_range && on_biz_day
|
125
|
+
end
|
126
|
+
|
127
|
+
remaining_range = (date2-remaining_days...date2)
|
128
|
+
# Loop through each day in remaining_range and count if a business day
|
129
|
+
num_biz_days + remaining_range.count { |a| business_day?(a) }
|
130
|
+
end
|
131
|
+
|
132
|
+
def day_interval_for(date)
|
133
|
+
date.is_a?(Date) ? 1 : 3600 * 24
|
134
|
+
end
|
135
|
+
|
136
|
+
# Internal method for assigning business days from a calendar config.
|
137
|
+
def set_business_days(business_days)
|
138
|
+
@business_days = (business_days || default_business_days).map do |day|
|
139
|
+
day.downcase.strip[0..2].tap do |normalised_day|
|
140
|
+
raise "Invalid day #{day}" unless DAY_NAMES.include?(normalised_day)
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
# Internal method for assigning holidays from a calendar config.
|
146
|
+
def set_holidays(holidays)
|
147
|
+
@holidays = (holidays || []).map { |holiday| Date.parse(holiday) }
|
148
|
+
end
|
149
|
+
|
150
|
+
# If no business days are provided in the calendar config, these are used.
|
151
|
+
def default_business_days
|
152
|
+
%w( mon tue wed thu fri )
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
@@ -0,0 +1,25 @@
|
|
1
|
+
business_days:
|
2
|
+
- monday
|
3
|
+
- tuesday
|
4
|
+
- wednesday
|
5
|
+
- thursday
|
6
|
+
- friday
|
7
|
+
|
8
|
+
holidays:
|
9
|
+
- January 1st, 2013
|
10
|
+
- March 29th, 2013
|
11
|
+
- April 1st, 2013
|
12
|
+
- May 6th, 2013
|
13
|
+
- May 27th, 2013
|
14
|
+
- August 26th, 2013
|
15
|
+
- December 25th, 2013
|
16
|
+
- December 26th, 2013
|
17
|
+
- January 1st, 2014
|
18
|
+
- April 18th, 2014
|
19
|
+
- April 21st, 2014
|
20
|
+
- May 5th, 2014
|
21
|
+
- May 26th, 2014
|
22
|
+
- August 25th, 2014
|
23
|
+
- December 25th, 2014
|
24
|
+
- December 26th, 2014
|
25
|
+
|
@@ -0,0 +1,35 @@
|
|
1
|
+
business_days:
|
2
|
+
- monday
|
3
|
+
- tuesday
|
4
|
+
- wednesday
|
5
|
+
- thursday
|
6
|
+
- friday
|
7
|
+
|
8
|
+
holidays:
|
9
|
+
- January 1st, 2013
|
10
|
+
- March 29th, 2013
|
11
|
+
- April 1st, 2013
|
12
|
+
- May 1st, 2013
|
13
|
+
- May 9th, 2013
|
14
|
+
- May 20th, 2013
|
15
|
+
- May 30th, 2013
|
16
|
+
- October 3rd, 2013
|
17
|
+
- November 1st, 2013
|
18
|
+
- December 24th, 2013
|
19
|
+
- December 25th, 2013
|
20
|
+
- December 26th, 2013
|
21
|
+
- December 31st, 2013
|
22
|
+
- January 1st, 2014
|
23
|
+
- April 18th, 2014
|
24
|
+
- April 21st, 2014
|
25
|
+
- May 1st, 2014
|
26
|
+
- May 9th, 2014
|
27
|
+
- May 29th, 2014
|
28
|
+
- June 9th, 2014
|
29
|
+
- June 19th, 2014
|
30
|
+
- October 3rd, 2014
|
31
|
+
- November 1st, 2014
|
32
|
+
- December 24th, 2014
|
33
|
+
- December 25th, 2014
|
34
|
+
- December 26th, 2014
|
35
|
+
- December 31st, 2014
|
@@ -0,0 +1,408 @@
|
|
1
|
+
require "business/calendar"
|
2
|
+
require "time"
|
3
|
+
|
4
|
+
describe Business::Calendar do
|
5
|
+
describe ".load" do
|
6
|
+
context "when given a valid calendar" do
|
7
|
+
subject { Business::Calendar.load("weekdays") }
|
8
|
+
|
9
|
+
it "loads the yaml file" do
|
10
|
+
YAML.should_receive(:load_file) do |path|
|
11
|
+
path.should match(/weekdays\.yml$/)
|
12
|
+
end.and_return({})
|
13
|
+
subject
|
14
|
+
end
|
15
|
+
|
16
|
+
it { should be_a Business::Calendar }
|
17
|
+
end
|
18
|
+
|
19
|
+
context "when given an invalid calendar" do
|
20
|
+
subject { Business::Calendar.load("invalid-calendar") }
|
21
|
+
specify { ->{ subject }.should raise_error }
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
describe "#set_business_days" do
|
26
|
+
let(:calendar) { Business::Calendar.new({}) }
|
27
|
+
let(:business_days) { [] }
|
28
|
+
subject { calendar.set_business_days(business_days) }
|
29
|
+
|
30
|
+
context "when given valid business days" do
|
31
|
+
let(:business_days) { %w( mon fri ) }
|
32
|
+
before { subject }
|
33
|
+
|
34
|
+
it "assigns them" do
|
35
|
+
calendar.business_days.should == business_days
|
36
|
+
end
|
37
|
+
|
38
|
+
context "that are unnormalised" do
|
39
|
+
let(:business_days) { %w( Monday Friday ) }
|
40
|
+
it "normalises them" do
|
41
|
+
calendar.business_days.should == %w( mon fri )
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
context "when given an invalid business day" do
|
47
|
+
let(:business_days) { %w( Notaday ) }
|
48
|
+
specify { ->{ subject }.should raise_exception }
|
49
|
+
end
|
50
|
+
|
51
|
+
context "when given nil" do
|
52
|
+
let(:business_days) { nil }
|
53
|
+
it "uses the default business days" do
|
54
|
+
calendar.business_days.should == calendar.default_business_days
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
describe "#set_holidays" do
|
60
|
+
let(:calendar) { Business::Calendar.new({}) }
|
61
|
+
let(:holidays) { [] }
|
62
|
+
before { calendar.set_holidays(holidays) }
|
63
|
+
subject { calendar.holidays }
|
64
|
+
|
65
|
+
context "when given valid business days" do
|
66
|
+
let(:holidays) { ["1st Jan, 2013"] }
|
67
|
+
|
68
|
+
it { should_not be_empty }
|
69
|
+
|
70
|
+
it "converts them to Date objects" do
|
71
|
+
subject.each { |h| h.should be_a Date }
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
context "when given nil" do
|
76
|
+
let(:holidays) { nil }
|
77
|
+
it { should be_empty }
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
# A set of examples that are supposed to work when given Date and Time
|
82
|
+
# objects. The implementation slightly differs, so i's worth running the
|
83
|
+
# tests for both Date *and* Time.
|
84
|
+
shared_examples "common" do
|
85
|
+
describe "#business_day?" do
|
86
|
+
let(:calendar) do
|
87
|
+
Business::Calendar.new(holidays: ["9am, Tuesday 1st Jan, 2013"])
|
88
|
+
end
|
89
|
+
subject { calendar.business_day?(day) }
|
90
|
+
|
91
|
+
context "when given a business day" do
|
92
|
+
let(:day) { date_class.parse("9am, Wednesday 2nd Jan, 2013") }
|
93
|
+
it { should be_true }
|
94
|
+
end
|
95
|
+
|
96
|
+
context "when given a non-business day" do
|
97
|
+
let(:day) { date_class.parse("9am, Saturday 5th Jan, 2013") }
|
98
|
+
it { should be_false }
|
99
|
+
end
|
100
|
+
|
101
|
+
context "when given a business day that is a holiday" do
|
102
|
+
let(:day) { date_class.parse("9am, Tuesday 1st Jan, 2013") }
|
103
|
+
it { should be_false }
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
describe "#roll_forward" do
|
108
|
+
let(:calendar) do
|
109
|
+
Business::Calendar.new(holidays: ["Tuesday 1st Jan, 2013"])
|
110
|
+
end
|
111
|
+
subject { calendar.roll_forward(date) }
|
112
|
+
|
113
|
+
context "given a business day" do
|
114
|
+
let(:date) { date_class.parse("Wednesday 2nd Jan, 2013") }
|
115
|
+
it { should == date }
|
116
|
+
end
|
117
|
+
|
118
|
+
context "given a non-business day" do
|
119
|
+
context "with a business day following it" do
|
120
|
+
let(:date) { date_class.parse("Tuesday 1st Jan, 2013") }
|
121
|
+
it { should == date + day_interval }
|
122
|
+
end
|
123
|
+
|
124
|
+
context "followed by another non-business day" do
|
125
|
+
let(:date) { date_class.parse("Saturday 5th Jan, 2013") }
|
126
|
+
it { should == date + 2 * day_interval }
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
describe "#roll_backward" do
|
132
|
+
let(:calendar) do
|
133
|
+
Business::Calendar.new(holidays: ["Tuesday 1st Jan, 2013"])
|
134
|
+
end
|
135
|
+
subject { calendar.roll_backward(date) }
|
136
|
+
|
137
|
+
context "given a business day" do
|
138
|
+
let(:date) { date_class.parse("Wednesday 2nd Jan, 2013") }
|
139
|
+
it { should == date }
|
140
|
+
end
|
141
|
+
|
142
|
+
context "given a non-business day" do
|
143
|
+
context "with a business day preceeding it" do
|
144
|
+
let(:date) { date_class.parse("Tuesday 1st Jan, 2013") }
|
145
|
+
it { should == date - day_interval }
|
146
|
+
end
|
147
|
+
|
148
|
+
context "preceeded by another non-business day" do
|
149
|
+
let(:date) { date_class.parse("Sunday 6th Jan, 2013") }
|
150
|
+
it { should == date - 2 * day_interval }
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
describe "#next_business_day" do
|
156
|
+
let(:calendar) do
|
157
|
+
Business::Calendar.new(holidays: ["Tuesday 1st Jan, 2013"])
|
158
|
+
end
|
159
|
+
subject { calendar.next_business_day(date) }
|
160
|
+
|
161
|
+
context "given a business day" do
|
162
|
+
let(:date) { date_class.parse("Wednesday 2nd Jan, 2013") }
|
163
|
+
it { should == date + day_interval }
|
164
|
+
end
|
165
|
+
|
166
|
+
context "given a non-business day" do
|
167
|
+
context "with a business day following it" do
|
168
|
+
let(:date) { date_class.parse("Tuesday 1st Jan, 2013") }
|
169
|
+
it { should == date + day_interval }
|
170
|
+
end
|
171
|
+
|
172
|
+
context "followed by another non-business day" do
|
173
|
+
let(:date) { date_class.parse("Saturday 5th Jan, 2013") }
|
174
|
+
it { should == date + 2 * day_interval }
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
describe "#add_business_days" do
|
180
|
+
let(:calendar) do
|
181
|
+
Business::Calendar.new(holidays: ["Tuesday 1st Jan, 2013"])
|
182
|
+
end
|
183
|
+
let(:delta) { 2 }
|
184
|
+
subject { calendar.add_business_days(date, delta) }
|
185
|
+
|
186
|
+
context "given a business day" do
|
187
|
+
context "and a period that includes only business days" do
|
188
|
+
let(:date) { date_class.parse("Wednesday 2nd Jan, 2013") }
|
189
|
+
it { should == date + delta * day_interval }
|
190
|
+
end
|
191
|
+
|
192
|
+
context "and a period that includes a weekend" do
|
193
|
+
let(:date) { date_class.parse("Friday 4th Jan, 2013") }
|
194
|
+
it { should == date + (delta + 2) * day_interval }
|
195
|
+
end
|
196
|
+
|
197
|
+
context "and a period that includes a holiday day" do
|
198
|
+
let(:date) { date_class.parse("Monday 31st Dec, 2012") }
|
199
|
+
it { should == date + (delta + 1) * day_interval }
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
context "given a non-business day" do
|
204
|
+
let(:date) { date_class.parse("Tuesday 1st Jan, 2013") }
|
205
|
+
it { should == date + (delta + 1) * day_interval }
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
describe "#subtract_business_days" do
|
210
|
+
let(:calendar) do
|
211
|
+
Business::Calendar.new(holidays: ["Thursday 3rd Jan, 2013"])
|
212
|
+
end
|
213
|
+
let(:delta) { 2 }
|
214
|
+
subject { calendar.subtract_business_days(date, delta) }
|
215
|
+
|
216
|
+
context "given a business day" do
|
217
|
+
context "and a period that includes only business days" do
|
218
|
+
let(:date) { date_class.parse("Wednesday 2nd Jan, 2013") }
|
219
|
+
it { should == date - delta * day_interval }
|
220
|
+
end
|
221
|
+
|
222
|
+
context "and a period that includes a weekend" do
|
223
|
+
let(:date) { date_class.parse("Monday 31st Dec, 2012") }
|
224
|
+
it { should == date - (delta + 2) * day_interval }
|
225
|
+
end
|
226
|
+
|
227
|
+
context "and a period that includes a holiday day" do
|
228
|
+
let(:date) { date_class.parse("Friday 4th Jan, 2013") }
|
229
|
+
it { should == date - (delta + 1) * day_interval }
|
230
|
+
end
|
231
|
+
end
|
232
|
+
|
233
|
+
context "given a non-business day" do
|
234
|
+
let(:date) { date_class.parse("Thursday 3rd Jan, 2013") }
|
235
|
+
it { should == date - (delta + 1) * day_interval }
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
239
|
+
describe "#business_days_between" do
|
240
|
+
let(:holidays) do
|
241
|
+
["Thu 12/6/2014", "Wed 18/6/2014", "Fri 20/6/2014", "Sun 22/6/2014"]
|
242
|
+
end
|
243
|
+
let(:calendar) { Business::Calendar.new(holidays: holidays) }
|
244
|
+
subject { calendar.business_days_between(date_1, date_2) }
|
245
|
+
|
246
|
+
|
247
|
+
context "starting on a business day" do
|
248
|
+
let(:date_1) { date_class.parse("Mon 2/6/2014") }
|
249
|
+
|
250
|
+
context "ending on a business day" do
|
251
|
+
context "including only business days" do
|
252
|
+
let(:date_2) { date_class.parse("Thu 5/6/2014") }
|
253
|
+
it { should == 3 }
|
254
|
+
end
|
255
|
+
|
256
|
+
context "including only business days & weekend days" do
|
257
|
+
let(:date_2) { date_class.parse("Mon 9/6/2014") }
|
258
|
+
it { should == 5 }
|
259
|
+
end
|
260
|
+
|
261
|
+
context "including only business days & holidays" do
|
262
|
+
let(:date_1) { date_class.parse("Mon 9/6/2014") }
|
263
|
+
let(:date_2) { date_class.parse("Fri 13/6/2014") }
|
264
|
+
it { should == 3 }
|
265
|
+
end
|
266
|
+
|
267
|
+
context "including business, weekend days, and holidays" do
|
268
|
+
let(:date_2) { date_class.parse("Fri 13/6/2014") }
|
269
|
+
it { should == 8 }
|
270
|
+
end
|
271
|
+
end
|
272
|
+
|
273
|
+
context "ending on a weekend day" do
|
274
|
+
context "including only business days & weekend days" do
|
275
|
+
let(:date_2) { date_class.parse("Sun 8/6/2014") }
|
276
|
+
it { should == 5 }
|
277
|
+
end
|
278
|
+
|
279
|
+
context "including business, weekend days, and holidays" do
|
280
|
+
let(:date_2) { date_class.parse("Sat 14/6/2014") }
|
281
|
+
it { should == 9 }
|
282
|
+
end
|
283
|
+
end
|
284
|
+
|
285
|
+
context "ending on a holiday" do
|
286
|
+
context "including only business days & holidays" do
|
287
|
+
let(:date_1) { date_class.parse("Mon 9/6/2014") }
|
288
|
+
let(:date_2) { date_class.parse("Thu 12/6/2014") }
|
289
|
+
it { should == 3 }
|
290
|
+
end
|
291
|
+
|
292
|
+
context "including business, weekend days, and holidays" do
|
293
|
+
let(:date_2) { date_class.parse("Thu 12/6/2014") }
|
294
|
+
it { should == 8 }
|
295
|
+
end
|
296
|
+
end
|
297
|
+
end
|
298
|
+
|
299
|
+
context "starting on a weekend" do
|
300
|
+
let(:date_1) { date_class.parse("Sat 7/6/2014") }
|
301
|
+
|
302
|
+
context "ending on a business day" do
|
303
|
+
|
304
|
+
context "including only business days & weekend days" do
|
305
|
+
let(:date_2) { date_class.parse("Mon 9/6/2014") }
|
306
|
+
it { should == 0 }
|
307
|
+
end
|
308
|
+
|
309
|
+
context "including business, weekend days, and holidays" do
|
310
|
+
let(:date_2) { date_class.parse("Fri 13/6/2014") }
|
311
|
+
it { should == 3 }
|
312
|
+
end
|
313
|
+
end
|
314
|
+
|
315
|
+
context "ending on a weekend day" do
|
316
|
+
context "including only business days & weekend days" do
|
317
|
+
let(:date_2) { date_class.parse("Sun 8/6/2014") }
|
318
|
+
it { should == 0 }
|
319
|
+
end
|
320
|
+
|
321
|
+
context "including business, weekend days, and holidays" do
|
322
|
+
let(:date_2) { date_class.parse("Sat 14/6/2014") }
|
323
|
+
it { should == 4 }
|
324
|
+
end
|
325
|
+
end
|
326
|
+
|
327
|
+
context "ending on a holiday" do
|
328
|
+
context "including business, weekend days, and holidays" do
|
329
|
+
let(:date_2) { date_class.parse("Thu 12/6/2014") }
|
330
|
+
it { should == 3 }
|
331
|
+
end
|
332
|
+
end
|
333
|
+
end
|
334
|
+
|
335
|
+
context "starting on a holiday" do
|
336
|
+
let(:date_1) { date_class.parse("Thu 12/6/2014") }
|
337
|
+
|
338
|
+
context "ending on a business day" do
|
339
|
+
|
340
|
+
context "including only business days & holidays" do
|
341
|
+
let(:date_2) { date_class.parse("Fri 13/6/2014") }
|
342
|
+
it { should == 0 }
|
343
|
+
end
|
344
|
+
|
345
|
+
context "including business, weekend days, and holidays" do
|
346
|
+
let(:date_2) { date_class.parse("Thu 19/6/2014") }
|
347
|
+
it { should == 3 }
|
348
|
+
end
|
349
|
+
end
|
350
|
+
|
351
|
+
context "ending on a weekend day" do
|
352
|
+
context "including business, weekend days, and holidays" do
|
353
|
+
let(:date_2) { date_class.parse("Sun 15/6/2014") }
|
354
|
+
it { should == 1 }
|
355
|
+
end
|
356
|
+
end
|
357
|
+
|
358
|
+
context "ending on a holiday" do
|
359
|
+
context "including only business days & holidays" do
|
360
|
+
let(:date_1) { date_class.parse("Wed 18/6/2014") }
|
361
|
+
let(:date_2) { date_class.parse("Fri 20/6/2014") }
|
362
|
+
it { should == 1 }
|
363
|
+
end
|
364
|
+
|
365
|
+
context "including business, weekend days, and holidays" do
|
366
|
+
let(:date_2) { date_class.parse("Wed 18/6/2014") }
|
367
|
+
it { should == 3 }
|
368
|
+
end
|
369
|
+
end
|
370
|
+
end
|
371
|
+
|
372
|
+
context "if a calendar has a holiday on a non-working (weekend) day" do
|
373
|
+
context "for a range less than a week long" do
|
374
|
+
let(:date_1) { date_class.parse("Thu 19/6/2014") }
|
375
|
+
let(:date_2) { date_class.parse("Tue 24/6/2014") }
|
376
|
+
it { should == 2 }
|
377
|
+
end
|
378
|
+
context "for a range more than a week long" do
|
379
|
+
let(:date_1) { date_class.parse("Mon 16/6/2014") }
|
380
|
+
let(:date_2) { date_class.parse("Tue 24/6/2014") }
|
381
|
+
it { should == 4 }
|
382
|
+
end
|
383
|
+
end
|
384
|
+
end
|
385
|
+
end
|
386
|
+
|
387
|
+
context "(using Date objects)" do
|
388
|
+
let(:date_class) { Date }
|
389
|
+
let(:day_interval) { 1 }
|
390
|
+
|
391
|
+
it_behaves_like "common"
|
392
|
+
end
|
393
|
+
|
394
|
+
context "(using Time objects)" do
|
395
|
+
let(:date_class) { Time }
|
396
|
+
let(:day_interval) { 3600 * 24 }
|
397
|
+
|
398
|
+
it_behaves_like "common"
|
399
|
+
end
|
400
|
+
|
401
|
+
context "(using DateTime objects)" do
|
402
|
+
let(:date_class) { DateTime }
|
403
|
+
let(:day_interval) { 1 }
|
404
|
+
|
405
|
+
it_behaves_like "common"
|
406
|
+
end
|
407
|
+
end
|
408
|
+
|
metadata
ADDED
@@ -0,0 +1,83 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: business
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Harry Marr
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-06-11 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ~>
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.3'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ~>
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.3'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rspec
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ~>
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 2.14.1
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ~>
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 2.14.1
|
41
|
+
description: Date calculations based on business calendars
|
42
|
+
email:
|
43
|
+
- engineering@gocardless.com
|
44
|
+
executables: []
|
45
|
+
extensions: []
|
46
|
+
extra_rdoc_files: []
|
47
|
+
files:
|
48
|
+
- .gitignore
|
49
|
+
- Gemfile
|
50
|
+
- README.md
|
51
|
+
- business.gemspec
|
52
|
+
- lib/business.rb
|
53
|
+
- lib/business/calendar.rb
|
54
|
+
- lib/business/data/bacs.yml
|
55
|
+
- lib/business/data/sepa.yml
|
56
|
+
- lib/business/data/weekdays.yml
|
57
|
+
- lib/business/version.rb
|
58
|
+
- spec/calendar_spec.rb
|
59
|
+
homepage: https://github.com/gocardless/business
|
60
|
+
licenses: []
|
61
|
+
metadata: {}
|
62
|
+
post_install_message:
|
63
|
+
rdoc_options: []
|
64
|
+
require_paths:
|
65
|
+
- lib
|
66
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
67
|
+
requirements:
|
68
|
+
- - '>='
|
69
|
+
- !ruby/object:Gem::Version
|
70
|
+
version: '0'
|
71
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - '>='
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
requirements: []
|
77
|
+
rubyforge_project:
|
78
|
+
rubygems_version: 2.2.2
|
79
|
+
signing_key:
|
80
|
+
specification_version: 4
|
81
|
+
summary: Date calculations based on business calendars
|
82
|
+
test_files:
|
83
|
+
- spec/calendar_spec.rb
|