business_day 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 +7 -0
- data/.circleci/config.yml +37 -0
- data/.gitignore +17 -0
- data/.rubocop.yml +13 -0
- data/.ruby-version +1 -0
- data/CHANGELOG.md +118 -0
- data/Gemfile +5 -0
- data/LICENSE +22 -0
- data/README.md +196 -0
- data/business_day.gemspec +27 -0
- data/lib/business_day.rb +3 -0
- data/lib/business_day/calendar.rb +229 -0
- data/lib/business_day/version.rb +5 -0
- data/spec/business/calendar_spec.rb +935 -0
- data/spec/fixtures/calendars/bacs.yml +9 -0
- data/spec/fixtures/calendars/ecb.yml +49 -0
- data/spec/fixtures/calendars/invalid-keys.yml +9 -0
- metadata +119 -0
data/lib/business_day.rb
ADDED
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "yaml"
|
|
4
|
+
require "date"
|
|
5
|
+
|
|
6
|
+
module BusinessDay
|
|
7
|
+
class Calendar
|
|
8
|
+
VALID_KEYS = %w[holidays working_days extra_working_dates].freeze
|
|
9
|
+
|
|
10
|
+
class << self
|
|
11
|
+
attr_accessor :load_paths
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def self.calendar_directories
|
|
15
|
+
@load_paths
|
|
16
|
+
end
|
|
17
|
+
private_class_method :calendar_directories
|
|
18
|
+
|
|
19
|
+
def self.load(calendar_name)
|
|
20
|
+
data = find_calendar_data(calendar_name)
|
|
21
|
+
raise "No such calendar '#{calendar_name}'" unless data
|
|
22
|
+
|
|
23
|
+
unless (data.keys - VALID_KEYS).empty?
|
|
24
|
+
raise "Only valid keys are: #{VALID_KEYS.join(', ')}"
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
new(
|
|
28
|
+
holidays: data["holidays"],
|
|
29
|
+
working_days: data["working_days"],
|
|
30
|
+
extra_working_dates: data["extra_working_dates"],
|
|
31
|
+
)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def self.find_calendar_data(calendar_name)
|
|
35
|
+
calendar_directories.detect do |path|
|
|
36
|
+
if path.is_a?(Hash)
|
|
37
|
+
break path[calendar_name] if path[calendar_name]
|
|
38
|
+
else
|
|
39
|
+
next unless File.exist?(File.join(path, "#{calendar_name}.yml"))
|
|
40
|
+
|
|
41
|
+
break YAML.load_file(File.join(path, "#{calendar_name}.yml"))
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
@lock = Mutex.new
|
|
47
|
+
def self.load_cached(calendar)
|
|
48
|
+
@lock.synchronize do
|
|
49
|
+
@cache ||= {}
|
|
50
|
+
@cache[calendar] = self.load(calendar) unless @cache.include?(calendar)
|
|
51
|
+
@cache[calendar]
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
DAY_NAMES = %( mon tue wed thu fri sat sun )
|
|
56
|
+
|
|
57
|
+
attr_reader :holidays, :working_days, :extra_working_dates
|
|
58
|
+
|
|
59
|
+
def initialize(config)
|
|
60
|
+
set_extra_working_dates(config[:extra_working_dates])
|
|
61
|
+
set_working_days(config[:working_days])
|
|
62
|
+
set_holidays(config[:holidays])
|
|
63
|
+
|
|
64
|
+
unless (@holidays & @extra_working_dates).none?
|
|
65
|
+
raise ArgumentError, "Holidays cannot be extra working dates"
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Return true if the date given is a business day (typically that means a
|
|
70
|
+
# non-weekend day) and not a holiday.
|
|
71
|
+
def business_day?(date)
|
|
72
|
+
date = date.to_date
|
|
73
|
+
working_day?(date) && !holiday?(date)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def working_day?(date)
|
|
77
|
+
date = date.to_date
|
|
78
|
+
extra_working_dates.include?(date) ||
|
|
79
|
+
working_days.include?(date.strftime("%a").downcase)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def holiday?(date)
|
|
83
|
+
holidays.include?(date.to_date)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Roll forward to the next business day. If the date given is a business
|
|
87
|
+
# day, that day will be returned. If the day given is a holiday or
|
|
88
|
+
# non-working day, the next non-holiday working day will be returned.
|
|
89
|
+
def roll_forward(date)
|
|
90
|
+
date += day_interval_for(date) until business_day?(date)
|
|
91
|
+
date
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# Roll backward to the previous business day. If the date given is a
|
|
95
|
+
# business day, that day will be returned. If the day given is a holiday or
|
|
96
|
+
# non-working day, the previous non-holiday working day will be returned.
|
|
97
|
+
def roll_backward(date)
|
|
98
|
+
date -= day_interval_for(date) until business_day?(date)
|
|
99
|
+
date
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# Roll forward to the next business day regardless of whether the given
|
|
103
|
+
# date is a business day or not.
|
|
104
|
+
def next_business_day(date)
|
|
105
|
+
loop do
|
|
106
|
+
date += day_interval_for(date)
|
|
107
|
+
break date if business_day?(date)
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# Roll backward to the previous business day regardless of whether the given
|
|
112
|
+
# date is a business day or not.
|
|
113
|
+
def previous_business_day(date)
|
|
114
|
+
loop do
|
|
115
|
+
date -= day_interval_for(date)
|
|
116
|
+
break date if business_day?(date)
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# Add a number of business days to a date. If a non-business day is given,
|
|
121
|
+
# counting will start from the next business day. So,
|
|
122
|
+
# monday + 1 = tuesday
|
|
123
|
+
# friday + 1 = monday
|
|
124
|
+
# sunday + 1 = tuesday
|
|
125
|
+
def add_business_days(date, delta)
|
|
126
|
+
date = roll_forward(date)
|
|
127
|
+
delta.times do
|
|
128
|
+
loop do
|
|
129
|
+
date += day_interval_for(date)
|
|
130
|
+
break date if business_day?(date)
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
date
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
# Subtract a number of business days to a date. If a non-business day is
|
|
137
|
+
# given, counting will start from the previous business day. So,
|
|
138
|
+
# friday - 1 = thursday
|
|
139
|
+
# monday - 1 = friday
|
|
140
|
+
# sunday - 1 = thursday
|
|
141
|
+
def subtract_business_days(date, delta)
|
|
142
|
+
date = roll_backward(date)
|
|
143
|
+
delta.times do
|
|
144
|
+
loop do
|
|
145
|
+
date -= day_interval_for(date)
|
|
146
|
+
break date if business_day?(date)
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
date
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
# Count the number of business days between two dates.
|
|
153
|
+
# This method counts from start of date1 to start of date2. So,
|
|
154
|
+
# business_days_between(mon, weds) = 2 (assuming no holidays)
|
|
155
|
+
# rubocop:disable Metrics/AbcSize
|
|
156
|
+
# rubocop:disable Metrics/MethodLength
|
|
157
|
+
def business_days_between(date1, date2)
|
|
158
|
+
date1 = date1.to_date
|
|
159
|
+
date2 = date2.to_date
|
|
160
|
+
|
|
161
|
+
# To optimise this method we split the range into full weeks and a
|
|
162
|
+
# remaining period.
|
|
163
|
+
#
|
|
164
|
+
# We then calculate business days in the full weeks period by
|
|
165
|
+
# multiplying number of weeks by number of working days in a week and
|
|
166
|
+
# removing holidays one by one.
|
|
167
|
+
|
|
168
|
+
# For the remaining period, we just loop through each day and check
|
|
169
|
+
# whether it is a business day.
|
|
170
|
+
|
|
171
|
+
# Calculate number of full weeks and remaining days
|
|
172
|
+
num_full_weeks, remaining_days = (date2 - date1).to_i.divmod(7)
|
|
173
|
+
|
|
174
|
+
# First estimate for full week range based on # biz days in a week
|
|
175
|
+
num_biz_days = num_full_weeks * working_days.length
|
|
176
|
+
|
|
177
|
+
full_weeks_range = (date1...(date2 - remaining_days))
|
|
178
|
+
num_biz_days -= holidays.count do |holiday|
|
|
179
|
+
in_range = full_weeks_range.cover?(holiday)
|
|
180
|
+
# Only pick a holiday if its on a working day (e.g., not a weekend)
|
|
181
|
+
on_biz_day = working_days.include?(holiday.strftime("%a").downcase)
|
|
182
|
+
in_range && on_biz_day
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
remaining_range = (date2 - remaining_days...date2)
|
|
186
|
+
# Loop through each day in remaining_range and count if a business day
|
|
187
|
+
num_biz_days + remaining_range.count { |a| business_day?(a) }
|
|
188
|
+
end
|
|
189
|
+
# rubocop:enable Metrics/AbcSize
|
|
190
|
+
# rubocop:enable Metrics/MethodLength
|
|
191
|
+
|
|
192
|
+
def day_interval_for(date)
|
|
193
|
+
date.is_a?(Date) ? 1 : 3600 * 24
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
# Internal method for assigning working days from a calendar config.
|
|
197
|
+
def set_working_days(working_days)
|
|
198
|
+
@working_days = (working_days || default_working_days).map do |day|
|
|
199
|
+
day.downcase.strip[0..2].tap do |normalised_day|
|
|
200
|
+
raise "Invalid day #{day}" unless DAY_NAMES.include?(normalised_day)
|
|
201
|
+
end
|
|
202
|
+
end
|
|
203
|
+
extra_working_dates_names = @extra_working_dates.map do |d|
|
|
204
|
+
d.strftime("%a").downcase
|
|
205
|
+
end
|
|
206
|
+
return if (extra_working_dates_names & @working_days).none?
|
|
207
|
+
|
|
208
|
+
raise ArgumentError, "Extra working dates cannot be on working days"
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
def parse_dates(dates)
|
|
212
|
+
(dates || []).map { |date| date.is_a?(Date) ? date : Date.parse(date) }
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
# Internal method for assigning holidays from a calendar config.
|
|
216
|
+
def set_holidays(holidays)
|
|
217
|
+
@holidays = parse_dates(holidays)
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
def set_extra_working_dates(extra_working_dates)
|
|
221
|
+
@extra_working_dates = parse_dates(extra_working_dates)
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
# If no working days are provided in the calendar config, these are used.
|
|
225
|
+
def default_working_days
|
|
226
|
+
%w[mon tue wed thu fri]
|
|
227
|
+
end
|
|
228
|
+
end
|
|
229
|
+
end
|
|
@@ -0,0 +1,935 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "business/calendar"
|
|
4
|
+
require "time"
|
|
5
|
+
|
|
6
|
+
RSpec.configure do |config|
|
|
7
|
+
config.mock_with(:rspec) { |mocks| mocks.verify_partial_doubles = true }
|
|
8
|
+
config.raise_errors_for_deprecations!
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
RSpec.describe BusinessDay::Calendar do
|
|
12
|
+
describe ".load" do
|
|
13
|
+
subject(:load_calendar) { described_class.load(calendar) }
|
|
14
|
+
|
|
15
|
+
let(:dummy_calendar) { { "working_days" => ["monday"] } }
|
|
16
|
+
|
|
17
|
+
before do
|
|
18
|
+
fixture_path = File.join(File.dirname(__FILE__), "../fixtures", "calendars")
|
|
19
|
+
described_class.load_paths = [fixture_path, { "foobar" => dummy_calendar }]
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
context "when given a calendar from a custom directory" do
|
|
23
|
+
let(:calendar) { "ecb" }
|
|
24
|
+
|
|
25
|
+
after { described_class.load_paths = nil }
|
|
26
|
+
|
|
27
|
+
it "loads the yaml file" do
|
|
28
|
+
expect(YAML).to receive(:load_file).with(/ecb\.yml$/).and_return({})
|
|
29
|
+
|
|
30
|
+
load_calendar
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
it { is_expected.to be_a described_class }
|
|
34
|
+
|
|
35
|
+
context "that also exists as a default calendar" do
|
|
36
|
+
let(:calendar) { "bacs" }
|
|
37
|
+
|
|
38
|
+
it "uses the custom calendar" do
|
|
39
|
+
expect(load_calendar.business_day?(Date.parse("25th December 2014"))).
|
|
40
|
+
to eq(true)
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
context "when loading a calendar as a hash" do
|
|
46
|
+
let(:calendar) { "foobar" }
|
|
47
|
+
|
|
48
|
+
it { is_expected.to be_a described_class }
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
context "when given a calendar that does not exist" do
|
|
52
|
+
let(:calendar) { "invalid-calendar" }
|
|
53
|
+
|
|
54
|
+
specify { expect { load_calendar }.to raise_error(/No such calendar/) }
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
context "when given a calendar that has invalid keys" do
|
|
58
|
+
let(:calendar) { "invalid-keys" }
|
|
59
|
+
|
|
60
|
+
specify do
|
|
61
|
+
expect { load_calendar }.
|
|
62
|
+
to raise_error(
|
|
63
|
+
"Only valid keys are: holidays, working_days, extra_working_dates",
|
|
64
|
+
)
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
context "when given real business data" do
|
|
69
|
+
let(:data_path) do
|
|
70
|
+
File.join(File.dirname(__FILE__), "..", "lib", "business", "data")
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
it "validates they are all loadable by the calendar" do
|
|
74
|
+
Dir.glob("#{data_path}/*").each do |filename|
|
|
75
|
+
calendar_name = File.basename(filename, ".yml")
|
|
76
|
+
calendar = described_class.load(calendar_name)
|
|
77
|
+
|
|
78
|
+
expect(calendar.working_days.length).to be >= 1
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
describe "#set_working_days" do
|
|
85
|
+
subject(:set_working_days) { calendar.set_working_days(working_days) }
|
|
86
|
+
|
|
87
|
+
let(:calendar) { described_class.new({}) }
|
|
88
|
+
let(:working_days) { [] }
|
|
89
|
+
|
|
90
|
+
context "when given valid working days" do
|
|
91
|
+
let(:working_days) { %w[mon fri] }
|
|
92
|
+
|
|
93
|
+
before { set_working_days }
|
|
94
|
+
|
|
95
|
+
it "assigns them" do
|
|
96
|
+
expect(calendar.working_days).to eq(working_days)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
context "that are unnormalised" do
|
|
100
|
+
let(:working_days) { %w[Monday Friday] }
|
|
101
|
+
|
|
102
|
+
it "normalises them" do
|
|
103
|
+
expect(calendar.working_days).to eq(%w[mon fri])
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
context "when given an invalid business day" do
|
|
109
|
+
let(:working_days) { %w[Notaday] }
|
|
110
|
+
|
|
111
|
+
specify { expect { set_working_days }.to raise_error(/Invalid day/) }
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
context "when given nil" do
|
|
115
|
+
let(:working_days) { nil }
|
|
116
|
+
|
|
117
|
+
it "uses the default business days" do
|
|
118
|
+
expect(calendar.working_days).to eq(calendar.default_working_days)
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
describe "#set_holidays" do
|
|
124
|
+
subject(:holidays) { calendar.holidays }
|
|
125
|
+
|
|
126
|
+
let(:calendar) { described_class.new({}) }
|
|
127
|
+
let(:holiday_dates) { [] }
|
|
128
|
+
|
|
129
|
+
before { calendar.set_holidays(holiday_dates) }
|
|
130
|
+
|
|
131
|
+
context "when given valid business days" do
|
|
132
|
+
let(:holiday_dates) { ["1st Jan, 2013"] }
|
|
133
|
+
|
|
134
|
+
it { is_expected.to_not be_empty }
|
|
135
|
+
|
|
136
|
+
it "converts them to Date objects" do
|
|
137
|
+
expect(holidays).to all be_a Date
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
context "when given nil" do
|
|
142
|
+
let(:holiday_dates) { nil }
|
|
143
|
+
|
|
144
|
+
it { is_expected.to be_empty }
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
describe "#set_extra_working_dates" do
|
|
149
|
+
subject(:extra_working_dates) { calendar.extra_working_dates }
|
|
150
|
+
|
|
151
|
+
let(:calendar) { described_class.new({}) }
|
|
152
|
+
let(:extra_dates) { [] }
|
|
153
|
+
|
|
154
|
+
before { calendar.set_extra_working_dates(extra_dates) }
|
|
155
|
+
|
|
156
|
+
context "when given valid business days" do
|
|
157
|
+
let(:extra_dates) { ["1st Jan, 2013"] }
|
|
158
|
+
|
|
159
|
+
it { is_expected.to_not be_empty }
|
|
160
|
+
|
|
161
|
+
it "converts them to Date objects" do
|
|
162
|
+
expect(extra_working_dates).to all be_a Date
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
context "when given nil" do
|
|
167
|
+
let(:holidays) { nil }
|
|
168
|
+
|
|
169
|
+
it { is_expected.to be_empty }
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
context "when holiday is also a working date" do
|
|
174
|
+
let(:instance) do
|
|
175
|
+
described_class.new(holidays: ["2018-01-06"],
|
|
176
|
+
extra_working_dates: ["2018-01-06"])
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
it do
|
|
180
|
+
expect { instance }.to raise_error(ArgumentError).
|
|
181
|
+
with_message("Holidays cannot be extra working dates")
|
|
182
|
+
end
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
context "when working date on working day" do
|
|
186
|
+
let(:instance) do
|
|
187
|
+
described_class.new(working_days: ["mon"],
|
|
188
|
+
extra_working_dates: ["Monday 26th Mar, 2018"])
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
it do
|
|
192
|
+
expect { instance }.to raise_error(ArgumentError).
|
|
193
|
+
with_message("Extra working dates cannot be on working days")
|
|
194
|
+
end
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
# A set of examples that are supposed to work when given Date and Time
|
|
198
|
+
# objects. The implementation slightly differs, so i's worth running the
|
|
199
|
+
# tests for both Date *and* Time.
|
|
200
|
+
shared_examples "common" do
|
|
201
|
+
describe "#business_day?" do
|
|
202
|
+
subject { calendar.business_day?(day) }
|
|
203
|
+
|
|
204
|
+
let(:calendar) do
|
|
205
|
+
described_class.new(holidays: ["9am, Tuesday 1st Jan, 2013"],
|
|
206
|
+
extra_working_dates: ["9am, Sunday 6th Jan, 2013"])
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
context "when given a business day" do
|
|
210
|
+
let(:day) { date_class.parse("9am, Wednesday 2nd Jan, 2013") }
|
|
211
|
+
|
|
212
|
+
it { is_expected.to be_truthy }
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
context "when given a non-business day" do
|
|
216
|
+
let(:day) { date_class.parse("9am, Saturday 5th Jan, 2013") }
|
|
217
|
+
|
|
218
|
+
it { is_expected.to be_falsey }
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
context "when given a business day that is a holiday" do
|
|
222
|
+
let(:day) { date_class.parse("9am, Tuesday 1st Jan, 2013") }
|
|
223
|
+
|
|
224
|
+
it { is_expected.to be_falsey }
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
context "when given a non-business day that is a working date" do
|
|
228
|
+
let(:day) { date_class.parse("9am, Sunday 6th Jan, 2013") }
|
|
229
|
+
|
|
230
|
+
it { is_expected.to be_truthy }
|
|
231
|
+
end
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
describe "#working_day?" do
|
|
235
|
+
subject { calendar.working_day?(day) }
|
|
236
|
+
|
|
237
|
+
let(:calendar) do
|
|
238
|
+
described_class.new(holidays: ["9am, Tuesday 1st Jan, 2013"],
|
|
239
|
+
extra_working_dates: ["9am, Sunday 6th Jan, 2013"])
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
context "when given a working day" do
|
|
243
|
+
let(:day) { date_class.parse("9am, Wednesday 2nd Jan, 2013") }
|
|
244
|
+
|
|
245
|
+
it { is_expected.to be_truthy }
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
context "when given a non-working day" do
|
|
249
|
+
let(:day) { date_class.parse("9am, Saturday 5th Jan, 2013") }
|
|
250
|
+
|
|
251
|
+
it { is_expected.to be_falsey }
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
context "when given a working day that is a holiday" do
|
|
255
|
+
let(:day) { date_class.parse("9am, Tuesday 1st Jan, 2013") }
|
|
256
|
+
|
|
257
|
+
it { is_expected.to be_truthy }
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
context "when given a non-business day that is a working date" do
|
|
261
|
+
let(:day) { date_class.parse("9am, Sunday 6th Jan, 2013") }
|
|
262
|
+
|
|
263
|
+
it { is_expected.to be_truthy }
|
|
264
|
+
end
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
describe "#holiday?" do
|
|
268
|
+
subject { calendar.holiday?(day) }
|
|
269
|
+
|
|
270
|
+
let(:calendar) do
|
|
271
|
+
described_class.new(holidays: ["9am, Tuesday 1st Jan, 2013"],
|
|
272
|
+
extra_working_dates: ["9am, Sunday 6th Jan, 2013"])
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
context "when given a working day that is not a holiday" do
|
|
276
|
+
let(:day) { date_class.parse("9am, Wednesday 2nd Jan, 2013") }
|
|
277
|
+
|
|
278
|
+
it { is_expected.to be_falsey }
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
context "when given a non-working day that is not a holiday day" do
|
|
282
|
+
let(:day) { date_class.parse("9am, Saturday 5th Jan, 2013") }
|
|
283
|
+
|
|
284
|
+
it { is_expected.to be_falsey }
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
context "when given a day that is a holiday" do
|
|
288
|
+
let(:day) { date_class.parse("9am, Tuesday 1st Jan, 2013") }
|
|
289
|
+
|
|
290
|
+
it { is_expected.to be_truthy }
|
|
291
|
+
end
|
|
292
|
+
|
|
293
|
+
context "when given a non-business day that is no a holiday" do
|
|
294
|
+
let(:day) { date_class.parse("9am, Sunday 6th Jan, 2013") }
|
|
295
|
+
|
|
296
|
+
it { is_expected.to be_falsey }
|
|
297
|
+
end
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
describe "#roll_forward" do
|
|
301
|
+
subject { calendar.roll_forward(date) }
|
|
302
|
+
|
|
303
|
+
let(:calendar) do
|
|
304
|
+
described_class.new(holidays: ["Tuesday 1st Jan, 2013"])
|
|
305
|
+
end
|
|
306
|
+
|
|
307
|
+
context "given a business day" do
|
|
308
|
+
let(:date) { date_class.parse("Wednesday 2nd Jan, 2013") }
|
|
309
|
+
|
|
310
|
+
it { is_expected.to eq(date) }
|
|
311
|
+
end
|
|
312
|
+
|
|
313
|
+
context "given a non-business day" do
|
|
314
|
+
context "with a business day following it" do
|
|
315
|
+
let(:date) { date_class.parse("Tuesday 1st Jan, 2013") }
|
|
316
|
+
|
|
317
|
+
it { is_expected.to eq(date + day_interval) }
|
|
318
|
+
end
|
|
319
|
+
|
|
320
|
+
context "followed by another non-business day" do
|
|
321
|
+
let(:date) { date_class.parse("Saturday 5th Jan, 2013") }
|
|
322
|
+
|
|
323
|
+
it { is_expected.to eq(date + 2 * day_interval) }
|
|
324
|
+
end
|
|
325
|
+
end
|
|
326
|
+
end
|
|
327
|
+
|
|
328
|
+
describe "#roll_backward" do
|
|
329
|
+
subject { calendar.roll_backward(date) }
|
|
330
|
+
|
|
331
|
+
let(:calendar) do
|
|
332
|
+
described_class.new(holidays: ["Tuesday 1st Jan, 2013"])
|
|
333
|
+
end
|
|
334
|
+
|
|
335
|
+
context "given a business day" do
|
|
336
|
+
let(:date) { date_class.parse("Wednesday 2nd Jan, 2013") }
|
|
337
|
+
|
|
338
|
+
it { is_expected.to eq(date) }
|
|
339
|
+
end
|
|
340
|
+
|
|
341
|
+
context "given a non-business day" do
|
|
342
|
+
context "with a business day preceeding it" do
|
|
343
|
+
let(:date) { date_class.parse("Tuesday 1st Jan, 2013") }
|
|
344
|
+
|
|
345
|
+
it { is_expected.to eq(date - day_interval) }
|
|
346
|
+
end
|
|
347
|
+
|
|
348
|
+
context "preceeded by another non-business day" do
|
|
349
|
+
let(:date) { date_class.parse("Sunday 6th Jan, 2013") }
|
|
350
|
+
|
|
351
|
+
it { is_expected.to eq(date - 2 * day_interval) }
|
|
352
|
+
end
|
|
353
|
+
end
|
|
354
|
+
end
|
|
355
|
+
|
|
356
|
+
describe "#next_business_day" do
|
|
357
|
+
subject { calendar.next_business_day(date) }
|
|
358
|
+
|
|
359
|
+
let(:calendar) do
|
|
360
|
+
described_class.new(holidays: ["Tuesday 1st Jan, 2013"])
|
|
361
|
+
end
|
|
362
|
+
|
|
363
|
+
context "given a business day" do
|
|
364
|
+
let(:date) { date_class.parse("Wednesday 2nd Jan, 2013") }
|
|
365
|
+
|
|
366
|
+
it { is_expected.to eq(date + day_interval) }
|
|
367
|
+
end
|
|
368
|
+
|
|
369
|
+
context "given a non-business day" do
|
|
370
|
+
context "with a business day following it" do
|
|
371
|
+
let(:date) { date_class.parse("Tuesday 1st Jan, 2013") }
|
|
372
|
+
|
|
373
|
+
it { is_expected.to eq(date + day_interval) }
|
|
374
|
+
end
|
|
375
|
+
|
|
376
|
+
context "followed by another non-business day" do
|
|
377
|
+
let(:date) { date_class.parse("Saturday 5th Jan, 2013") }
|
|
378
|
+
|
|
379
|
+
it { is_expected.to eq(date + 2 * day_interval) }
|
|
380
|
+
end
|
|
381
|
+
end
|
|
382
|
+
end
|
|
383
|
+
|
|
384
|
+
describe "#previous_business_day" do
|
|
385
|
+
subject { calendar.previous_business_day(date) }
|
|
386
|
+
|
|
387
|
+
let(:calendar) do
|
|
388
|
+
described_class.new(holidays: ["Tuesday 1st Jan, 2013"])
|
|
389
|
+
end
|
|
390
|
+
|
|
391
|
+
context "given a business day" do
|
|
392
|
+
let(:date) { date_class.parse("Thursday 3nd Jan, 2013") }
|
|
393
|
+
|
|
394
|
+
it { is_expected.to eq(date - day_interval) }
|
|
395
|
+
end
|
|
396
|
+
|
|
397
|
+
context "given a non-business day" do
|
|
398
|
+
context "with a business day before it" do
|
|
399
|
+
let(:date) { date_class.parse("Tuesday 1st Jan, 2013") }
|
|
400
|
+
|
|
401
|
+
it { is_expected.to eq(date - day_interval) }
|
|
402
|
+
end
|
|
403
|
+
|
|
404
|
+
context "preceeded by another non-business day" do
|
|
405
|
+
let(:date) { date_class.parse("Sunday 6th Jan, 2013") }
|
|
406
|
+
|
|
407
|
+
it { is_expected.to eq(date - 2 * day_interval) }
|
|
408
|
+
end
|
|
409
|
+
end
|
|
410
|
+
end
|
|
411
|
+
|
|
412
|
+
describe "#add_business_days" do
|
|
413
|
+
subject { calendar.add_business_days(date, delta) }
|
|
414
|
+
|
|
415
|
+
let(:extra_working_dates) { [] }
|
|
416
|
+
let(:calendar) do
|
|
417
|
+
described_class.new(holidays: ["Tuesday 1st Jan, 2013"],
|
|
418
|
+
extra_working_dates: extra_working_dates)
|
|
419
|
+
end
|
|
420
|
+
let(:delta) { 2 }
|
|
421
|
+
|
|
422
|
+
context "given a business day" do
|
|
423
|
+
context "and a period that includes only business days" do
|
|
424
|
+
let(:date) { date_class.parse("Wednesday 2nd Jan, 2013") }
|
|
425
|
+
|
|
426
|
+
it { is_expected.to eq(date + delta * day_interval) }
|
|
427
|
+
end
|
|
428
|
+
|
|
429
|
+
context "and a period that includes a weekend" do
|
|
430
|
+
let(:date) { date_class.parse("Friday 4th Jan, 2013") }
|
|
431
|
+
|
|
432
|
+
it { is_expected.to eq(date + (delta + 2) * day_interval) }
|
|
433
|
+
end
|
|
434
|
+
|
|
435
|
+
context "and a period that includes a working date weekend" do
|
|
436
|
+
let(:extra_working_dates) { ["Sunday 6th Jan, 2013"] }
|
|
437
|
+
let(:date) { date_class.parse("Friday 4th Jan, 2013") }
|
|
438
|
+
|
|
439
|
+
it { is_expected.to eq(date + (delta + 1) * day_interval) }
|
|
440
|
+
end
|
|
441
|
+
|
|
442
|
+
context "and a period that includes a holiday day" do
|
|
443
|
+
let(:date) { date_class.parse("Monday 31st Dec, 2012") }
|
|
444
|
+
|
|
445
|
+
it { is_expected.to eq(date + (delta + 1) * day_interval) }
|
|
446
|
+
end
|
|
447
|
+
end
|
|
448
|
+
|
|
449
|
+
context "given a non-business day" do
|
|
450
|
+
let(:date) { date_class.parse("Tuesday 1st Jan, 2013") }
|
|
451
|
+
|
|
452
|
+
it { is_expected.to eq(date + (delta + 1) * day_interval) }
|
|
453
|
+
end
|
|
454
|
+
end
|
|
455
|
+
|
|
456
|
+
describe "#subtract_business_days" do
|
|
457
|
+
subject { calendar.subtract_business_days(date, delta) }
|
|
458
|
+
|
|
459
|
+
let(:extra_working_dates) { [] }
|
|
460
|
+
let(:calendar) do
|
|
461
|
+
described_class.new(holidays: ["Thursday 3rd Jan, 2013"],
|
|
462
|
+
extra_working_dates: extra_working_dates)
|
|
463
|
+
end
|
|
464
|
+
let(:delta) { 2 }
|
|
465
|
+
|
|
466
|
+
context "given a business day" do
|
|
467
|
+
context "and a period that includes only business days" do
|
|
468
|
+
let(:date) { date_class.parse("Wednesday 2nd Jan, 2013") }
|
|
469
|
+
|
|
470
|
+
it { is_expected.to eq(date - delta * day_interval) }
|
|
471
|
+
end
|
|
472
|
+
|
|
473
|
+
context "and a period that includes a weekend" do
|
|
474
|
+
let(:date) { date_class.parse("Monday 31st Dec, 2012") }
|
|
475
|
+
|
|
476
|
+
it { is_expected.to eq(date - (delta + 2) * day_interval) }
|
|
477
|
+
end
|
|
478
|
+
|
|
479
|
+
context "and a period that includes a working date weekend" do
|
|
480
|
+
let(:extra_working_dates) { ["Saturday 29th Dec, 2012"] }
|
|
481
|
+
let(:date) { date_class.parse("Monday 31st Dec, 2012") }
|
|
482
|
+
|
|
483
|
+
it { is_expected.to eq(date - (delta + 1) * day_interval) }
|
|
484
|
+
end
|
|
485
|
+
|
|
486
|
+
context "and a period that includes a holiday day" do
|
|
487
|
+
let(:date) { date_class.parse("Friday 4th Jan, 2013") }
|
|
488
|
+
|
|
489
|
+
it { is_expected.to eq(date - (delta + 1) * day_interval) }
|
|
490
|
+
end
|
|
491
|
+
end
|
|
492
|
+
|
|
493
|
+
context "given a non-business day" do
|
|
494
|
+
let(:date) { date_class.parse("Thursday 3rd Jan, 2013") }
|
|
495
|
+
|
|
496
|
+
it { is_expected.to eq(date - (delta + 1) * day_interval) }
|
|
497
|
+
end
|
|
498
|
+
end
|
|
499
|
+
|
|
500
|
+
describe "#business_days_between" do
|
|
501
|
+
subject do
|
|
502
|
+
calendar.business_days_between(date_class.parse(date_1),
|
|
503
|
+
date_class.parse(date_2))
|
|
504
|
+
end
|
|
505
|
+
|
|
506
|
+
let(:holidays) do
|
|
507
|
+
["Wed 27/5/2014", "Thu 12/6/2014", "Wed 18/6/2014", "Fri 20/6/2014",
|
|
508
|
+
"Sun 22/6/2014", "Fri 27/6/2014", "Thu 3/7/2014"]
|
|
509
|
+
end
|
|
510
|
+
let(:extra_working_dates) do
|
|
511
|
+
["Sun 1/6/2014", "Sat 28/6/2014", "Sat 5/7/2014"]
|
|
512
|
+
end
|
|
513
|
+
let(:calendar) do
|
|
514
|
+
described_class.new(holidays: holidays, extra_working_dates: extra_working_dates)
|
|
515
|
+
end
|
|
516
|
+
|
|
517
|
+
context "starting on a business day" do
|
|
518
|
+
let(:date_1) { "Mon 2/6/2014" }
|
|
519
|
+
|
|
520
|
+
context "ending on a business day" do
|
|
521
|
+
context "including only business days" do
|
|
522
|
+
let(:date_2) { "Thu 5/6/2014" }
|
|
523
|
+
|
|
524
|
+
it { is_expected.to eq(3) }
|
|
525
|
+
end
|
|
526
|
+
|
|
527
|
+
context "including only business days & weekend days" do
|
|
528
|
+
let(:date_2) { "Mon 9/6/2014" }
|
|
529
|
+
|
|
530
|
+
it { is_expected.to eq(5) }
|
|
531
|
+
end
|
|
532
|
+
|
|
533
|
+
context "including only business days, weekend days & working date" do
|
|
534
|
+
let(:date_1) { "Thu 29/5/2014" }
|
|
535
|
+
let(:date_2) { "The 3/6/2014" }
|
|
536
|
+
|
|
537
|
+
it { is_expected.to be(4) }
|
|
538
|
+
end
|
|
539
|
+
|
|
540
|
+
context "including only business days & holidays" do
|
|
541
|
+
let(:date_1) { "Mon 9/6/2014" }
|
|
542
|
+
let(:date_2) { "Fri 13/6/2014" }
|
|
543
|
+
|
|
544
|
+
it { is_expected.to eq(3) }
|
|
545
|
+
end
|
|
546
|
+
|
|
547
|
+
context "including business, weekend days, and holidays" do
|
|
548
|
+
let(:date_2) { "Fri 13/6/2014" }
|
|
549
|
+
|
|
550
|
+
it { is_expected.to eq(8) }
|
|
551
|
+
end
|
|
552
|
+
|
|
553
|
+
context "including business, weekend, hoilday days & working date" do
|
|
554
|
+
let(:date_1) { "Thu 26/6/2014" }
|
|
555
|
+
let(:date_2) { "The 1/7/2014" }
|
|
556
|
+
|
|
557
|
+
it { is_expected.to be(3) }
|
|
558
|
+
end
|
|
559
|
+
end
|
|
560
|
+
|
|
561
|
+
context "ending on a weekend day" do
|
|
562
|
+
context "including only business days & weekend days" do
|
|
563
|
+
let(:date_2) { "Sun 8/6/2014" }
|
|
564
|
+
|
|
565
|
+
it { is_expected.to eq(5) }
|
|
566
|
+
end
|
|
567
|
+
|
|
568
|
+
context "including business & weekend days & working date" do
|
|
569
|
+
let(:date_1) { "Thu 29/5/2014" }
|
|
570
|
+
let(:date_2) { "Sun 3/6/2014" }
|
|
571
|
+
|
|
572
|
+
it { is_expected.to eq(4) }
|
|
573
|
+
end
|
|
574
|
+
|
|
575
|
+
context "including business, weekend days, and holidays" do
|
|
576
|
+
let(:date_2) { "Sat 14/6/2014" }
|
|
577
|
+
|
|
578
|
+
it { is_expected.to eq(9) }
|
|
579
|
+
end
|
|
580
|
+
|
|
581
|
+
context "including business, weekend & holiday days & working date" do
|
|
582
|
+
let(:date_1) { "Thu 26/6/2014" }
|
|
583
|
+
let(:date_2) { "Tue 2/7/2014" }
|
|
584
|
+
|
|
585
|
+
it { is_expected.to eq(4) }
|
|
586
|
+
end
|
|
587
|
+
end
|
|
588
|
+
|
|
589
|
+
context "ending on a holiday" do
|
|
590
|
+
context "including only business days & holidays" do
|
|
591
|
+
let(:date_1) { "Mon 9/6/2014" }
|
|
592
|
+
let(:date_2) { "Thu 12/6/2014" }
|
|
593
|
+
|
|
594
|
+
it { is_expected.to eq(3) }
|
|
595
|
+
end
|
|
596
|
+
|
|
597
|
+
context "including business, weekend days, and holidays" do
|
|
598
|
+
let(:date_2) { "Thu 12/6/2014" }
|
|
599
|
+
|
|
600
|
+
it { is_expected.to eq(8) }
|
|
601
|
+
end
|
|
602
|
+
|
|
603
|
+
context "including business, weekend, holiday days & business date" do
|
|
604
|
+
let(:date_1) { "Wed 28/5/2014" }
|
|
605
|
+
let(:date_2) { "Thu 12/6/2014" }
|
|
606
|
+
|
|
607
|
+
it { is_expected.to eq(11) }
|
|
608
|
+
end
|
|
609
|
+
end
|
|
610
|
+
|
|
611
|
+
context "ending on a working date" do
|
|
612
|
+
let(:date_1) { "Fri 4/7/2014" }
|
|
613
|
+
|
|
614
|
+
context "including only business days & working date" do
|
|
615
|
+
let(:date_2) { "Sat 5/7/2014" }
|
|
616
|
+
|
|
617
|
+
it { is_expected.to eq(1) }
|
|
618
|
+
end
|
|
619
|
+
|
|
620
|
+
context "including business, weekend days & working date" do
|
|
621
|
+
let(:date_2) { "Tue 8/7/2014" }
|
|
622
|
+
|
|
623
|
+
it { is_expected.to eq(3) }
|
|
624
|
+
end
|
|
625
|
+
|
|
626
|
+
context "including business, weekend days, holidays & working date" do
|
|
627
|
+
let(:date_1) { "Wed 25/6/2014" }
|
|
628
|
+
let(:date_2) { "Tue 8/7/2014" }
|
|
629
|
+
|
|
630
|
+
it { is_expected.to eq(8) }
|
|
631
|
+
end
|
|
632
|
+
end
|
|
633
|
+
end
|
|
634
|
+
|
|
635
|
+
context "starting on a weekend" do
|
|
636
|
+
let(:date_1) { "Sat 7/6/2014" }
|
|
637
|
+
|
|
638
|
+
context "ending on a business day" do
|
|
639
|
+
context "including only business days & weekend days" do
|
|
640
|
+
let(:date_2) { "Mon 9/6/2014" }
|
|
641
|
+
|
|
642
|
+
it { is_expected.to eq(0) }
|
|
643
|
+
end
|
|
644
|
+
|
|
645
|
+
context "including business, weekend days & working date" do
|
|
646
|
+
let(:date_1) { "Sat 31/5/2014" }
|
|
647
|
+
let(:date_2) { "Tue 3/6/2014" }
|
|
648
|
+
|
|
649
|
+
it { is_expected.to eq(2) }
|
|
650
|
+
end
|
|
651
|
+
|
|
652
|
+
context "including business, weekend days, and holidays" do
|
|
653
|
+
let(:date_2) { "Fri 13/6/2014" }
|
|
654
|
+
|
|
655
|
+
it { is_expected.to eq(3) }
|
|
656
|
+
end
|
|
657
|
+
|
|
658
|
+
context "including business, weekend, holilday days & working date" do
|
|
659
|
+
let(:date_1) { "Sat 31/5/2014" }
|
|
660
|
+
let(:date_2) { "Fri 13/6/2014" }
|
|
661
|
+
|
|
662
|
+
it { is_expected.to eq(8) }
|
|
663
|
+
end
|
|
664
|
+
end
|
|
665
|
+
|
|
666
|
+
context "ending on a weekend day" do
|
|
667
|
+
context "including only business days & weekend days" do
|
|
668
|
+
let(:date_2) { "Sun 8/6/2014" }
|
|
669
|
+
|
|
670
|
+
it { is_expected.to eq(0) }
|
|
671
|
+
end
|
|
672
|
+
|
|
673
|
+
context "including business, weekend days & working date" do
|
|
674
|
+
let(:date_1) { "Sat 31/5/2014" }
|
|
675
|
+
let(:date_2) { "Sun 8/6/2014" }
|
|
676
|
+
|
|
677
|
+
it { is_expected.to be(5) }
|
|
678
|
+
end
|
|
679
|
+
|
|
680
|
+
context "including business, weekend days, and holidays" do
|
|
681
|
+
let(:date_2) { "Sat 14/6/2014" }
|
|
682
|
+
|
|
683
|
+
it { is_expected.to eq(4) }
|
|
684
|
+
end
|
|
685
|
+
|
|
686
|
+
context "including business, weekend, holiday days & working date" do
|
|
687
|
+
let(:date_1) { "Sat 31/5/2014" }
|
|
688
|
+
let(:date_2) { "Sun 14/6/2014" }
|
|
689
|
+
|
|
690
|
+
it { is_expected.to be(9) }
|
|
691
|
+
end
|
|
692
|
+
end
|
|
693
|
+
|
|
694
|
+
context "ending on a holiday" do
|
|
695
|
+
context "including business, weekend days, and holidays" do
|
|
696
|
+
let(:date_2) { "Thu 12/6/2014" }
|
|
697
|
+
|
|
698
|
+
it { is_expected.to eq(3) }
|
|
699
|
+
end
|
|
700
|
+
|
|
701
|
+
context "including business, weekend days & working date" do
|
|
702
|
+
let(:date_1) { "Sat 31/5/2014" }
|
|
703
|
+
let(:date_2) { "Thu 12/6/2014" }
|
|
704
|
+
|
|
705
|
+
it { is_expected.to eq(8) }
|
|
706
|
+
end
|
|
707
|
+
end
|
|
708
|
+
|
|
709
|
+
context "ending on a working date" do
|
|
710
|
+
let(:date_1) { "Sat 31/5/2014" }
|
|
711
|
+
|
|
712
|
+
context "including only weekend days & working date" do
|
|
713
|
+
let(:date_2) { "Sat 2/6/2014" }
|
|
714
|
+
|
|
715
|
+
it { is_expected.to eq(1) }
|
|
716
|
+
end
|
|
717
|
+
|
|
718
|
+
context "including business, weekend days & working date" do
|
|
719
|
+
let(:date_2) { "Tue 4/6/2014" }
|
|
720
|
+
|
|
721
|
+
it { is_expected.to eq(3) }
|
|
722
|
+
end
|
|
723
|
+
|
|
724
|
+
context "including business, weekend days, holidays & working date" do
|
|
725
|
+
let(:date_2) { "Tue 13/6/2014" }
|
|
726
|
+
|
|
727
|
+
it { is_expected.to eq(8) }
|
|
728
|
+
end
|
|
729
|
+
end
|
|
730
|
+
end
|
|
731
|
+
|
|
732
|
+
context "starting on a holiday" do
|
|
733
|
+
let(:date_1) { "Thu 12/6/2014" }
|
|
734
|
+
|
|
735
|
+
context "ending on a business day" do
|
|
736
|
+
context "including only business days & holidays" do
|
|
737
|
+
let(:date_2) { "Fri 13/6/2014" }
|
|
738
|
+
|
|
739
|
+
it { is_expected.to eq(0) }
|
|
740
|
+
end
|
|
741
|
+
|
|
742
|
+
context "including business, weekend days, and holidays" do
|
|
743
|
+
let(:date_2) { "Thu 19/6/2014" }
|
|
744
|
+
|
|
745
|
+
it { is_expected.to eq(3) }
|
|
746
|
+
end
|
|
747
|
+
|
|
748
|
+
context "including business, weekend days, holidays & working date" do
|
|
749
|
+
let(:date_1) { "Fri 27/6/2014" }
|
|
750
|
+
let(:date_2) { "Tue 1/7/2014" }
|
|
751
|
+
|
|
752
|
+
it { is_expected.to eq(2) }
|
|
753
|
+
end
|
|
754
|
+
end
|
|
755
|
+
|
|
756
|
+
context "ending on a weekend day" do
|
|
757
|
+
context "including business, weekend days, and holidays" do
|
|
758
|
+
let(:date_2) { "Sun 15/6/2014" }
|
|
759
|
+
|
|
760
|
+
it { is_expected.to eq(1) }
|
|
761
|
+
end
|
|
762
|
+
|
|
763
|
+
context "including business, weekend days, holidays & working date" do
|
|
764
|
+
let(:date_1) { "Fri 27/6/2014" }
|
|
765
|
+
let(:date_2) { "Sun 29/6/2014" }
|
|
766
|
+
|
|
767
|
+
it { is_expected.to eq(1) }
|
|
768
|
+
end
|
|
769
|
+
end
|
|
770
|
+
|
|
771
|
+
context "ending on a holiday" do
|
|
772
|
+
context "including only business days & holidays" do
|
|
773
|
+
let(:date_1) { "Wed 18/6/2014" }
|
|
774
|
+
let(:date_2) { "Fri 20/6/2014" }
|
|
775
|
+
|
|
776
|
+
it { is_expected.to eq(1) }
|
|
777
|
+
end
|
|
778
|
+
|
|
779
|
+
context "including business, weekend days, and holidays" do
|
|
780
|
+
let(:date_2) { "Wed 18/6/2014" }
|
|
781
|
+
|
|
782
|
+
it { is_expected.to eq(3) }
|
|
783
|
+
end
|
|
784
|
+
|
|
785
|
+
context "including business/weekend days, holidays & working date" do
|
|
786
|
+
let(:date_1) { "27/5/2014" }
|
|
787
|
+
let(:date_2) { "Thu 12/6/2014" }
|
|
788
|
+
|
|
789
|
+
it { is_expected.to eq(11) }
|
|
790
|
+
end
|
|
791
|
+
end
|
|
792
|
+
|
|
793
|
+
context "ending on a working date" do
|
|
794
|
+
let(:date_1) { "Sat 27/6/2014" }
|
|
795
|
+
|
|
796
|
+
context "including only holiday & working date" do
|
|
797
|
+
let(:date_2) { "Sat 29/6/2014" }
|
|
798
|
+
|
|
799
|
+
it { is_expected.to eq(1) }
|
|
800
|
+
end
|
|
801
|
+
|
|
802
|
+
context "including holiday, weekend days & working date" do
|
|
803
|
+
let(:date_2) { "Tue 30/6/2014" }
|
|
804
|
+
|
|
805
|
+
it { is_expected.to eq(1) }
|
|
806
|
+
end
|
|
807
|
+
|
|
808
|
+
context "including business, weekend days, holidays & working date" do
|
|
809
|
+
let(:date_2) { "Tue 2/7/2014" }
|
|
810
|
+
|
|
811
|
+
it { is_expected.to eq(3) }
|
|
812
|
+
end
|
|
813
|
+
end
|
|
814
|
+
end
|
|
815
|
+
|
|
816
|
+
context "starting on a working date" do
|
|
817
|
+
let(:date_1) { "Sun 1/6/2014" }
|
|
818
|
+
|
|
819
|
+
context "ending on a working day" do
|
|
820
|
+
context "including only working date & working day" do
|
|
821
|
+
let(:date_2) { "Wed 4/6/2014" }
|
|
822
|
+
|
|
823
|
+
it { is_expected.to eq(3) }
|
|
824
|
+
end
|
|
825
|
+
|
|
826
|
+
context "including working date, working & weekend days" do
|
|
827
|
+
let(:date_2) { "Tue 10/6/2014" }
|
|
828
|
+
|
|
829
|
+
it { is_expected.to eq(6) }
|
|
830
|
+
end
|
|
831
|
+
|
|
832
|
+
context "including working date, working & weekend days & holiday" do
|
|
833
|
+
let(:date_2) { "Tue 13/6/2014" }
|
|
834
|
+
|
|
835
|
+
it { is_expected.to eq(8) }
|
|
836
|
+
end
|
|
837
|
+
end
|
|
838
|
+
|
|
839
|
+
context "ending on a weekend day" do
|
|
840
|
+
let(:date_1) { "Sat 28/6/2014" }
|
|
841
|
+
|
|
842
|
+
context "including only working date & weekend day" do
|
|
843
|
+
let(:date_2) { "Sun 29/6/2014" }
|
|
844
|
+
|
|
845
|
+
it { is_expected.to eq(1) }
|
|
846
|
+
end
|
|
847
|
+
|
|
848
|
+
context "including working date, weekend & working days" do
|
|
849
|
+
let(:date_1) { "Sat 5/7/2014" }
|
|
850
|
+
let(:date_2) { "Wed 9/7/2014" }
|
|
851
|
+
|
|
852
|
+
it { is_expected.to eq(3) }
|
|
853
|
+
end
|
|
854
|
+
|
|
855
|
+
context "including working date, weekend & working days & holiday" do
|
|
856
|
+
let(:date_2) { "Fri 4/7/2014" }
|
|
857
|
+
|
|
858
|
+
it { is_expected.to eq(4) }
|
|
859
|
+
end
|
|
860
|
+
end
|
|
861
|
+
|
|
862
|
+
context "ending on a holiday" do
|
|
863
|
+
let(:date_1) { "Sat 28/6/2014" }
|
|
864
|
+
|
|
865
|
+
context "including only working date & holiday" do
|
|
866
|
+
let(:holidays) { ["Mon 2/6/2014"] }
|
|
867
|
+
let(:date_1) { "Sun 1/6/2014" }
|
|
868
|
+
let(:date_2) { "Mon 2/6/2014" }
|
|
869
|
+
|
|
870
|
+
it { is_expected.to eq(1) }
|
|
871
|
+
end
|
|
872
|
+
|
|
873
|
+
context "including working date, holiday & weekend day" do
|
|
874
|
+
let(:holidays) { ["Mon 30/6/2014"] }
|
|
875
|
+
let(:date_2) { "Mon 30/6/2014" }
|
|
876
|
+
|
|
877
|
+
it { is_expected.to eq(1) }
|
|
878
|
+
end
|
|
879
|
+
|
|
880
|
+
context "including working date, holiday, weekend & working days" do
|
|
881
|
+
let(:date_2) { "Thu 3/7/2014" }
|
|
882
|
+
|
|
883
|
+
it { is_expected.to eq(4) }
|
|
884
|
+
end
|
|
885
|
+
end
|
|
886
|
+
|
|
887
|
+
context "ending on a working date" do
|
|
888
|
+
context "including working dates, weekend & working days" do
|
|
889
|
+
let(:date_1) { "Sat 28/6/2014" }
|
|
890
|
+
let(:date_2) { "Sat 5/7/2014" }
|
|
891
|
+
|
|
892
|
+
it { is_expected.to eq(4) }
|
|
893
|
+
end
|
|
894
|
+
end
|
|
895
|
+
end
|
|
896
|
+
|
|
897
|
+
context "if a calendar has a holiday on a non-working (weekend) day" do
|
|
898
|
+
context "for a range less than a week long" do
|
|
899
|
+
let(:date_1) { "Thu 19/6/2014" }
|
|
900
|
+
let(:date_2) { "Tue 24/6/2014" }
|
|
901
|
+
|
|
902
|
+
it { is_expected.to eq(2) }
|
|
903
|
+
end
|
|
904
|
+
|
|
905
|
+
context "for a range more than a week long" do
|
|
906
|
+
let(:date_1) { "Mon 16/6/2014" }
|
|
907
|
+
let(:date_2) { "Tue 24/6/2014" }
|
|
908
|
+
|
|
909
|
+
it { is_expected.to eq(4) }
|
|
910
|
+
end
|
|
911
|
+
end
|
|
912
|
+
end
|
|
913
|
+
end
|
|
914
|
+
|
|
915
|
+
context "(using Date objects)" do
|
|
916
|
+
let(:date_class) { Date }
|
|
917
|
+
let(:day_interval) { 1 }
|
|
918
|
+
|
|
919
|
+
it_behaves_like "common"
|
|
920
|
+
end
|
|
921
|
+
|
|
922
|
+
context "(using Time objects)" do
|
|
923
|
+
let(:date_class) { Time }
|
|
924
|
+
let(:day_interval) { 3600 * 24 }
|
|
925
|
+
|
|
926
|
+
it_behaves_like "common"
|
|
927
|
+
end
|
|
928
|
+
|
|
929
|
+
context "(using DateTime objects)" do
|
|
930
|
+
let(:date_class) { DateTime }
|
|
931
|
+
let(:day_interval) { 1 }
|
|
932
|
+
|
|
933
|
+
it_behaves_like "common"
|
|
934
|
+
end
|
|
935
|
+
end
|