business_calendar 0.0.4
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/.rspec +1 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +55 -0
- data/Rakefile +1 -0
- data/business_calendar.gemspec +27 -0
- data/data/holidays.yml +38 -0
- data/lib/business_calendar.rb +27 -0
- data/lib/business_calendar/calendar.rb +111 -0
- data/lib/business_calendar/holiday_determiner.rb +24 -0
- data/lib/business_calendar/version.rb +3 -0
- data/spec/acceptance_gb_spec.rb +34 -0
- data/spec/acceptance_us_spec.rb +48 -0
- data/spec/business_calendar/holiday_determiner_spec.rb +36 -0
- data/spec/business_calendar_spec.rb +109 -0
- data/spec/spec_helper.rb +19 -0
- metadata +137 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: d7f0e2f419f6a231337604c75979a9a8621e07df
|
4
|
+
data.tar.gz: 7c569d7e47f024718fbf2810b28f3a14bdf854bd
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 3a003dc3a36d6d9fc25743e4f0387ce7b738a11515efa50e8f5badb31af4fb0dea912b69debfd9bb49897f49ba0e83472d17958af157ce86859b43c61f20eaab
|
7
|
+
data.tar.gz: 6e0c666edabf29fd49a7d442c98b40133da419bf56b9571c85b110025a8a4ede613bb38c0047cded18a819f155ee67416c587bbbffb427070bf4bb9ffd548d1b
|
data/.gitignore
ADDED
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--colour
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 Robert Nubel
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
# BusinessCalendar
|
2
|
+
|
3
|
+
Need to know what days you can actually debit a customer on in excruciating detail? Fed up with singleton-based gems
|
4
|
+
that can't handle your complex, multi-international special needs? *So* over extending core objects like Numeric
|
5
|
+
and Date just to make your code a little cleaner? Well, do I have the gem for you! **BusinessCalendar** is a
|
6
|
+
reasonably light-weight solution, concerned primarily with banking holidays in different countries.
|
7
|
+
|
8
|
+
## Installation
|
9
|
+
|
10
|
+
Add this line to your application's Gemfile:
|
11
|
+
|
12
|
+
gem 'business_calendar'
|
13
|
+
|
14
|
+
And then execute:
|
15
|
+
|
16
|
+
$ bundle
|
17
|
+
|
18
|
+
## Usage
|
19
|
+
|
20
|
+
### Basic usage
|
21
|
+
|
22
|
+
Instantiate a calendar object with:
|
23
|
+
|
24
|
+
bc = BusinessCalendar.for(:US)
|
25
|
+
|
26
|
+
This will automatically load holidays based on the US banking holiday schedule, as configured in `data/holidays.yml`.
|
27
|
+
Currently, this gem supports `:GB` and `:US` regions.
|
28
|
+
|
29
|
+
Now, you can use it:
|
30
|
+
|
31
|
+
bc.is_business_day? Date.parse('2014-03-10') # => true
|
32
|
+
bc.add_business_day Date.parse('2014-03-10') # => #<Date 2014-03-11>
|
33
|
+
bc.nearest_business_day Date.parse('2014-03-08') # => #<Date 2014-03-10>
|
34
|
+
bc.add_business_days Date.parse('2014-03-10'), 2 # => #<Date 2014-03-12>
|
35
|
+
bc.preceding_business_day Date.parse('2014-03-11') # => #<Date 2014-03-10>
|
36
|
+
|
37
|
+
And in multi-value contexts, too!
|
38
|
+
|
39
|
+
bc.add_business_day [ Date.parse('2014-03-10'), Date.parse('2014-03-11') ]
|
40
|
+
# => [ #<Date 2014-03-11>, #<Date 2014-03-12> ]
|
41
|
+
|
42
|
+
### Non-standard holidays
|
43
|
+
|
44
|
+
If you need a different list of holidays, you can skip the porcelain and create the calendar yourself with a custom holiday-determiner proc:
|
45
|
+
|
46
|
+
holiday_tester = Proc.new { |date| MY_HOLIDAY_DATES.include? date }
|
47
|
+
bc = BusinessCalendar::Calendar.new(holiday_tester)
|
48
|
+
|
49
|
+
## Contributing
|
50
|
+
|
51
|
+
1. Fork it
|
52
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
53
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
54
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
55
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'business_calendar/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "business_calendar"
|
8
|
+
spec.version = BusinessCalendar::VERSION
|
9
|
+
spec.authors = ["Robert Nubel"]
|
10
|
+
spec.email = ["rnubel@enova.com"]
|
11
|
+
spec.description = %q{Helper gem for dealing with business days and date adjustment in multiple countries.}
|
12
|
+
spec.summary = %q{Country-aware business-date logic and handling.}
|
13
|
+
spec.homepage = ""
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files`.split($/)
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_dependency "holidays", "~> 1.0"
|
22
|
+
|
23
|
+
spec.add_development_dependency "bundler", "~> 1.3"
|
24
|
+
spec.add_development_dependency "rake"
|
25
|
+
spec.add_development_dependency "rspec"
|
26
|
+
spec.add_development_dependency "timecop"
|
27
|
+
end
|
data/data/holidays.yml
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
US:
|
2
|
+
regions:
|
3
|
+
- 'us'
|
4
|
+
holiday_names:
|
5
|
+
- "New Year's Day"
|
6
|
+
- "Martin Luther King, Jr. Day"
|
7
|
+
- "Presidents' Day"
|
8
|
+
- "Memorial Day"
|
9
|
+
- "Independence Day"
|
10
|
+
- "Labor Day"
|
11
|
+
- "Columbus Day"
|
12
|
+
- "Veterans Day"
|
13
|
+
- "Thanksgiving"
|
14
|
+
- "Christmas Day"
|
15
|
+
|
16
|
+
GB:
|
17
|
+
regions:
|
18
|
+
- 'gb'
|
19
|
+
- 'gb_eng'
|
20
|
+
holiday_names:
|
21
|
+
- "Good Friday"
|
22
|
+
- "Easter Sunday"
|
23
|
+
- "Easter Monday"
|
24
|
+
- "May Day"
|
25
|
+
- "Bank Holiday"
|
26
|
+
- "Christmas Day"
|
27
|
+
- "Boxing Day"
|
28
|
+
- "New Year's Day"
|
29
|
+
additions:
|
30
|
+
- '2002-06-04' # Summer Banking Holiday
|
31
|
+
- '2012-06-04'
|
32
|
+
removals:
|
33
|
+
- '2002-05-28'
|
34
|
+
- '2012-05-28'
|
35
|
+
|
36
|
+
CN:
|
37
|
+
regions: []
|
38
|
+
holiday_names: [] #FIXME
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module BusinessCalendar
|
2
|
+
CountryNotSupported = Class.new(StandardError)
|
3
|
+
|
4
|
+
def self.for(country)
|
5
|
+
Calendar.new(holiday_determiner(country))
|
6
|
+
end
|
7
|
+
|
8
|
+
private
|
9
|
+
def self.holiday_determiner(country)
|
10
|
+
cfg = config(country) or raise CountryNotSupported.new(country.inspect)
|
11
|
+
@holiday_procs ||= {}
|
12
|
+
@holiday_procs[country] ||= HolidayDeterminer.new(
|
13
|
+
cfg["regions"],
|
14
|
+
cfg["holiday_names"],
|
15
|
+
:additions => (cfg["additions"] || []).map { |s| Date.parse s },
|
16
|
+
:removals => (cfg["removals"] || []).map { |s| Date.parse s } )
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.config(country)
|
20
|
+
@config ||= YAML.load_file(File.join(File.dirname(File.expand_path(__FILE__)), '../data/holidays.yml'))
|
21
|
+
@config[country.to_s]
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
require 'yaml'
|
26
|
+
require 'business_calendar/calendar'
|
27
|
+
require 'business_calendar/holiday_determiner'
|
@@ -0,0 +1,111 @@
|
|
1
|
+
class BusinessCalendar::Calendar
|
2
|
+
attr_reader :holiday_determiner
|
3
|
+
|
4
|
+
# @param [Proc[Date -> Boolean]] a proc which returns whether or not a date is a
|
5
|
+
# holiday.
|
6
|
+
def initialize(holiday_determiner)
|
7
|
+
@holiday_determiner = holiday_determiner
|
8
|
+
end
|
9
|
+
|
10
|
+
# @param [Date] date
|
11
|
+
# @return [Boolean] Whether or not this calendar's list of holidays includes <date>.
|
12
|
+
def is_holiday?(date)
|
13
|
+
holiday_determiner.call(date)
|
14
|
+
end
|
15
|
+
|
16
|
+
# @param [Date] date
|
17
|
+
# @return [Boolean] Whether or not banking can be done on <date>.
|
18
|
+
def is_business_day?(date)
|
19
|
+
return false if date.saturday? || date.sunday?
|
20
|
+
return false if is_holiday?(date)
|
21
|
+
true
|
22
|
+
end
|
23
|
+
|
24
|
+
# @param [Date or Array] date_or_dates The day(s) to start from.
|
25
|
+
# @param [Integer] num Number of days to advance. If negative,
|
26
|
+
# this will call `subtract_business_days`.
|
27
|
+
# @param [Symbol] direction Either `:forward` or `:backward`; the
|
28
|
+
# direction to move if today is not a
|
29
|
+
# business day.
|
30
|
+
# @return [Date] The result of adding <num> business days to <date>.
|
31
|
+
# @note 'Adding a business day' means moving one full business day from the
|
32
|
+
# start of the given day -- meaning that adding one business day to a
|
33
|
+
# Saturday will return the following Tuesday, not Monday.
|
34
|
+
def add_business_days(date_or_dates, num=1, initial_direction = :forward)
|
35
|
+
return subtract_business_days(date_or_dates, -num) if num < 0
|
36
|
+
|
37
|
+
with_one_or_many(date_or_dates) do |date|
|
38
|
+
start = nearest_business_day(date, initial_direction)
|
39
|
+
num.times.reduce(start) { |d| following_business_day(d) }
|
40
|
+
end
|
41
|
+
end
|
42
|
+
alias :add_business_day :add_business_days
|
43
|
+
|
44
|
+
# @param [Date or Array] date_or_dates The day(s) to start from.
|
45
|
+
# @param [Integer] num Number of days to retreat. If negative,
|
46
|
+
# this will call `add_business_days`.
|
47
|
+
# @return [Date] The business date to which adding <num> business dats
|
48
|
+
# would arrive at <date>.
|
49
|
+
def subtract_business_days(date_or_dates, num=1)
|
50
|
+
return add_business_days(date_or_dates, -num) if num < 0
|
51
|
+
|
52
|
+
with_one_or_many(date_or_dates) do |date|
|
53
|
+
num.times.reduce(date) { |d| preceding_business_day(d) }
|
54
|
+
end
|
55
|
+
end
|
56
|
+
alias :subtract_business_day :subtract_business_days
|
57
|
+
|
58
|
+
# @param [Date or Array] date_or_dates The day(s) to start from.
|
59
|
+
# @return [Date] The business day immediately prior to <date>.
|
60
|
+
def preceding_business_day(date_or_dates)
|
61
|
+
with_one_or_many(date_or_dates) do |date|
|
62
|
+
begin
|
63
|
+
date = date - 1
|
64
|
+
end until is_business_day? date
|
65
|
+
date
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
# @param [Date or Array] date_or_dates The day(s) to start from.
|
70
|
+
# @param [Symbol] direction Either `:forward` or `:backward`; the
|
71
|
+
# direction to move if today is not a
|
72
|
+
# business day.
|
73
|
+
# @return [Date] The nearest (going <direction> in time) business day from
|
74
|
+
# <date>. If <date> is already a business day, it will be returned.
|
75
|
+
def nearest_business_day(date_or_dates, direction = :forward)
|
76
|
+
with_one_or_many(date_or_dates) do |date|
|
77
|
+
until is_business_day? date
|
78
|
+
date += case direction
|
79
|
+
when :forward
|
80
|
+
1
|
81
|
+
when :backward
|
82
|
+
-1
|
83
|
+
else
|
84
|
+
raise ArgumentError, "Invalid direction supplied: '#{direction}' should instead be :forward or :backward"
|
85
|
+
end
|
86
|
+
end
|
87
|
+
date
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
# @param [Date or Array] date_or_dates The day(s) to start from.
|
92
|
+
# @return [Date] The nearest (going forward in time) business day from <date>
|
93
|
+
# which is not <date> itself.
|
94
|
+
def following_business_day(date_or_dates)
|
95
|
+
with_one_or_many(date_or_dates) do |date|
|
96
|
+
nearest_business_day(date + 1)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
private
|
101
|
+
def with_one_or_many(thing_or_things)
|
102
|
+
is_array = thing_or_things.is_a? Enumerable
|
103
|
+
things = is_array ? thing_or_things : [thing_or_things]
|
104
|
+
|
105
|
+
results = things.collect do |thing|
|
106
|
+
yield thing
|
107
|
+
end
|
108
|
+
|
109
|
+
return is_array ? results : results.first
|
110
|
+
end
|
111
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'holidays'
|
2
|
+
|
3
|
+
class BusinessCalendar::HolidayDeterminer
|
4
|
+
attr_reader :regions, :holiday_names, :additions, :removals
|
5
|
+
|
6
|
+
def initialize(regions, holiday_names, opts = {})
|
7
|
+
@regions = regions
|
8
|
+
@holiday_names = holiday_names
|
9
|
+
@additions = opts[:additions] || []
|
10
|
+
@removals = opts[:removals] || []
|
11
|
+
end
|
12
|
+
|
13
|
+
def call(date)
|
14
|
+
if additions.include? date
|
15
|
+
true
|
16
|
+
elsif removals.include? date
|
17
|
+
false
|
18
|
+
else
|
19
|
+
Holidays.between(date, date, @regions, :observed)
|
20
|
+
.select { |h| @holiday_names.include? h[:name] }
|
21
|
+
.size > 0
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe "GB bank holidays" do
|
4
|
+
%w(
|
5
|
+
2012-06-04
|
6
|
+
2014-04-18
|
7
|
+
2014-04-21
|
8
|
+
2014-05-05
|
9
|
+
2014-05-26
|
10
|
+
2014-08-25
|
11
|
+
2014-12-25
|
12
|
+
2014-12-26
|
13
|
+
2015-01-01
|
14
|
+
2015-04-03
|
15
|
+
2015-04-06
|
16
|
+
2015-05-04
|
17
|
+
2015-05-25
|
18
|
+
2015-08-31
|
19
|
+
2015-12-25
|
20
|
+
2015-12-28
|
21
|
+
).map { |x| Date.parse x }.each do |expected_holiday|
|
22
|
+
it "treats #{expected_holiday} as a holiday" do
|
23
|
+
BusinessCalendar.for(:GB).is_holiday?(expected_holiday).should be_true
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
%w(
|
28
|
+
2012-05-28
|
29
|
+
).map { |x| Date.parse x }.each do |date|
|
30
|
+
it "treats #{date} as not a holiday" do
|
31
|
+
BusinessCalendar.for(:GB).is_holiday?(date).should be_false
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe "US holidays" do
|
4
|
+
%w(
|
5
|
+
2014-05-26
|
6
|
+
2014-07-04
|
7
|
+
2014-09-01
|
8
|
+
2014-10-13
|
9
|
+
2014-11-11
|
10
|
+
2014-11-27
|
11
|
+
2014-12-25
|
12
|
+
2015-01-01
|
13
|
+
2015-01-19
|
14
|
+
2015-02-16
|
15
|
+
2015-05-25
|
16
|
+
2015-07-03
|
17
|
+
2015-09-07
|
18
|
+
2015-10-12
|
19
|
+
2015-11-11
|
20
|
+
2015-11-26
|
21
|
+
2015-12-25
|
22
|
+
2016-01-01
|
23
|
+
2016-01-18
|
24
|
+
2016-02-15
|
25
|
+
2016-05-30
|
26
|
+
2016-07-04
|
27
|
+
2016-09-05
|
28
|
+
2016-10-10
|
29
|
+
2016-11-11
|
30
|
+
2016-11-24
|
31
|
+
2016-12-26
|
32
|
+
2017-01-02
|
33
|
+
2017-01-16
|
34
|
+
2017-02-20
|
35
|
+
2017-05-29
|
36
|
+
2017-07-04
|
37
|
+
2017-09-04
|
38
|
+
2017-10-09
|
39
|
+
2017-11-10
|
40
|
+
2017-11-23
|
41
|
+
2017-12-25
|
42
|
+
2018-01-01
|
43
|
+
).map { |x| Date.parse x }.each do |expected_holiday|
|
44
|
+
it "treats #{expected_holiday} as a holiday" do
|
45
|
+
BusinessCalendar.for(:US).is_holiday?(expected_holiday).should be_true
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe BusinessCalendar::HolidayDeterminer do
|
4
|
+
let(:regions) { [ :us ] }
|
5
|
+
let(:opts) {{}}
|
6
|
+
subject { BusinessCalendar::HolidayDeterminer.new(regions, ["Independence Day"], opts) }
|
7
|
+
|
8
|
+
it "initializes with list of regions and a list of accepted holidays" do
|
9
|
+
subject.regions.should == regions
|
10
|
+
subject.holiday_names.should == ["Independence Day"]
|
11
|
+
end
|
12
|
+
|
13
|
+
it "knows which dates are holidays" do
|
14
|
+
# check for Independence Day's presence
|
15
|
+
(Date.today.year - 1 .. Date.today.year + 10).each do |year|
|
16
|
+
fourth = Date.new year, 7, 4
|
17
|
+
obs_date = fourth.sunday? ? fourth + 1 : (fourth.saturday? ? fourth - 1 : fourth)
|
18
|
+
subject.call(obs_date).should be_true
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
context "with a list of exceptions and additions" do
|
23
|
+
let(:opts) {{
|
24
|
+
:removals => ['2101-07-04'.to_date], # Moved in honor of Great Lizard-Emperor Krall's birthday,
|
25
|
+
:additions => ['2101-07-05'.to_date] # probably.
|
26
|
+
}}
|
27
|
+
|
28
|
+
it "correctly determines false for the exceptions" do
|
29
|
+
subject.call('2101-07-04'.to_date).should be_false
|
30
|
+
end
|
31
|
+
|
32
|
+
it "correctly determines true for the additions" do
|
33
|
+
subject.call('2101-07-05'.to_date).should be_true
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,109 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe BusinessCalendar do
|
4
|
+
before { Timecop.freeze('2014-04-28'.to_date) }
|
5
|
+
subject { BusinessCalendar.for(country) }
|
6
|
+
|
7
|
+
shared_examples_for "standard business time" do
|
8
|
+
specify "a weekend is not a business day" do
|
9
|
+
subject.is_business_day?('2014-03-09'.to_date).should be_false
|
10
|
+
end
|
11
|
+
|
12
|
+
specify "a normal weekday is a business day" do
|
13
|
+
subject.is_business_day?('2014-03-10'.to_date).should be_true
|
14
|
+
end
|
15
|
+
|
16
|
+
specify 'the nearest business day to monday is monday in both directions' do
|
17
|
+
subject.nearest_business_day('2014-03-10'.to_date) .should == '2014-03-10'.to_date
|
18
|
+
subject.nearest_business_day('2014-03-10'.to_date, :backward) .should == '2014-03-10'.to_date
|
19
|
+
subject.nearest_business_day('2014-03-10'.to_date, :forward) .should == '2014-03-10'.to_date
|
20
|
+
end
|
21
|
+
|
22
|
+
specify 'the nearest business day to a saturday, forward, is monday' do
|
23
|
+
subject.nearest_business_day('2014-03-08'.to_date) .should == '2014-03-10'.to_date
|
24
|
+
subject.nearest_business_day('2014-03-08'.to_date, :forward).should == '2014-03-10'.to_date
|
25
|
+
end
|
26
|
+
|
27
|
+
specify 'the nearest business day to a saturday, backward, is friday' do
|
28
|
+
subject.nearest_business_day('2014-03-08'.to_date, :backward).should == '2014-03-07'.to_date
|
29
|
+
end
|
30
|
+
|
31
|
+
specify 'one business day added to a monday is the next day' do
|
32
|
+
subject.add_business_day('2014-03-10'.to_date).should == '2014-03-11'.to_date
|
33
|
+
end
|
34
|
+
|
35
|
+
specify 'one business day added to a friday is the coming monday' do
|
36
|
+
subject.add_business_day('2014-03-07'.to_date).should == '2014-03-10'.to_date
|
37
|
+
end
|
38
|
+
|
39
|
+
specify 'one business day added to a weekend is the coming tuesday' do
|
40
|
+
subject.add_business_day('2014-03-08'.to_date).should == '2014-03-11'.to_date
|
41
|
+
end
|
42
|
+
|
43
|
+
specify 'one business day added to a monday as well as a tuesday is tuesday and wednesday' do
|
44
|
+
d1, d2 = subject.add_business_days ['2014-03-10'.to_date, '2014-03-11'.to_date]
|
45
|
+
d1.should == '2014-03-11'.to_date
|
46
|
+
d2.should == '2014-03-12'.to_date
|
47
|
+
end
|
48
|
+
|
49
|
+
specify 'the following business day of a saturday is the coming monday' do
|
50
|
+
subject.following_business_day('2014-03-08'.to_date).should == '2014-03-10'.to_date
|
51
|
+
end
|
52
|
+
|
53
|
+
specify 'a saturday plus zero business days is the nearest business day, monday' do
|
54
|
+
subject.add_business_days('2014-03-08'.to_date, 0).should == '2014-03-10'.to_date
|
55
|
+
end
|
56
|
+
|
57
|
+
specify 'a saturday plus zero business days with initial direction backward is the nearest preceding business day, friday' do
|
58
|
+
subject.add_business_days('2014-03-08'.to_date, 0, :backward).should == '2014-03-07'.to_date
|
59
|
+
end
|
60
|
+
|
61
|
+
specify 'the following business day of a monday is tuesday' do
|
62
|
+
subject.following_business_day('2014-03-10'.to_date).should == '2014-03-11'.to_date
|
63
|
+
end
|
64
|
+
|
65
|
+
specify 'the preceding business day of a monday is friday' do
|
66
|
+
subject.preceding_business_day('2014-03-10'.to_date).should == '2014-03-07'.to_date
|
67
|
+
end
|
68
|
+
|
69
|
+
specify 'a monday less three business days is the previous wednesday' do
|
70
|
+
subject.add_business_days('2014-03-10'.to_date, -3).should == '2014-03-05'.to_date
|
71
|
+
end
|
72
|
+
|
73
|
+
specify 'a saturday less one business day is the previous friday' do
|
74
|
+
subject.subtract_business_day('2014-03-08'.to_date).should == '2014-03-07'.to_date
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
context "in the US" do
|
79
|
+
let(:country) { :US }
|
80
|
+
|
81
|
+
it_behaves_like "standard business time"
|
82
|
+
|
83
|
+
specify "American Independence Day is not a business day" do
|
84
|
+
subject.is_business_day?('2014-07-04'.to_date).should be_false
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
context "in the UK" do
|
89
|
+
let(:country) { :GB }
|
90
|
+
|
91
|
+
it_behaves_like "standard business time"
|
92
|
+
|
93
|
+
specify "American Independence Day is a business day" do
|
94
|
+
subject.is_business_day?('2014-07-04'.to_date).should be_true
|
95
|
+
end
|
96
|
+
|
97
|
+
specify 'Boxing Day is observed on a weekday' do
|
98
|
+
subject.is_business_day?('2015-12-28'.to_date).should be_false
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
context "in an unknown country" do
|
103
|
+
let(:country) { :GBXX }
|
104
|
+
|
105
|
+
it "raises an informative error when building the calendar" do
|
106
|
+
expect { subject }.to raise_error BusinessCalendar::CountryNotSupported
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'bundler/setup'
|
2
|
+
Bundler.setup
|
3
|
+
|
4
|
+
require 'business_calendar'
|
5
|
+
require 'date'
|
6
|
+
require 'timecop'
|
7
|
+
|
8
|
+
# I'm not depending on ActiveSupport just for this.
|
9
|
+
class String
|
10
|
+
def to_date
|
11
|
+
Date.parse self
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
class Date
|
16
|
+
def inspect
|
17
|
+
"#<Date #{strftime("%Y-%m-%d")}>"
|
18
|
+
end
|
19
|
+
end
|
metadata
ADDED
@@ -0,0 +1,137 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: business_calendar
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.4
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Robert Nubel
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-03-31 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: holidays
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: bundler
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1.3'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1.3'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rake
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rspec
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: timecop
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
description: Helper gem for dealing with business days and date adjustment in multiple
|
84
|
+
countries.
|
85
|
+
email:
|
86
|
+
- rnubel@enova.com
|
87
|
+
executables: []
|
88
|
+
extensions: []
|
89
|
+
extra_rdoc_files: []
|
90
|
+
files:
|
91
|
+
- ".gitignore"
|
92
|
+
- ".rspec"
|
93
|
+
- Gemfile
|
94
|
+
- LICENSE.txt
|
95
|
+
- README.md
|
96
|
+
- Rakefile
|
97
|
+
- business_calendar.gemspec
|
98
|
+
- data/holidays.yml
|
99
|
+
- lib/business_calendar.rb
|
100
|
+
- lib/business_calendar/calendar.rb
|
101
|
+
- lib/business_calendar/holiday_determiner.rb
|
102
|
+
- lib/business_calendar/version.rb
|
103
|
+
- spec/acceptance_gb_spec.rb
|
104
|
+
- spec/acceptance_us_spec.rb
|
105
|
+
- spec/business_calendar/holiday_determiner_spec.rb
|
106
|
+
- spec/business_calendar_spec.rb
|
107
|
+
- spec/spec_helper.rb
|
108
|
+
homepage: ''
|
109
|
+
licenses:
|
110
|
+
- MIT
|
111
|
+
metadata: {}
|
112
|
+
post_install_message:
|
113
|
+
rdoc_options: []
|
114
|
+
require_paths:
|
115
|
+
- lib
|
116
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
117
|
+
requirements:
|
118
|
+
- - ">="
|
119
|
+
- !ruby/object:Gem::Version
|
120
|
+
version: '0'
|
121
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
122
|
+
requirements:
|
123
|
+
- - ">="
|
124
|
+
- !ruby/object:Gem::Version
|
125
|
+
version: '0'
|
126
|
+
requirements: []
|
127
|
+
rubyforge_project:
|
128
|
+
rubygems_version: 2.2.0
|
129
|
+
signing_key:
|
130
|
+
specification_version: 4
|
131
|
+
summary: Country-aware business-date logic and handling.
|
132
|
+
test_files:
|
133
|
+
- spec/acceptance_gb_spec.rb
|
134
|
+
- spec/acceptance_us_spec.rb
|
135
|
+
- spec/business_calendar/holiday_determiner_spec.rb
|
136
|
+
- spec/business_calendar_spec.rb
|
137
|
+
- spec/spec_helper.rb
|