work_days 0.0.3
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.
- data/.gitignore +17 -0
- data/.rspec +2 -0
- data/Gemfile +9 -0
- data/Guardfile +46 -0
- data/LICENSE +22 -0
- data/README.md +63 -0
- data/Rakefile +2 -0
- data/lib/work_days.rb +33 -0
- data/lib/work_days/calculation_methods.rb +103 -0
- data/lib/work_days/ext/date.rb +5 -0
- data/lib/work_days/ext/date_time.rb +5 -0
- data/lib/work_days/ext/range.rb +5 -0
- data/lib/work_days/ext/time.rb +5 -0
- data/lib/work_days/holiday_methods.rb +87 -0
- data/lib/work_days/version.rb +3 -0
- data/lib/work_days/work_schedules/bank.rb +19 -0
- data/lib/work_days/work_schedules/default.rb +17 -0
- data/spec/lib/work_days/calculation_methods_spec.rb +198 -0
- data/spec/lib/work_days/ext/date_spec.rb +5 -0
- data/spec/lib/work_days/ext/datetime_spec.rb +5 -0
- data/spec/lib/work_days/ext/range_spec.rb +12 -0
- data/spec/lib/work_days/ext/time_spec.rb +5 -0
- data/spec/lib/work_days/holiday_methods_spec.rb +38 -0
- data/spec/lib/work_days/work_days_spec.rb +58 -0
- data/spec/lib/work_days/work_schedules/bank_spec.rb +5 -0
- data/spec/lib/work_days/work_schedules/default_spec.rb +5 -0
- data/spec/spec_helper.rb +16 -0
- data/spec/support/holiday_helpers.rb +43 -0
- data/spec/support/shared_work_schedule.rb +21 -0
- data/spec/support/work_day_proxy.rb +13 -0
- data/work_days.gemspec +19 -0
- metadata +107 -0
data/.gitignore
ADDED
data/.rspec
ADDED
data/Gemfile
ADDED
data/Guardfile
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
# A sample Guardfile
|
2
|
+
# More info at https://github.com/guard/guard#readme
|
3
|
+
|
4
|
+
guard 'rspec' do
|
5
|
+
watch(%r{^spec/.+_spec\.rb$})
|
6
|
+
watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
|
7
|
+
watch('spec/spec_helper.rb') { "spec" }
|
8
|
+
|
9
|
+
# Rails example
|
10
|
+
watch(%r{^app/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
|
11
|
+
watch(%r{^app/(.*)(\.erb|\.haml)$}) { |m| "spec/#{m[1]}#{m[2]}_spec.rb" }
|
12
|
+
watch(%r{^app/controllers/(.+)_(controller)\.rb$}) { |m| ["spec/routing/#{m[1]}_routing_spec.rb", "spec/#{m[2]}s/#{m[1]}_#{m[2]}_spec.rb", "spec/acceptance/#{m[1]}_spec.rb"] }
|
13
|
+
watch(%r{^spec/support/(.+)\.rb$}) { "spec" }
|
14
|
+
watch('config/routes.rb') { "spec/routing" }
|
15
|
+
watch('app/controllers/application_controller.rb') { "spec/controllers" }
|
16
|
+
|
17
|
+
# Capybara features specs
|
18
|
+
watch(%r{^app/views/(.+)/.*\.(erb|haml)$}) { |m| "spec/features/#{m[1]}_spec.rb" }
|
19
|
+
|
20
|
+
# Turnip features and steps
|
21
|
+
watch(%r{^spec/acceptance/(.+)\.feature$})
|
22
|
+
watch(%r{^spec/acceptance/steps/(.+)_steps\.rb$}) { |m| Dir[File.join("**/#{m[1]}.feature")][0] || 'spec/acceptance' }
|
23
|
+
end
|
24
|
+
|
25
|
+
|
26
|
+
guard 'rspec' do
|
27
|
+
watch(%r{^spec/.+_spec\.rb$})
|
28
|
+
watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
|
29
|
+
watch('spec/spec_helper.rb') { "spec" }
|
30
|
+
|
31
|
+
# Rails example
|
32
|
+
watch(%r{^app/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
|
33
|
+
watch(%r{^app/(.*)(\.erb|\.haml)$}) { |m| "spec/#{m[1]}#{m[2]}_spec.rb" }
|
34
|
+
watch(%r{^app/controllers/(.+)_(controller)\.rb$}) { |m| ["spec/routing/#{m[1]}_routing_spec.rb", "spec/#{m[2]}s/#{m[1]}_#{m[2]}_spec.rb", "spec/acceptance/#{m[1]}_spec.rb"] }
|
35
|
+
watch(%r{^spec/support/(.+)\.rb$}) { "spec" }
|
36
|
+
watch('config/routes.rb') { "spec/routing" }
|
37
|
+
watch('app/controllers/application_controller.rb') { "spec/controllers" }
|
38
|
+
|
39
|
+
# Capybara features specs
|
40
|
+
watch(%r{^app/views/(.+)/.*\.(erb|haml)$}) { |m| "spec/features/#{m[1]}_spec.rb" }
|
41
|
+
|
42
|
+
# Turnip features and steps
|
43
|
+
watch(%r{^spec/acceptance/(.+)\.feature$})
|
44
|
+
watch(%r{^spec/acceptance/steps/(.+)_steps\.rb$}) { |m| Dir[File.join("**/#{m[1]}.feature")][0] || 'spec/acceptance' }
|
45
|
+
end
|
46
|
+
|
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2012 Robert Jackson
|
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,63 @@
|
|
1
|
+
# WorkDays
|
2
|
+
|
3
|
+
Calculate the number of business days in a given period. Also, add convenience methods to Range, Date, and Time.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
gem 'work_days'
|
10
|
+
|
11
|
+
And then execute:
|
12
|
+
|
13
|
+
$ bundle
|
14
|
+
|
15
|
+
Or install it yourself as:
|
16
|
+
|
17
|
+
$ gem install work_days
|
18
|
+
|
19
|
+
## Usage
|
20
|
+
|
21
|
+
Use your own custom work schedule by creating a class implementing
|
22
|
+
your custom holiday methods and including the WorkDays::CalculationMethods module
|
23
|
+
(and optionally the WorkDays::HolidayMethods module for a few of the presets).
|
24
|
+
|
25
|
+
class SampleSchedule
|
26
|
+
WorkDays::CalculationMethods
|
27
|
+
WorkDays::HolidayMethods
|
28
|
+
|
29
|
+
def observed_holidays
|
30
|
+
[:new_years_day, :christmas_day]
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
Then tell the library to use your new schedule:
|
35
|
+
|
36
|
+
WorkDays.work_schedule = SampleSchedule.new
|
37
|
+
|
38
|
+
You can also use a few pre-built schedules (WorkDays::WorkSchedules::Default, WorkDays::WorkSchedules::Bank).
|
39
|
+
|
40
|
+
By default a day is considered a work day as long as it is a week day and it isn't a holiday.
|
41
|
+
You can configure your own week days by overriding the week_day? method in your work schedule class.
|
42
|
+
|
43
|
+
### Methods
|
44
|
+
|
45
|
+
These methods can be called either on an instance of your work schedule class or directly
|
46
|
+
on the WorkDays module (as long as you set the WorkDays.work_schedule).
|
47
|
+
|
48
|
+
* work_day?(date)
|
49
|
+
* Returns true for week days as long as it isn't a holiday.
|
50
|
+
* non_work_day?(date)
|
51
|
+
* The opposite of work_day?.
|
52
|
+
* work_days_in_range(start_date, end_date)
|
53
|
+
* Returns an array of the work days between the start and end dates.
|
54
|
+
* work_days_in_month(date)
|
55
|
+
* Returns an array of the work days for the year and month of the passed in date.
|
56
|
+
|
57
|
+
## Contributing
|
58
|
+
|
59
|
+
1. Fork it
|
60
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
61
|
+
3. Commit your changes (`git commit -am 'Added some feature'`)
|
62
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
63
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
data/lib/work_days.rb
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'date'
|
2
|
+
|
3
|
+
require_relative 'work_days/version'
|
4
|
+
require_relative 'work_days/calculation_methods'
|
5
|
+
require_relative 'work_days/holiday_methods'
|
6
|
+
require_relative 'work_days/work_schedules/default'
|
7
|
+
require_relative 'work_days/work_schedules/bank'
|
8
|
+
require_relative 'work_days/ext/date'
|
9
|
+
require_relative 'work_days/ext/time'
|
10
|
+
require_relative 'work_days/ext/date_time'
|
11
|
+
require_relative 'work_days/ext/range'
|
12
|
+
|
13
|
+
module WorkDays
|
14
|
+
def self.work_schedule=(schedule)
|
15
|
+
@work_schedule = schedule
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.work_schedule
|
19
|
+
@work_schedule ||= WorkDays::WorkSchedules::Default.new
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.respond_to_missing?(method_name, include_private = false)
|
23
|
+
work_schedule.respond_to?(method_name)
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.method_missing(name, *args, &block)
|
27
|
+
if work_schedule.respond_to?(name)
|
28
|
+
work_schedule.public_send(name, *args, &block)
|
29
|
+
else
|
30
|
+
super
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,103 @@
|
|
1
|
+
module WorkDays::CalculationMethods
|
2
|
+
def holiday?(date)
|
3
|
+
observed_holidays.any? do |sym|
|
4
|
+
next if caller.any?{|c| c =~ /`#{sym.to_s}'\z/}
|
5
|
+
send(sym, date.year) == date
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
def weekend_day?(date)
|
10
|
+
date.sunday? || date.saturday?
|
11
|
+
end
|
12
|
+
|
13
|
+
def week_day?(date)
|
14
|
+
!weekend_day?(date)
|
15
|
+
end
|
16
|
+
|
17
|
+
def work_day?(date)
|
18
|
+
week_day?(date) && !holiday?(date)
|
19
|
+
end
|
20
|
+
|
21
|
+
def non_work_day?(date)
|
22
|
+
!work_day?(date)
|
23
|
+
end
|
24
|
+
|
25
|
+
def work_days_in_range(start, stop)
|
26
|
+
working_days = []
|
27
|
+
|
28
|
+
(start.to_date..stop.to_date).each do |date|
|
29
|
+
working_days << date if work_day?(date)
|
30
|
+
end
|
31
|
+
|
32
|
+
working_days
|
33
|
+
end
|
34
|
+
|
35
|
+
def work_days_in_month(date)
|
36
|
+
start_date = Date.new(date.year, date.month, 1)
|
37
|
+
end_date = Date.new(date.year, date.month, -1)
|
38
|
+
|
39
|
+
work_days_in_range(start_date, end_date)
|
40
|
+
end
|
41
|
+
|
42
|
+
def previous_work_day(date)
|
43
|
+
loop do
|
44
|
+
date = date.to_date.prev_day
|
45
|
+
break if work_day?(date)
|
46
|
+
end
|
47
|
+
|
48
|
+
date
|
49
|
+
end
|
50
|
+
|
51
|
+
def next_work_day(date)
|
52
|
+
loop do
|
53
|
+
date = date.to_date.next_day
|
54
|
+
break if work_day?(date)
|
55
|
+
end
|
56
|
+
|
57
|
+
date
|
58
|
+
end
|
59
|
+
|
60
|
+
def work_days_from(number_of_days, date)
|
61
|
+
number_of_days.times do
|
62
|
+
date = next_work_day(date)
|
63
|
+
end
|
64
|
+
|
65
|
+
date
|
66
|
+
end
|
67
|
+
|
68
|
+
def observed_holidays
|
69
|
+
raise NotImplementedError, 'You must override this method.'
|
70
|
+
end
|
71
|
+
|
72
|
+
def monthly_work_days(year=nil)
|
73
|
+
year = format_year(year)
|
74
|
+
|
75
|
+
(1..12).collect{|month| work_days_in_month(Date.new(year, month))}
|
76
|
+
end
|
77
|
+
|
78
|
+
private
|
79
|
+
|
80
|
+
def weekday_if_weekend(date)
|
81
|
+
date -= 1 if date.saturday?
|
82
|
+
date += 1 if date.sunday?
|
83
|
+
|
84
|
+
date
|
85
|
+
end
|
86
|
+
|
87
|
+
def format_year(year=nil)
|
88
|
+
year ||= Date.today.year
|
89
|
+
year.to_i
|
90
|
+
end
|
91
|
+
|
92
|
+
def day_of_week_occurence(year, month, test, count=nil)
|
93
|
+
year = format_year(year)
|
94
|
+
count ||= 1
|
95
|
+
counter = 0
|
96
|
+
|
97
|
+
1.upto(31).each do |day|
|
98
|
+
date = Date.new(year, month, day)
|
99
|
+
counter += 1 if date.send(test)
|
100
|
+
return date if counter == count
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
require_relative 'calculation_methods'
|
2
|
+
|
3
|
+
module WorkDays::HolidayMethods
|
4
|
+
include WorkDays::CalculationMethods
|
5
|
+
|
6
|
+
def new_years_day(year=nil)
|
7
|
+
year = format_year(year)
|
8
|
+
weekday_if_weekend(Date.new(year,1,1))
|
9
|
+
end
|
10
|
+
|
11
|
+
def martin_luther_king_day(year=nil)
|
12
|
+
year = format_year(year)
|
13
|
+
|
14
|
+
return nil if year < 1986
|
15
|
+
|
16
|
+
day_of_week_occurence(year, 1, :monday?, 3)
|
17
|
+
end
|
18
|
+
|
19
|
+
def presidents_day(year=nil)
|
20
|
+
day_of_week_occurence(year, 2, :monday?, 3)
|
21
|
+
end
|
22
|
+
|
23
|
+
def easter_sunday(year=nil)
|
24
|
+
year = format_year(year)
|
25
|
+
y = year
|
26
|
+
a = y % 19
|
27
|
+
b = y / 100
|
28
|
+
c = y % 100
|
29
|
+
d = b / 4
|
30
|
+
e = b % 4
|
31
|
+
f = (b + 8) / 25
|
32
|
+
g = (b - f + 1) / 3
|
33
|
+
h = (19 * a + b - d - g + 15) % 30
|
34
|
+
i = c / 4
|
35
|
+
k = c % 4
|
36
|
+
l = (32 + 2 * e + 2 * i - h - k) % 7
|
37
|
+
m = (a + 11 * h + 22 * l) / 451
|
38
|
+
month = (h + l - 7 * m + 114) / 31
|
39
|
+
day = ((h + l - 7 * m + 114) % 31) + 1
|
40
|
+
Date.new(year, month, day)
|
41
|
+
end
|
42
|
+
|
43
|
+
def memorial_day(year=nil)
|
44
|
+
year = format_year(year)
|
45
|
+
|
46
|
+
31.downto(1).each do |day|
|
47
|
+
date = Date.new(year, 5, day)
|
48
|
+
return date if date.monday?
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def independence_day(year=nil)
|
53
|
+
year = format_year(year)
|
54
|
+
weekday_if_weekend(Date.new(year,7,4))
|
55
|
+
end
|
56
|
+
|
57
|
+
def labor_day(year=nil)
|
58
|
+
day_of_week_occurence(year, 9, :monday?)
|
59
|
+
end
|
60
|
+
|
61
|
+
def columbus_day(year=nil)
|
62
|
+
day_of_week_occurence(year, 10, :monday?, 2)
|
63
|
+
end
|
64
|
+
|
65
|
+
def veterans_day(year=nil)
|
66
|
+
year = format_year(year)
|
67
|
+
weekday_if_weekend(Date.new(year,11,11))
|
68
|
+
end
|
69
|
+
|
70
|
+
def thanksgiving_day(year=nil)
|
71
|
+
day_of_week_occurence(year, 11, :thursday?, 4)
|
72
|
+
end
|
73
|
+
|
74
|
+
def black_friday(year=nil)
|
75
|
+
thanksgiving_day(year).next_day
|
76
|
+
end
|
77
|
+
|
78
|
+
def christmas_eve_day(year=nil)
|
79
|
+
previous_work_day(christmas_day(year))
|
80
|
+
end
|
81
|
+
|
82
|
+
def christmas_day(year=nil)
|
83
|
+
year = format_year(year)
|
84
|
+
weekday_if_weekend(Date.new(year,12,25))
|
85
|
+
end
|
86
|
+
|
87
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module WorkDays::WorkSchedules
|
2
|
+
class Bank
|
3
|
+
include WorkDays::CalculationMethods
|
4
|
+
include WorkDays::HolidayMethods
|
5
|
+
|
6
|
+
def observed_holidays
|
7
|
+
[ :new_years_day,
|
8
|
+
:martin_luther_king_day,
|
9
|
+
:presidents_day,
|
10
|
+
:memorial_day,
|
11
|
+
:independence_day,
|
12
|
+
:labor_day,
|
13
|
+
:columbus_day,
|
14
|
+
:veterans_day,
|
15
|
+
:thanksgiving_day,
|
16
|
+
:christmas_day ]
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module WorkDays::WorkSchedules
|
2
|
+
class Default
|
3
|
+
include WorkDays::CalculationMethods
|
4
|
+
include WorkDays::HolidayMethods
|
5
|
+
|
6
|
+
def observed_holidays
|
7
|
+
[ :new_years_day,
|
8
|
+
:memorial_day,
|
9
|
+
:independence_day,
|
10
|
+
:labor_day,
|
11
|
+
:thanksgiving_day,
|
12
|
+
:black_friday,
|
13
|
+
:christmas_eve_day,
|
14
|
+
:christmas_day ]
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,198 @@
|
|
1
|
+
require_relative '../../spec_helper'
|
2
|
+
|
3
|
+
describe WorkDays::CalculationMethods, :type => :holiday_helpers do
|
4
|
+
let(:today) {Date.today}
|
5
|
+
let(:current_year) {Date.today.year}
|
6
|
+
|
7
|
+
let(:dummy_class) do
|
8
|
+
Class.new do
|
9
|
+
include(WorkDays::CalculationMethods)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
subject{dummy_class.new}
|
14
|
+
|
15
|
+
context "#weekend_day?" do
|
16
|
+
let(:date) {double('date', :sunday? => false, :saturday? => false)}
|
17
|
+
|
18
|
+
it "should be true for sundays" do
|
19
|
+
date.should_receive(:sunday?).and_return(true)
|
20
|
+
subject.weekend_day?(date).should be_true
|
21
|
+
end
|
22
|
+
|
23
|
+
it "should be true for saturdays" do
|
24
|
+
date.should_receive(:saturday?).and_return(true)
|
25
|
+
subject.weekend_day?(date).should be_true
|
26
|
+
end
|
27
|
+
|
28
|
+
it "should be false for non-saturdays/non-sundays" do
|
29
|
+
date.should_receive(:sunday?).and_return(false)
|
30
|
+
date.should_receive(:saturday?).and_return(false)
|
31
|
+
subject.weekend_day?(date).should be_false
|
32
|
+
end
|
33
|
+
it "should be true for weekday holidays" do
|
34
|
+
all_holiday_dates.each do |date|
|
35
|
+
date = Date.parse(date)
|
36
|
+
weekend_flag = date.saturday? || date.sunday?
|
37
|
+
|
38
|
+
subject.weekend_day?(date).should == weekend_flag
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
context "#week_day?" do
|
44
|
+
it "is true when weekend_day? is false" do
|
45
|
+
subject.should_receive(:weekend_day?).and_return(false)
|
46
|
+
subject.week_day?(today).should be_true
|
47
|
+
end
|
48
|
+
|
49
|
+
it "is false when weekend_day? is true" do
|
50
|
+
subject.should_receive(:weekend_day?).and_return(true)
|
51
|
+
subject.week_day?(today).should be_false
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
context "#work_day?" do
|
56
|
+
it "should be true for a non-holiday weekday" do
|
57
|
+
subject.should_receive(:week_day?).and_return(true)
|
58
|
+
subject.should_receive(:holiday?).and_return(false)
|
59
|
+
|
60
|
+
subject.work_day?(today).should be_true
|
61
|
+
end
|
62
|
+
|
63
|
+
it "should be false for a holiday" do
|
64
|
+
subject.should_receive(:week_day?).and_return(true)
|
65
|
+
subject.should_receive(:holiday?).and_return(true)
|
66
|
+
|
67
|
+
subject.work_day?(today).should be_false
|
68
|
+
end
|
69
|
+
|
70
|
+
it "should be false for a weekend" do
|
71
|
+
subject.should_receive(:week_day?).and_return(false)
|
72
|
+
subject.should_not_receive(:holiday?)
|
73
|
+
|
74
|
+
subject.work_day?(today).should be_false
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
context "#non_work_day?" do
|
79
|
+
it "is true when work_day? is false" do
|
80
|
+
subject.should_receive(:work_day?).and_return(false)
|
81
|
+
subject.non_work_day?(today).should be_true
|
82
|
+
end
|
83
|
+
|
84
|
+
it "is false when work_day? is true" do
|
85
|
+
subject.should_receive(:work_day?).and_return(true)
|
86
|
+
subject.non_work_day?(today).should be_false
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
context "#work_days_in_range" do
|
91
|
+
let(:start_date) {random_date}
|
92
|
+
let(:end_date) {start_date + rand(45)}
|
93
|
+
|
94
|
+
it "should return an array of the work days between two dates" do
|
95
|
+
valid_work_days = []
|
96
|
+
|
97
|
+
(start_date..end_date).each do |date|
|
98
|
+
work_day = random_boolean
|
99
|
+
|
100
|
+
valid_work_days << date if work_day
|
101
|
+
subject.should_receive(:work_day?).with(date).and_return(work_day)
|
102
|
+
end
|
103
|
+
|
104
|
+
subject.work_days_in_range(start_date, end_date).should eq(valid_work_days)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
context "#work_days_in_month" do
|
109
|
+
let(:date) {random_date}
|
110
|
+
let(:range_start_date) {Date.new(date.year, date.month, 1)}
|
111
|
+
let(:range_end_date) {Date.new(date.year, date.month, -1)}
|
112
|
+
let(:range_work_days) {rand(1..31)}
|
113
|
+
|
114
|
+
it "should return the number of days in the month specified" do
|
115
|
+
subject.should_receive(:work_days_in_range).with(range_start_date, range_end_date).and_return(range_work_days)
|
116
|
+
subject.work_days_in_month(date).should eq(range_work_days)
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
context "#monthly_work_days" do
|
121
|
+
let(:year) {rand(1900..2500)}
|
122
|
+
let(:monthly_work_days) do
|
123
|
+
Hash[(1..12).collect{|i| [Date.new(year, i, 1), i]}]
|
124
|
+
end
|
125
|
+
|
126
|
+
it "should return an array of the work_days in each month of the given year" do
|
127
|
+
monthly_work_days.each do |start_date, days|
|
128
|
+
subject.should_receive(:work_days_in_month).with(start_date).and_return(days)
|
129
|
+
end
|
130
|
+
|
131
|
+
subject.monthly_work_days(year).should eql(monthly_work_days.values)
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
context "#next_work_day" do
|
136
|
+
it "should never return the same date" do
|
137
|
+
starting_date = random_date
|
138
|
+
ending_date = starting_date + 1
|
139
|
+
|
140
|
+
subject.should_receive(:work_day?).and_return(true)
|
141
|
+
subject.next_work_day(starting_date).should eq(ending_date)
|
142
|
+
end
|
143
|
+
|
144
|
+
it "iterates through each day until it finds the first non-holiday/weekend" do
|
145
|
+
first_work_day = random_date
|
146
|
+
starting_date = first_work_day - 2
|
147
|
+
|
148
|
+
subject.should_receive(:work_day?).and_return(false)
|
149
|
+
subject.should_receive(:work_day?).with(first_work_day).and_return(true)
|
150
|
+
|
151
|
+
subject.next_work_day(starting_date).should eq(first_work_day)
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
context "#previous_work_day" do
|
156
|
+
it "should never return the same date" do
|
157
|
+
starting_date = random_date
|
158
|
+
ending_date = starting_date - 1
|
159
|
+
|
160
|
+
subject.should_receive(:work_day?).and_return(true)
|
161
|
+
subject.previous_work_day(starting_date).should eq(ending_date)
|
162
|
+
end
|
163
|
+
|
164
|
+
it "iterates backwards through each day until it finds the first non-holiday/weekend" do
|
165
|
+
first_work_day = random_date
|
166
|
+
starting_date = first_work_day + 2
|
167
|
+
|
168
|
+
subject.should_receive(:work_day?).and_return(false)
|
169
|
+
subject.should_receive(:work_day?).with(first_work_day).and_return(true)
|
170
|
+
|
171
|
+
subject.previous_work_day(starting_date).should eq(first_work_day)
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
context "#work_days_from" do
|
176
|
+
it "should accept the number of days and starting date" do
|
177
|
+
should respond_to(:work_days_from)
|
178
|
+
end
|
179
|
+
|
180
|
+
it "should call next_work_day the specified number of times" do
|
181
|
+
start_date = random_date
|
182
|
+
current_date = start_date
|
183
|
+
days_from_start = rand(1..100)
|
184
|
+
|
185
|
+
days_from_start.downto(1) do
|
186
|
+
subject.should_receive(:next_work_day).with(current_date).and_return(current_date += 1)
|
187
|
+
end
|
188
|
+
|
189
|
+
subject.work_days_from(days_from_start, start_date).should eq(current_date)
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
context "#observed_holidays" do
|
194
|
+
it "should raise an exception" do
|
195
|
+
expect{subject.observed_holidays}.to raise_error
|
196
|
+
end
|
197
|
+
end
|
198
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
require_relative '../../../spec_helper'
|
2
|
+
|
3
|
+
describe Range, :type => :holiday_helpers do
|
4
|
+
let(:start_date) { random_date}
|
5
|
+
let(:end_date) { start_date + rand(35)}
|
6
|
+
let(:range) { start_date..end_date}
|
7
|
+
|
8
|
+
it "should call WorkDays.work_days_in_range" do
|
9
|
+
WorkDays.should_receive(:work_days_in_range).with(range.first, range.last)
|
10
|
+
range.work_days
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require_relative '../../spec_helper'
|
2
|
+
|
3
|
+
describe WorkDays::HolidayMethods, :type => :holiday_helpers do
|
4
|
+
let(:today) {Date.today}
|
5
|
+
let(:current_year) {Date.today.year}
|
6
|
+
|
7
|
+
let(:dummy_class) do
|
8
|
+
Class.new do
|
9
|
+
include(WorkDays::HolidayMethods)
|
10
|
+
|
11
|
+
def observed_holidays
|
12
|
+
[:christmas_day]
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
holidays.each do |holiday, dates|
|
18
|
+
context "##{holiday.to_s}" do
|
19
|
+
subject {dummy_class.new}
|
20
|
+
|
21
|
+
it "should accept a string as the year input" do
|
22
|
+
subject.public_send(holiday, '2012')
|
23
|
+
end
|
24
|
+
|
25
|
+
it "should default to the current year" do
|
26
|
+
current_year_holiday = subject.public_send(holiday, current_year)
|
27
|
+
subject.public_send(holiday).should eq(current_year_holiday)
|
28
|
+
end
|
29
|
+
|
30
|
+
it "should return the proper date given a year" do
|
31
|
+
dates.each do |date|
|
32
|
+
date = Date.parse(date)
|
33
|
+
subject.public_send(holiday, date.year).should eq(date)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require_relative '../../spec_helper'
|
2
|
+
|
3
|
+
describe WorkDays, :type => :holiday_helpers do
|
4
|
+
subject {WorkDays}
|
5
|
+
|
6
|
+
before do
|
7
|
+
subject.instance_variable_set(:@work_schedule, nil)
|
8
|
+
end
|
9
|
+
|
10
|
+
after do
|
11
|
+
subject.instance_variable_set(:@work_schedule, nil)
|
12
|
+
end
|
13
|
+
|
14
|
+
|
15
|
+
context ".work_schedule=" do
|
16
|
+
it "sets a module instance variable to the passed value" do
|
17
|
+
subject.work_schedule = 'random value'
|
18
|
+
subject.instance_variable_get(:@work_schedule).should eq('random value')
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
context ".work_schedule" do
|
23
|
+
it "returns the value of the currently set work_schedule" do
|
24
|
+
subject.work_schedule = 'random value'
|
25
|
+
subject.work_schedule.should eq('random value')
|
26
|
+
end
|
27
|
+
|
28
|
+
it "returns new WorkSchedules::Default if the work_schedule wasn't already set" do
|
29
|
+
subject.work_schedule.should be_an_instance_of(WorkDays::WorkSchedules::Default)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
context ".method_missing" do
|
34
|
+
before do
|
35
|
+
subject.work_schedule = double('schedule',:random_method => 'non_nil', :foo => 'bar')
|
36
|
+
end
|
37
|
+
|
38
|
+
after do
|
39
|
+
subject.work_schedule = nil
|
40
|
+
end
|
41
|
+
|
42
|
+
it "raises NoMethodError when a method is called that the current WorkSchedule doesn't implement" do
|
43
|
+
expect{ subject.adsfakjlk }.to raise_error(NoMethodError)
|
44
|
+
end
|
45
|
+
|
46
|
+
it "proxies all calls to current work_schedule" do
|
47
|
+
subject.work_schedule.should_receive(:random_method)
|
48
|
+
subject.work_schedule.should_receive(:foo)
|
49
|
+
subject.random_method.should eq('non_nil')
|
50
|
+
subject.foo.should eq('bar')
|
51
|
+
end
|
52
|
+
|
53
|
+
it "indicates that it responds to methods implemented by the current WorkSchedule" do
|
54
|
+
subject.respond_to?(:random_method).should be_true
|
55
|
+
subject.respond_to?(:foo).should be_true
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'pry'
|
2
|
+
|
3
|
+
require_relative '../lib/work_days'
|
4
|
+
Dir["./spec/support/**/*.rb"].sort.each {|f| require f}
|
5
|
+
|
6
|
+
RSpec.configure do |config|
|
7
|
+
config.treat_symbols_as_metadata_keys_with_true_values = true
|
8
|
+
config.run_all_when_everything_filtered = true
|
9
|
+
config.filter_run :focus
|
10
|
+
|
11
|
+
# Run specs in random order to surface order dependencies. If you find an
|
12
|
+
# order dependency and want to debug it, you can fix the order by providing
|
13
|
+
# the seed, which is printed after each run.
|
14
|
+
# --seed 1234
|
15
|
+
config.order = 'random'
|
16
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module WorkDays::SpecHelpers
|
2
|
+
def holidays
|
3
|
+
@holidays ||= {
|
4
|
+
:new_years_day => %w{1980-01-01 1987-01-01 1993-01-01 2001-01-01 2006-01-02 2015-01-01},
|
5
|
+
:martin_luther_king_day => %w{1986-01-20 1989-01-16 1993-01-18 2001-01-15 2006-01-16 2015-01-19},
|
6
|
+
:presidents_day => %w{1986-02-17 1989-02-20 1993-02-15 2001-02-19 2006-02-20 2015-02-16},
|
7
|
+
:easter_sunday => %w{1980-04-06 1987-04-19 1993-04-11 2001-04-15 2002-03-31 2015-04-05},
|
8
|
+
:memorial_day => %w{1980-05-26 1987-05-25 1993-05-31 2001-05-28 2006-05-29 2015-05-25},
|
9
|
+
:independence_day => %w{1980-07-04 1987-07-03 1993-07-05 2001-07-04 2002-07-04 2015-07-03},
|
10
|
+
:labor_day => %w{1980-09-01 1987-09-07 1993-09-06 2001-09-03 2006-09-04 2015-09-07},
|
11
|
+
:columbus_day => %w{1980-10-13 1987-10-12 1993-10-11 2001-10-08 2006-10-09 2015-10-12},
|
12
|
+
:veterans_day => %w{1980-11-11 1987-11-11 1993-11-11 2001-11-12 2006-11-10 2015-11-11},
|
13
|
+
:thanksgiving_day => %w{1980-11-27 1987-11-26 1993-11-25 2001-11-22 2006-11-23 2015-11-26},
|
14
|
+
:black_friday => %w{1980-11-28 1987-11-27 1993-11-26 2001-11-23 2006-11-24 2015-11-27},
|
15
|
+
:christmas_eve_day => %w{1982-12-23 1987-12-24 1994-12-23 2001-12-24 2006-12-22 2015-12-24},
|
16
|
+
:christmas_day => %w{1982-12-24 1987-12-25 1994-12-26 2001-12-25 2006-12-25 2015-12-25},
|
17
|
+
}
|
18
|
+
end
|
19
|
+
|
20
|
+
def all_holiday_dates
|
21
|
+
@all_holiday_dates ||= holidays.values.flatten
|
22
|
+
end
|
23
|
+
|
24
|
+
def default_holiday_dates
|
25
|
+
WorkDays.send(:default_holiday_methods).collect do |holiday|
|
26
|
+
holidays[holiday]
|
27
|
+
end.flatten.compact
|
28
|
+
end
|
29
|
+
|
30
|
+
def random_date
|
31
|
+
Date.new(rand(1900..2500), rand(1..12), rand(1..28))
|
32
|
+
end
|
33
|
+
|
34
|
+
def random_boolean
|
35
|
+
rand(0..1) == 0
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
RSpec.configure do |c|
|
40
|
+
c.extend WorkDays::SpecHelpers, :type => :holiday_helpers
|
41
|
+
c.include WorkDays::SpecHelpers, :type => :holiday_helpers
|
42
|
+
end
|
43
|
+
|
@@ -0,0 +1,21 @@
|
|
1
|
+
shared_examples "a work schedule" do
|
2
|
+
context "included modules" do
|
3
|
+
it "includes WorkDays::HolidayMethods" do
|
4
|
+
described_class.ancestors.should include(WorkDays::HolidayMethods)
|
5
|
+
end
|
6
|
+
|
7
|
+
it "includes WorkDays::CalculationMethods" do
|
8
|
+
described_class.ancestors.should include(WorkDays::CalculationMethods)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
context "#observed_holidays" do
|
13
|
+
it {should respond_to(:observed_holidays)}
|
14
|
+
|
15
|
+
it "returns an enumerable of holiday methods" do
|
16
|
+
subject.observed_holidays.each do |sym|
|
17
|
+
subject.should respond_to(sym)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
shared_examples "a proxy for WorkDays.work_day?" do
|
2
|
+
context "#work_day?" do
|
3
|
+
it "should respond_to work_day?" do
|
4
|
+
should respond_to(:work_day?)
|
5
|
+
end
|
6
|
+
|
7
|
+
it "should call WorkDays.work_day? passing self" do
|
8
|
+
WorkDays.should_receive(:work_day?).with(subject)
|
9
|
+
|
10
|
+
subject.work_day?
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
data/work_days.gemspec
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require File.expand_path('../lib/work_days/version', __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |gem|
|
5
|
+
gem.authors = ["Robert Jackson"]
|
6
|
+
gem.email = ["robertj@promedicalinc.com"]
|
7
|
+
gem.description = %q{Calculate the number of business days in a given period. Also, add convenience methods to Range, Date, DateTime, and Time.}
|
8
|
+
gem.summary = %q{Simple business day calculations.}
|
9
|
+
gem.homepage = "https://github.com/rjackson/work_days"
|
10
|
+
|
11
|
+
gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
12
|
+
gem.files = `git ls-files`.split("\n")
|
13
|
+
gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
14
|
+
gem.name = "work_days"
|
15
|
+
gem.require_paths = ["lib"]
|
16
|
+
gem.version = WorkDays::VERSION
|
17
|
+
|
18
|
+
gem.add_development_dependency 'rspec'
|
19
|
+
end
|
metadata
ADDED
@@ -0,0 +1,107 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: work_days
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.3
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Robert Jackson
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-12-31 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: rspec
|
16
|
+
prerelease: false
|
17
|
+
requirement: !ruby/object:Gem::Requirement
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
none: false
|
23
|
+
type: :development
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
requirements:
|
26
|
+
- - ! '>='
|
27
|
+
- !ruby/object:Gem::Version
|
28
|
+
version: '0'
|
29
|
+
none: false
|
30
|
+
description: Calculate the number of business days in a given period. Also, add convenience
|
31
|
+
methods to Range, Date, DateTime, and Time.
|
32
|
+
email:
|
33
|
+
- robertj@promedicalinc.com
|
34
|
+
executables: []
|
35
|
+
extensions: []
|
36
|
+
extra_rdoc_files: []
|
37
|
+
files:
|
38
|
+
- .gitignore
|
39
|
+
- .rspec
|
40
|
+
- Gemfile
|
41
|
+
- Guardfile
|
42
|
+
- LICENSE
|
43
|
+
- README.md
|
44
|
+
- Rakefile
|
45
|
+
- lib/work_days.rb
|
46
|
+
- lib/work_days/calculation_methods.rb
|
47
|
+
- lib/work_days/ext/date.rb
|
48
|
+
- lib/work_days/ext/date_time.rb
|
49
|
+
- lib/work_days/ext/range.rb
|
50
|
+
- lib/work_days/ext/time.rb
|
51
|
+
- lib/work_days/holiday_methods.rb
|
52
|
+
- lib/work_days/version.rb
|
53
|
+
- lib/work_days/work_schedules/bank.rb
|
54
|
+
- lib/work_days/work_schedules/default.rb
|
55
|
+
- spec/lib/work_days/calculation_methods_spec.rb
|
56
|
+
- spec/lib/work_days/ext/date_spec.rb
|
57
|
+
- spec/lib/work_days/ext/datetime_spec.rb
|
58
|
+
- spec/lib/work_days/ext/range_spec.rb
|
59
|
+
- spec/lib/work_days/ext/time_spec.rb
|
60
|
+
- spec/lib/work_days/holiday_methods_spec.rb
|
61
|
+
- spec/lib/work_days/work_days_spec.rb
|
62
|
+
- spec/lib/work_days/work_schedules/bank_spec.rb
|
63
|
+
- spec/lib/work_days/work_schedules/default_spec.rb
|
64
|
+
- spec/spec_helper.rb
|
65
|
+
- spec/support/holiday_helpers.rb
|
66
|
+
- spec/support/shared_work_schedule.rb
|
67
|
+
- spec/support/work_day_proxy.rb
|
68
|
+
- work_days.gemspec
|
69
|
+
homepage: https://github.com/rjackson/work_days
|
70
|
+
licenses: []
|
71
|
+
post_install_message:
|
72
|
+
rdoc_options: []
|
73
|
+
require_paths:
|
74
|
+
- lib
|
75
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
76
|
+
requirements:
|
77
|
+
- - ! '>='
|
78
|
+
- !ruby/object:Gem::Version
|
79
|
+
version: '0'
|
80
|
+
none: false
|
81
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
82
|
+
requirements:
|
83
|
+
- - ! '>='
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: '0'
|
86
|
+
none: false
|
87
|
+
requirements: []
|
88
|
+
rubyforge_project:
|
89
|
+
rubygems_version: 1.8.24
|
90
|
+
signing_key:
|
91
|
+
specification_version: 3
|
92
|
+
summary: Simple business day calculations.
|
93
|
+
test_files:
|
94
|
+
- spec/lib/work_days/calculation_methods_spec.rb
|
95
|
+
- spec/lib/work_days/ext/date_spec.rb
|
96
|
+
- spec/lib/work_days/ext/datetime_spec.rb
|
97
|
+
- spec/lib/work_days/ext/range_spec.rb
|
98
|
+
- spec/lib/work_days/ext/time_spec.rb
|
99
|
+
- spec/lib/work_days/holiday_methods_spec.rb
|
100
|
+
- spec/lib/work_days/work_days_spec.rb
|
101
|
+
- spec/lib/work_days/work_schedules/bank_spec.rb
|
102
|
+
- spec/lib/work_days/work_schedules/default_spec.rb
|
103
|
+
- spec/spec_helper.rb
|
104
|
+
- spec/support/holiday_helpers.rb
|
105
|
+
- spec/support/shared_work_schedule.rb
|
106
|
+
- spec/support/work_day_proxy.rb
|
107
|
+
has_rdoc:
|