business 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|