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 ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format progress
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source 'https://rubygems.org'
2
+ gemspec
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
@@ -0,0 +1,7 @@
1
+ # RiStateHolidays
2
+
3
+ Calculate Rhode Island state holidays and observances
4
+
5
+ ## Usage
6
+
7
+ RiStateHolidays.holiday? Date.today [true or false]
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
@@ -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
@@ -0,0 +1,7 @@
1
+ require 'ri_state_holidays'
2
+
3
+ RSpec.configure do |config|
4
+ config.treat_symbols_as_metadata_keys_with_true_values = true
5
+ config.run_all_when_everything_filtered = true
6
+ config.order = 'random'
7
+ end
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