From 7268ece923821d96d94b91a83370e1fac9297f0e Mon Sep 17 00:00:00 2001 From: Alex Dunae Date: Tue, 20 Nov 2007 00:58:20 +0000 Subject: [PATCH] Initial import --- README | 48 ++++++++++++++ lib/holidays.rb | 149 ++++++++++++++++++++++++++++++++++++++++++ rakefile.rb | 15 +++++ test/benchmark.rb | 19 ++++++ test/test_helper.rb | 5 ++ test/test_holidays.rb | 72 ++++++++++++++++++++ 6 files changed, 308 insertions(+) create mode 100644 README create mode 100644 lib/holidays.rb create mode 100644 rakefile.rb create mode 100644 test/benchmark.rb create mode 100644 test/test_helper.rb create mode 100644 test/test_holidays.rb diff --git a/README b/README new file mode 100644 index 0000000..d5009a8 --- /dev/null +++ b/README @@ -0,0 +1,48 @@ +holidays + by FIX (your name) + FIX (url) + +== DESCRIPTION: + +FIX (describe your package) + +== FEATURES/PROBLEMS: + +* FIX (list of features or problems) + +== SYNOPSIS: + + FIX (code sample of usage) + +== REQUIREMENTS: + +* FIX (list of requirements) + +== INSTALL: + +* FIX (sudo gem install, anything else) + +== LICENSE: + +(The MIT License) + +Copyright (c) 2007 FIX + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +'Software'), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/lib/holidays.rb b/lib/holidays.rb new file mode 100644 index 0000000..c0e86c9 --- /dev/null +++ b/lib/holidays.rb @@ -0,0 +1,149 @@ + +# === References +# ==== Calculations +# * http://michaelthompson.org/technikos/holidays.php +# ==== World +# * http://en.wikipedia.org/wiki/List_of_holidays_by_country +# ==== US +# * http://www.opm.gov/Operating_Status_Schedules/fedhol/index.asp +# * http://www.smart.net/~mmontes/ushols.html +module Holidays + # Exception class for dealing with unknown regions. + class UnkownRegionError < StandardError; end + + VERSION = '1.0.0' + + HOLIDAY_REGIONS = {:ca => 'Canada', :us => 'United States'} + WEEKS = {:first => 1, :second => 2, :third => 3, :fourth => 4, :fifth => 5, :last => -1} + MONTH_LENGTHS = [ 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] + + + # :wday: Day of the week (0 is Sunday, 6 is Saturday) + HOLIDAYS_BY_MONTH = { + 1 => [{:mday => 1, :name => 'New Year\'s Day', :regions => [:us, :ca]}, + {:mday => 6, :name => 'Epiphany Day', :regions => [:gr]}, + {:wday => 1, :week => :third, :name => 'Martin Luther King, Jr. Day', :regions => [:us]}], + 3 => [{:wday => 1, :week => :third, :name => 'George Washington\'s Birthday', :regions => [:us]}, + {:mday => 25, :name => 'Independence Day', :regions => [:gr]}], + 5 => [{:mday => 1, :name => 'Labour Day', :regions => [:fr,:gr]}, + {:mday => 8, :name => 'Victoria 1945', :regions => [:fr]}, + {:wday => 6, :week => :third, :name => 'Armed Forces Day', :regions => [:us]}, + {:wday => 1, :week => :last, :name => 'Memorial Day', :regions => [:us]}], + 6 => [{:mday => 14, :name => 'Flag Day', :regions => [:us]}], + 7 => [{:mday => 4, :name => 'Independence Day', :regions => [:us]}, + {:mday => 14, :name => 'Ascension Day', :regions => [:fr]}], + 8 => [{:mday => 15, :name => 'Assumption of Mary', :regions => [:fr, :gr, :christ]}], + 9 => [{:wday => 1, :week => :first,:name => 'Labor Day', :regions => [:us]}, + {:wday => 1, :week => :first,:name => 'Labour Day', :regions => [:ca]}], + 10 => [{:wday => 1, :week => :second, :name => 'Columbus Day', :regions => [:us]}, + {:mday => 28, :name => 'National Day', :regions => [:gr]}], + 11 => [{:wday => 4, :week => :fourth, :name => 'Thanksgiving Day', :regions => [:us]}, + {:mday => 11, :name => 'Rememberance Day', :regions => [:ca]}, + {:mday => 11, :name => 'Armistice 1918', :regions => [:fr]}, + {:mday => 1, :name => 'Touissant', :regions => [:fr]}], + 12 => [{:mday => 25, :name => 'Christmas Day', :regions => [:us,:ca,:christ]}, + {:mday => 26, :name => 'Boxing Day', :regions => [:ca,:gr]}] + } + + # Get all holidays on a certain date + def self.lookup_holidays(date, regions = [:ca, :us]) + #raise(UnkownRegionError, "No holiday information is available for region '#{region}'") unless known_region?(region) + + regions = [regions] unless regions.kind_of?(Array) + hbm = HOLIDAYS_BY_MONTH[date.mon] + + holidays = [] + + year = date.year + month = date.month + mday = date.mday + wday = date.wday + + hbm.each do |h| + # start with the region check + next unless h[:regions].any?{ |reg| regions.include?(reg) } + + if h[:mday] and h[:mday] == mday + # fixed day of the month + holidays << h + elsif h[:wday] == wday + # by week calculation + if calculate_mday(year, month, h[:week], h[:wday]) == mday + holidays << h + end + end + end + + holidays + end + + #def self.calculate_mday(yr, mo, wday, int) + # earliest = 1 + 7 * (int - 1) + + # wd = Date.civil(yr, mo, earliest).wday + # if wday == earliest + # off = 0 + # else + # if wday < wd + # off = wday + (7 - wd) + # else + # off = (wday + (7 - wd)) - 7 + # end + # end + + # earliest + off + # end + + # Calculate the day of the month based on week and day of the week. + # + # First Monday of Jan, 2008 + # calculate_mday(2008, 1, :first, :monday) + # + # Third Thursday of Dec, 2008 + # calculate_mday(2008, 12, :third, 4) + # + # Last Monday of Jan, 2008 + # calculate_mday(2008, 1, :last, 1) + #-- + # see http://www.irt.org/articles/js050/index.htm + def self.calculate_mday(year, month, week, wday) + raise ArgumentError, "Week paramater must be one of Holidays::WEEKS" unless WEEKS.include?(week) + + nth = WEEKS[week] + + if nth > 0 + return (nth-1)*7 + 1 + (7 + wday - Date.civil(year, month,(nth-1)*7 + 1).wday)%7 + end + + days = MONTH_LENGTHS[month] + if month == 2 and Date.civil(year,1,1).leap? + days = 29 + end + + return days - (Date.civil(year, month, days).wday - wday + 8)%7; + end + + def self.holidays_in_range(from, to, regions = [:ca,:us]) + regions = [regions] unless regions.kind_of?(Array) + holidays = [] + + + + end +end + +class Date + include Holidays + + # Date.civil('2008-01-01').is_holiday?(:us) + def is_holiday?(region = 'us') + region = region.to_sym + + holidays = Holidays.lookup_holidays(self, region) + if holidays + return true + else + return false + end + end +end \ No newline at end of file diff --git a/rakefile.rb b/rakefile.rb new file mode 100644 index 0000000..30bbd2b --- /dev/null +++ b/rakefile.rb @@ -0,0 +1,15 @@ +require 'rake' +require 'rake/testtask' +require 'rake/rdoctask' +require 'rake/gempackagetask' +require 'fileutils' +require 'lib/holidays' + +desc 'Run the unit tests.' +Rake::TestTask.new do |t| + t.libs << 'lib' + t.libs << 'lib/test' + t.test_files = FileList['test/test*.rb'].exclude('test_helper.rb') + t.verbose = false +end + diff --git a/test/benchmark.rb b/test/benchmark.rb new file mode 100644 index 0000000..c9abfcd --- /dev/null +++ b/test/benchmark.rb @@ -0,0 +1,19 @@ +require File.dirname(__FILE__) + '/test_helper' +require 'date' +require 'benchmark' + + +n = 10000 +Benchmark.bm do |x| + x.report('calculate_mday') do + n.times do + + Holidays.calculate_mday(2008, 1, 1, 3) + end + end + x.report('calculate_mdaya') do + n.times do + Holidays.calculate_mdaya(:third, 1, 1, 2008) + end + end +end \ No newline at end of file diff --git a/test/test_helper.rb b/test/test_helper.rb new file mode 100644 index 0000000..9cb966b --- /dev/null +++ b/test/test_helper.rb @@ -0,0 +1,5 @@ +$LOAD_PATH.unshift(File.expand_path(File.dirname(__FILE__), '../')) +$LOAD_PATH.unshift(File.expand_path(File.dirname(__FILE__), '../lib/')) +require 'rubygems' +require 'test/unit' +require 'holidays' diff --git a/test/test_holidays.rb b/test/test_holidays.rb new file mode 100644 index 0000000..335fa77 --- /dev/null +++ b/test/test_holidays.rb @@ -0,0 +1,72 @@ +require File.dirname(__FILE__) + '/test_helper' +require 'date' +# Test cases for reading and generating CSS shorthand properties +class HolidayTests < Test::Unit::TestCase + + def setup + @date = Date.civil(2008,1,1) + end + + def test_extending_date_class + assert @date.respond_to?('is_holiday?') + end + + def test_calculating_mdays + assert_equal 21, Holidays.calculate_mday(2008, 1, :third, 1) + assert_equal 1, Holidays.calculate_mday(2007, 1, :first, 1) + assert_equal 2, Holidays.calculate_mday(2007, 3, :first, 5) + assert_equal 25, Holidays.calculate_mday(2008, 5, :last, 1) + + + # Labour day + assert_equal 3, Holidays.calculate_mday(2007, 9, :first, 1) + assert_equal 1, Holidays.calculate_mday(2008, 9, :first, 1) + assert_equal 7, Holidays.calculate_mday(2009, 9, :first, 1) + assert_equal 5, Holidays.calculate_mday(2011, 9, :first, 1) + assert_equal 5, Holidays.calculate_mday(2050, 9, :first, 1) + assert_equal 4, Holidays.calculate_mday(2051, 9, :first, 1) + + # Canadian thanksgiving + assert_equal 8, Holidays.calculate_mday(2007, 10, :second, 1) + assert_equal 13, Holidays.calculate_mday(2008, 10, :second, 1) + assert_equal 12, Holidays.calculate_mday(2009, 10, :second, 1) + assert_equal 11, Holidays.calculate_mday(2010, 10, :second, 1) + + end + + def test_region_params + holidays = Holidays.lookup_holidays(@date, :us) + assert_equal 1, holidays.length + + holidays = Holidays.lookup_holidays(@date, [:us,:ca]) + assert_equal 1, holidays.length + end + + def test_lookup_holidays_spot_checks + h = Holidays.lookup_holidays(Date.civil(2008,5,1), :gr) + assert_equal 'Labour Day', h[0][:name] + + h = Holidays.lookup_holidays(Date.civil(2045,11,1), :fr) + assert_equal 'Touissant', h[0][:name] + end + + def test_lookup_holidays_and_iterate + holidays = Holidays.lookup_holidays(@date, :ca) + holidays.each do |h| + puts h[:name] + end + end + + def test_lookup_holiday + holidays = Holidays.lookup_holidays(Date.civil(2008,1,21), :ca) + assert_equal 0, holidays.length + + holidays = Holidays.lookup_holidays(Date.civil(2008,1,21), :us) + assert_equal 1, holidays.length + end + + def test_basic + assert Date.civil(2008,1,1).is_holiday?('ca') + end + +end