ri_state_holidays 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +17 -0
- data/.rspec +2 -0
- data/Gemfile +2 -0
- data/LICENSE +25 -0
- data/README.md +7 -0
- data/Rakefile +2 -0
- data/bin/ri_state_holiday +33 -0
- data/lib/ri_state_holidays.rb +215 -0
- data/ri_state_holidays.gemspec +20 -0
- data/spec/ri_state_holidays_spec.rb +68 -0
- data/spec/spec_helper.rb +7 -0
- metadata +97 -0
data/.gitignore
ADDED
data/.rspec
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
==== RI State Holidays Gem - By Joseph Alba
|
2
|
+
|
3
|
+
Built with lots of help from the Ruby Holidays Gem by Alex Dunae
|
4
|
+
|
5
|
+
MIT License
|
6
|
+
|
7
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
8
|
+
a copy of this software and associated documentation files (the
|
9
|
+
"Software"), to deal in the Software without restriction, including
|
10
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
11
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
12
|
+
permit persons to whom the Software is furnished to do so, subject to
|
13
|
+
the following conditions:
|
14
|
+
|
15
|
+
The above copyright notice and this permission notice shall be
|
16
|
+
included in all copies or substantial portions of the Software.
|
17
|
+
|
18
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
19
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
20
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
21
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
22
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
23
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
24
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
25
|
+
|
data/README.md
ADDED
data/Rakefile
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'ri_state_holidays'
|
4
|
+
|
5
|
+
########################################
|
6
|
+
date = STDIN.tty? ? ARGV[0] : STDIN.read
|
7
|
+
if date
|
8
|
+
begin
|
9
|
+
date = Date.parse date
|
10
|
+
rescue
|
11
|
+
puts "ERROR: Invalid date -- unable to parse input"
|
12
|
+
exit 2
|
13
|
+
end
|
14
|
+
end
|
15
|
+
date ||= Date.today
|
16
|
+
|
17
|
+
|
18
|
+
########################################
|
19
|
+
if [0,6].include? date.wday
|
20
|
+
puts "ERROR: #{date.strftime('%B %d, %Y')} is a weekend day"
|
21
|
+
exit 2
|
22
|
+
end
|
23
|
+
|
24
|
+
is_holiday = RiStateHolidays.holiday? date
|
25
|
+
|
26
|
+
if is_holiday
|
27
|
+
puts "YES: #{date.strftime('%B %d, %Y')} is a Rhode Island state holiday - #{RiStateHolidays.holiday(date).first[:name]}"
|
28
|
+
exit 1
|
29
|
+
else
|
30
|
+
puts "NO: #{date.strftime('%B %d, %Y')} is not a Rhode Island state holiday"
|
31
|
+
exit 0
|
32
|
+
end
|
33
|
+
|
@@ -0,0 +1,215 @@
|
|
1
|
+
# Much of this code comes right from the Holidays Ruby gem (https://github.com/alexdunae/holidays)
|
2
|
+
# I created a separate gem rather than creating an ri_state_holidays 'region' because
|
3
|
+
# I wanted to keep the Date class unmodified.
|
4
|
+
|
5
|
+
## RI state holiday information from http://sos.ri.gov/library/stateholidays/
|
6
|
+
|
7
|
+
# New year's day, 4th of July, Christmas
|
8
|
+
# Dr. Martin Luther King Jr's Birthday - Third Monday in January
|
9
|
+
# Memorial Day - Last Monday in May
|
10
|
+
# Victory Day - Second Monday in August
|
11
|
+
# Labor Day - First Monday in September
|
12
|
+
# Columbus Day - Second Monday in October
|
13
|
+
# Election Day - Tuesday after the First Monday in November
|
14
|
+
# Thanksgiving - Fourth Thursday in November
|
15
|
+
|
16
|
+
require 'date'
|
17
|
+
require 'digest/md5'
|
18
|
+
|
19
|
+
module RiStateHolidays
|
20
|
+
WEEKS = {:first => 1, :second => 2, :third => 3, :fourth => 4, :fifth => 5, :last => -1, :second_last => -2, :third_last => -3}
|
21
|
+
MONTH_LENGTHS = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
|
22
|
+
DAY_SYMBOLS = Date::DAYNAMES.collect { |n| n.downcase.intern }
|
23
|
+
|
24
|
+
def self.holiday?(d)
|
25
|
+
!holiday(d).empty?
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.holiday(d)
|
29
|
+
self.between(d, d)
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.holidays_by_month
|
33
|
+
@holidays_by_month ||= get_holidays_by_month
|
34
|
+
end
|
35
|
+
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
|
40
|
+
def self.between(start_date, end_date, *options)
|
41
|
+
# remove the timezone
|
42
|
+
start_date = start_date.new_offset(0) + start_date.offset if start_date.respond_to?(:new_offset)
|
43
|
+
end_date = end_date.new_offset(0) + end_date.offset if end_date.respond_to?(:new_offset)
|
44
|
+
|
45
|
+
# get simple dates
|
46
|
+
if start_date.respond_to?(:to_date)
|
47
|
+
start_date = start_date.to_date
|
48
|
+
else
|
49
|
+
start_date = Date.civil(start_date.year, start_date.mon, start_date.mday)
|
50
|
+
end
|
51
|
+
|
52
|
+
if end_date.respond_to?(:to_date)
|
53
|
+
end_date = end_date.to_date
|
54
|
+
else
|
55
|
+
end_date = Date.civil(end_date.year, end_date.mon, end_date.mday)
|
56
|
+
end
|
57
|
+
|
58
|
+
regions, observed, informal = parse_options(options)
|
59
|
+
|
60
|
+
holidays = []
|
61
|
+
|
62
|
+
dates = {}
|
63
|
+
(start_date..end_date).each do |date|
|
64
|
+
# Always include month '0' for variable-month holidays
|
65
|
+
dates[date.year] = [0] unless dates[date.year]
|
66
|
+
# TODO: test this, maybe should push then flatten
|
67
|
+
dates[date.year] << date.month unless dates[date.year].include?(date.month)
|
68
|
+
end
|
69
|
+
|
70
|
+
dates.each do |year, months|
|
71
|
+
months.each do |month|
|
72
|
+
next unless hbm = holidays_by_month[month]
|
73
|
+
|
74
|
+
hbm.each do |h|
|
75
|
+
if h[:function]
|
76
|
+
# Holiday definition requires a calculation
|
77
|
+
result = call_proc(h[:function], year)
|
78
|
+
|
79
|
+
# Procs may return either Date or an integer representing mday
|
80
|
+
if result.kind_of?(Date)
|
81
|
+
month = result.month
|
82
|
+
mday = result.mday
|
83
|
+
else
|
84
|
+
mday = result
|
85
|
+
end
|
86
|
+
else
|
87
|
+
# Calculate the mday
|
88
|
+
mday = h[:mday] || calculate_mday(year, month, h[:week], h[:wday])
|
89
|
+
end
|
90
|
+
|
91
|
+
# Silently skip bad mdays
|
92
|
+
begin
|
93
|
+
date = Date.civil(year, month, mday)
|
94
|
+
rescue; next; end
|
95
|
+
|
96
|
+
# If the :observed option is set, calculate the date when the holiday
|
97
|
+
# is observed.
|
98
|
+
if observed and h[:observed]
|
99
|
+
date = call_proc(h[:observed], date)
|
100
|
+
end
|
101
|
+
|
102
|
+
if date.between?(start_date, end_date)
|
103
|
+
holidays << {:date => date, :name => h[:name]}
|
104
|
+
end
|
105
|
+
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
holidays.sort{|a, b| a[:date] <=> b[:date] }
|
111
|
+
end
|
112
|
+
|
113
|
+
|
114
|
+
def self.get_holidays_by_month
|
115
|
+
{
|
116
|
+
1 => [
|
117
|
+
{:mday => 1, :observed => lambda { |date| to_weekday_if_weekend(date) }, :observed_id => "to_weekday_if_weekend", :name => "New Year's Day", :regions => [:us, :us_ri]},
|
118
|
+
{:wday => 1, :week => 3, :name => "Martin Luther King, Jr. Day", :regions => [:us, :us_ri]},
|
119
|
+
{:function => lambda { |year| us_inauguration_day(year) }, :function_id => "us_inauguration_day(year)", :name => "Inauguration Day", :regions => [:us_dc, :us_ri]}
|
120
|
+
],
|
121
|
+
5 => [
|
122
|
+
{:wday => 1, :week => -1, :name => "Memorial Day", :regions => [:us, :us_ri]},
|
123
|
+
],
|
124
|
+
7 => [
|
125
|
+
{:mday => 4, :observed => lambda { |date| to_weekday_if_weekend(date) }, :observed_id => "to_weekday_if_weekend", :name => "Independence Day", :regions => [:us, :us_ri]}
|
126
|
+
],
|
127
|
+
8 => [
|
128
|
+
{:wday => 1, :week => 2, :name => "Victory Day", :regions => [:us, :us_ri]}
|
129
|
+
],
|
130
|
+
9 => [
|
131
|
+
{:wday => 1, :week => 1, :name => "Labor Day", :regions => [:us, :us_ri]}
|
132
|
+
],
|
133
|
+
10 => [
|
134
|
+
{:wday => 1, :week => 2, :name => "Columbus Day", :regions => [:us, :us_ri]}
|
135
|
+
],
|
136
|
+
11 => [
|
137
|
+
{:mday => 11, :observed => lambda { |date| to_weekday_if_weekend(date) }, :observed_id => "to_weekday_if_weekend", :name => "Veterans Day", :regions => [:us, :us_ri]},
|
138
|
+
{:wday => 4, :week => 4, :name => "Thanksgiving", :regions => [:us, :us_ri]},
|
139
|
+
{:function => lambda { |year| us_election_day(year) }, :function_id => "us_election_day(year)", :name => "Election Day", :regions => [:us_ri]}
|
140
|
+
],
|
141
|
+
12 => [
|
142
|
+
{:mday => 25, :observed => lambda { |date| to_weekday_if_weekend(date) }, :observed_id => "to_weekday_if_weekend", :name => "Christmas Day", :regions => [:us, :us_ri]}
|
143
|
+
]
|
144
|
+
}
|
145
|
+
end
|
146
|
+
|
147
|
+
|
148
|
+
# January 20, every fourth year, following Presidential election
|
149
|
+
def self.us_inauguration_day(year)
|
150
|
+
year % 4 == 1 ? 20 : nil
|
151
|
+
end
|
152
|
+
|
153
|
+
|
154
|
+
# Return the Tuesday after the first Monday
|
155
|
+
def self.us_election_day(year)
|
156
|
+
month = 11
|
157
|
+
mday = 1
|
158
|
+
if year % 2 == 0
|
159
|
+
date = Date.civil(year, month, mday)
|
160
|
+
if date.wday < 2
|
161
|
+
return date + (2 - date.wday)
|
162
|
+
elsif date.wday == 2
|
163
|
+
return date + 7
|
164
|
+
elsif date.wday > 2
|
165
|
+
return date + (9 - date.wday)
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
return nil
|
170
|
+
end
|
171
|
+
|
172
|
+
|
173
|
+
def self.to_weekday_if_weekend(date)
|
174
|
+
date += 1 if date.wday == 0
|
175
|
+
date -= 1 if date.wday == 6
|
176
|
+
date
|
177
|
+
end
|
178
|
+
|
179
|
+
|
180
|
+
def self.calculate_mday(year, month, week, wday)
|
181
|
+
raise ArgumentError, "Week parameter must be one of Holidays::WEEKS (provided #{week})." unless WEEKS.include?(week) or WEEKS.has_value?(week)
|
182
|
+
|
183
|
+
unless wday.kind_of?(Numeric) and wday.between?(0,6) or DAY_SYMBOLS.index(wday)
|
184
|
+
raise ArgumentError, "Wday parameter must be an integer between 0 and 6 or one of Date::DAY_SYMBOLS."
|
185
|
+
end
|
186
|
+
|
187
|
+
week = WEEKS[week] if week.kind_of?(Symbol)
|
188
|
+
wday = DAY_SYMBOLS.index(wday) if wday.kind_of?(Symbol)
|
189
|
+
|
190
|
+
# :first, :second, :third, :fourth or :fifth
|
191
|
+
if week > 0
|
192
|
+
return ((week - 1) * 7) + 1 + ((wday - Date.civil(year, month,(week-1)*7 + 1).wday) % 7)
|
193
|
+
end
|
194
|
+
|
195
|
+
days = MONTH_LENGTHS[month-1]
|
196
|
+
|
197
|
+
days = 29 if month == 2 and Date.leap?(year)
|
198
|
+
|
199
|
+
return days - ((Date.civil(year, month, days).wday - wday + 7) % 7) - (7 * (week.abs - 1))
|
200
|
+
end
|
201
|
+
|
202
|
+
def self.call_proc(function, year) # :nodoc:
|
203
|
+
proc_key = Digest::MD5.hexdigest("#{function.to_s}_#{year.to_s}")
|
204
|
+
function.call(year)
|
205
|
+
end
|
206
|
+
|
207
|
+
def self.parse_options(options)
|
208
|
+
regions = [:us_ri]
|
209
|
+
observed = true
|
210
|
+
informal = nil
|
211
|
+
return regions, observed, informal
|
212
|
+
end
|
213
|
+
|
214
|
+
|
215
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
Gem::Specification.new do |gem|
|
4
|
+
gem.authors = ["Joseph Alba"]
|
5
|
+
gem.email = ["jalba@egov.com"]
|
6
|
+
gem.description = %q{Calculate Rhode Island state holidays}
|
7
|
+
gem.summary = %q{Based off the Holidays Ruby gem -- but without altering the Date class}
|
8
|
+
gem.homepage = ""
|
9
|
+
|
10
|
+
gem.files = `git ls-files`.split($\)
|
11
|
+
gem.executables = ['ri_state_holiday']
|
12
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
13
|
+
gem.name = "ri_state_holidays"
|
14
|
+
gem.require_paths = ["lib"]
|
15
|
+
|
16
|
+
gem.version = '0.1.0'
|
17
|
+
|
18
|
+
gem.add_development_dependency "rspec"
|
19
|
+
gem.add_development_dependency "rake"
|
20
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe RiStateHolidays do
|
4
|
+
|
5
|
+
describe "easy day-of-month holidays" do
|
6
|
+
it "knows New Years Day" do
|
7
|
+
RiStateHolidays.holiday?(Date.new(2013, 1, 1)).should be_true
|
8
|
+
end
|
9
|
+
|
10
|
+
it "knows Independence Day" do
|
11
|
+
RiStateHolidays.holiday?(Date.new(2012, 7, 4)).should be_true
|
12
|
+
end
|
13
|
+
|
14
|
+
it "knows Christmas" do
|
15
|
+
RiStateHolidays.holiday?(Date.new(2012, 12, 25)).should be_true
|
16
|
+
end
|
17
|
+
|
18
|
+
it "knows a non-holiday" do
|
19
|
+
RiStateHolidays.holiday?(Date.new(2012, 2, 28)).should be_false
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
|
24
|
+
describe "observed holidays" do
|
25
|
+
# When any legal holiday falls on a Sunday, the day following it is a full holiday.
|
26
|
+
it "observed New Years from a Sunday" do
|
27
|
+
RiStateHolidays.holiday?(Date.new(2012, 1, 2)).should be_true
|
28
|
+
end
|
29
|
+
|
30
|
+
it "observed Veterans Day from a Sunday" do
|
31
|
+
RiStateHolidays.holiday?(Date.new(2012, 11, 12)).should be_true
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
|
36
|
+
describe "calculated holidays" do
|
37
|
+
it "knows Veteran's Day" do
|
38
|
+
RiStateHolidays.holiday?(Date.new(2013, 11, 11)).should be_true
|
39
|
+
RiStateHolidays.holiday?(Date.new(2014, 11, 11)).should be_true
|
40
|
+
end
|
41
|
+
|
42
|
+
it "knows Victory Day" do
|
43
|
+
RiStateHolidays.holiday?(Date.new(2012, 8, 13)).should be_true
|
44
|
+
RiStateHolidays.holiday?(Date.new(2013, 8, 12)).should be_true
|
45
|
+
RiStateHolidays.holiday?(Date.new(2014, 8, 11)).should be_true
|
46
|
+
end
|
47
|
+
|
48
|
+
it "knows Labor Day" do
|
49
|
+
RiStateHolidays.holiday?(Date.new(2012, 9, 3)).should be_true
|
50
|
+
RiStateHolidays.holiday?(Date.new(2013, 9, 2)).should be_true
|
51
|
+
RiStateHolidays.holiday?(Date.new(2014, 9, 1)).should be_true
|
52
|
+
end
|
53
|
+
|
54
|
+
it "knows election day" do
|
55
|
+
RiStateHolidays.holiday?(Date.new(2012, 11, 6)).should be_true
|
56
|
+
RiStateHolidays.holiday?(Date.new(2013, 11, 5)).should be_false
|
57
|
+
RiStateHolidays.holiday?(Date.new(2014, 11, 4)).should be_true
|
58
|
+
end
|
59
|
+
|
60
|
+
it "knows Thanksgiving" do
|
61
|
+
RiStateHolidays.holiday?(Date.new(2012, 11, 22)).should be_true
|
62
|
+
RiStateHolidays.holiday?(Date.new(2013, 11, 28)).should be_true
|
63
|
+
RiStateHolidays.holiday?(Date.new(2014, 11, 27)).should be_true
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
|
68
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,97 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: ri_state_holidays
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Joseph Alba
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-01-23 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: rspec
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: rake
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
type: :development
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
description: Calculate Rhode Island state holidays
|
47
|
+
email:
|
48
|
+
- jalba@egov.com
|
49
|
+
executables:
|
50
|
+
- ri_state_holiday
|
51
|
+
extensions: []
|
52
|
+
extra_rdoc_files: []
|
53
|
+
files:
|
54
|
+
- .gitignore
|
55
|
+
- .rspec
|
56
|
+
- Gemfile
|
57
|
+
- LICENSE
|
58
|
+
- README.md
|
59
|
+
- Rakefile
|
60
|
+
- bin/ri_state_holiday
|
61
|
+
- lib/ri_state_holidays.rb
|
62
|
+
- ri_state_holidays.gemspec
|
63
|
+
- spec/ri_state_holidays_spec.rb
|
64
|
+
- spec/spec_helper.rb
|
65
|
+
homepage: ''
|
66
|
+
licenses: []
|
67
|
+
post_install_message:
|
68
|
+
rdoc_options: []
|
69
|
+
require_paths:
|
70
|
+
- lib
|
71
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
72
|
+
none: false
|
73
|
+
requirements:
|
74
|
+
- - ! '>='
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: '0'
|
77
|
+
segments:
|
78
|
+
- 0
|
79
|
+
hash: 2455822407572811696
|
80
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
81
|
+
none: false
|
82
|
+
requirements:
|
83
|
+
- - ! '>='
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: '0'
|
86
|
+
segments:
|
87
|
+
- 0
|
88
|
+
hash: 2455822407572811696
|
89
|
+
requirements: []
|
90
|
+
rubyforge_project:
|
91
|
+
rubygems_version: 1.8.24
|
92
|
+
signing_key:
|
93
|
+
specification_version: 3
|
94
|
+
summary: Based off the Holidays Ruby gem -- but without altering the Date class
|
95
|
+
test_files:
|
96
|
+
- spec/ri_state_holidays_spec.rb
|
97
|
+
- spec/spec_helper.rb
|