ri_state_holidays 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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