Added Easter calculation
Began moving holidays into data/holidays.csv
This commit is contained in:
parent
7268ece923
commit
3260cd8be2
4 changed files with 192 additions and 64 deletions
9
data/holidays.csv
Normal file
9
data/holidays.csv
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
month, region, mday, wday, week, name
|
||||||
|
1, us, 1, , , 'New Year\'s Day'
|
||||||
|
1, ca, 1, , , 'New Year\'s Day'
|
||||||
|
1, au, 1, , , 'New Year\'s Day'
|
||||||
|
1, christian, 6, , , 'Epiphany'
|
||||||
|
1, us, , 1, 3, 'Martin Luther King, Jr. Day'
|
||||||
|
3, us, , 1, 3, 'George Washington\'s Birthday'
|
||||||
|
3, gr, 25, , , 'Independence Day'
|
||||||
|
4, au, 25, , , 'ANZAC Day'
|
|
133
lib/holidays.rb
133
lib/holidays.rb
|
@ -20,36 +20,42 @@ module Holidays
|
||||||
|
|
||||||
# :wday: Day of the week (0 is Sunday, 6 is Saturday)
|
# :wday: Day of the week (0 is Sunday, 6 is Saturday)
|
||||||
HOLIDAYS_BY_MONTH = {
|
HOLIDAYS_BY_MONTH = {
|
||||||
1 => [{:mday => 1, :name => 'New Year\'s Day', :regions => [:us, :ca]},
|
1 => [{:mday => 1, :name => 'New Year\'s Day', :regions => [:us, :ca, :au]},
|
||||||
{:mday => 6, :name => 'Epiphany Day', :regions => [:gr]},
|
{:mday => 1, :name => 'Australia Day', :regions => [:au]},
|
||||||
|
{:mday => 6, :name => 'Epiphany', :regions => [:christian]},
|
||||||
{:wday => 1, :week => :third, :name => 'Martin Luther King, Jr. Day', :regions => [:us]}],
|
{:wday => 1, :week => :third, :name => 'Martin Luther King, Jr. Day', :regions => [:us]}],
|
||||||
3 => [{:wday => 1, :week => :third, :name => 'George Washington\'s Birthday', :regions => [:us]},
|
3 => [{:wday => 1, :week => :third, :name => 'George Washington\'s Birthday', :regions => [:us]},
|
||||||
{:mday => 25, :name => 'Independence Day', :regions => [:gr]}],
|
{:mday => 25, :name => 'Independence Day', :regions => [:gr]}],
|
||||||
|
4 => [{:mday => 25, :name => 'ANZAC Day', :regions => [:au]}],
|
||||||
5 => [{:mday => 1, :name => 'Labour Day', :regions => [:fr,:gr]},
|
5 => [{:mday => 1, :name => 'Labour Day', :regions => [:fr,:gr]},
|
||||||
{:mday => 8, :name => 'Victoria 1945', :regions => [:fr]},
|
{:mday => 8, :name => 'Victoria 1945', :regions => [:fr]},
|
||||||
{:wday => 6, :week => :third, :name => 'Armed Forces Day', :regions => [:us]},
|
{:wday => 6, :week => :third, :name => 'Armed Forces Day', :regions => [:us]},
|
||||||
{:wday => 1, :week => :last, :name => 'Memorial Day', :regions => [:us]}],
|
{:wday => 1, :week => :last, :name => 'Memorial Day', :regions => [:us]}],
|
||||||
6 => [{:mday => 14, :name => 'Flag Day', :regions => [:us]}],
|
6 => [{:mday => 14, :name => 'Flag Day', :regions => [:us]},
|
||||||
7 => [{:mday => 4, :name => 'Independence Day', :regions => [:us]},
|
{:wday => 1, :week => :second, :name => 'Queen\'s Birthday', :regions => [:au]}
|
||||||
|
],
|
||||||
|
7 => [{:mday => 1, :name => 'Canada Day', :regions => [:ca]},
|
||||||
|
{:mday => 4, :name => 'Independence Day', :regions => [:us]},
|
||||||
{:mday => 14, :name => 'Ascension Day', :regions => [:fr]}],
|
{:mday => 14, :name => 'Ascension Day', :regions => [:fr]}],
|
||||||
8 => [{:mday => 15, :name => 'Assumption of Mary', :regions => [:fr, :gr, :christ]}],
|
8 => [{:mday => 15, :name => 'Assumption of Mary', :regions => [:fr, :gr, :christian]}],
|
||||||
9 => [{:wday => 1, :week => :first,:name => 'Labor Day', :regions => [:us]},
|
9 => [{:wday => 1, :week => :first,:name => 'Labor Day', :regions => [:us]},
|
||||||
{:wday => 1, :week => :first,:name => 'Labour Day', :regions => [:ca]}],
|
{:wday => 1, :week => :first,:name => 'Labour Day', :regions => [:ca]}],
|
||||||
10 => [{:wday => 1, :week => :second, :name => 'Columbus Day', :regions => [:us]},
|
10 => [{:wday => 1, :week => :second, :name => 'Columbus Day', :regions => [:us]},
|
||||||
{:mday => 28, :name => 'National Day', :regions => [:gr]}],
|
{:mday => 28, :name => 'National Day', :regions => [:gr]}],
|
||||||
11 => [{:wday => 4, :week => :fourth, :name => 'Thanksgiving Day', :regions => [:us]},
|
11 => [{:wday => 4, :week => :fourth, :name => 'Thanksgiving Day', :regions => [:us]},
|
||||||
{:mday => 11, :name => 'Rememberance Day', :regions => [:ca]},
|
{:mday => 11, :name => 'Rememberance Day', :regions => [:ca,:au]},
|
||||||
{:mday => 11, :name => 'Armistice 1918', :regions => [:fr]},
|
{:mday => 11, :name => 'Armistice 1918', :regions => [:fr]},
|
||||||
{:mday => 1, :name => 'Touissant', :regions => [:fr]}],
|
{:mday => 1, :name => 'Touissant', :regions => [:fr]}],
|
||||||
12 => [{:mday => 25, :name => 'Christmas Day', :regions => [:us,:ca,:christ]},
|
12 => [{:mday => 25, :name => 'Christmas Day', :regions => [:us,:ca,:christian,:au]},
|
||||||
{:mday => 26, :name => 'Boxing Day', :regions => [:ca,:gr]}]
|
{:mday => 26, :name => 'Boxing Day', :regions => [:ca,:gr,:au]}]
|
||||||
}
|
}
|
||||||
|
|
||||||
# Get all holidays on a certain date
|
# Get all holidays on a certain date
|
||||||
def self.lookup_holidays(date, regions = [:ca, :us])
|
def self.by_day(date, regions = [:ca, :us])
|
||||||
#raise(UnkownRegionError, "No holiday information is available for region '#{region}'") unless known_region?(region)
|
#raise(UnkownRegionError, "No holiday information is available for region '#{region}'") unless known_region?(region)
|
||||||
|
|
||||||
regions = [regions] unless regions.kind_of?(Array)
|
regions = [regions] unless regions.kind_of?(Array)
|
||||||
|
|
||||||
hbm = HOLIDAYS_BY_MONTH[date.mon]
|
hbm = HOLIDAYS_BY_MONTH[date.mon]
|
||||||
|
|
||||||
holidays = []
|
holidays = []
|
||||||
|
@ -68,7 +74,7 @@ module Holidays
|
||||||
holidays << h
|
holidays << h
|
||||||
elsif h[:wday] == wday
|
elsif h[:wday] == wday
|
||||||
# by week calculation
|
# by week calculation
|
||||||
if calculate_mday(year, month, h[:week], h[:wday]) == mday
|
if Date.calculate_mday(year, month, h[:week], h[:wday]) == mday
|
||||||
holidays << h
|
holidays << h
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -77,6 +83,62 @@ module Holidays
|
||||||
holidays
|
holidays
|
||||||
end
|
end
|
||||||
|
|
||||||
|
#--
|
||||||
|
# TODO: do not take full months
|
||||||
|
def self.between(start_date, end_date, regions = [:ca,:us])
|
||||||
|
regions = [regions] unless regions.kind_of?(Array)
|
||||||
|
holidays = []
|
||||||
|
|
||||||
|
dates = {}
|
||||||
|
(start_date..end_date).each do |date|
|
||||||
|
dates[date.year] = Array.new unless dates[date.year]
|
||||||
|
# TODO: test this, maybe should push then flatten
|
||||||
|
dates[date.year] << date.month unless dates[date.year].include?(date.month)
|
||||||
|
end
|
||||||
|
|
||||||
|
dates.each do |year, months|
|
||||||
|
months.each do |month|
|
||||||
|
next unless hbm = HOLIDAYS_BY_MONTH[month]
|
||||||
|
hbm.each do |h|
|
||||||
|
next unless h[:regions].any?{ |reg| regions.include?(reg) }
|
||||||
|
|
||||||
|
day = h[:mday] || Date.calculate_mday(year, month, h[:week], h[:wday])
|
||||||
|
holidays << {:month => month, :day => day, :year => year, :name => h[:name], :regions => h[:regions]}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
#puts dates.inspect
|
||||||
|
holidays
|
||||||
|
end
|
||||||
|
|
||||||
|
# Get the date of Easter in a given year.
|
||||||
|
#
|
||||||
|
# +year+ must be a valid Gregorian year.
|
||||||
|
#--
|
||||||
|
# from http://snippets.dzone.com/posts/show/765
|
||||||
|
# TODO: check year to ensure Gregorian
|
||||||
|
def self.easter(year)
|
||||||
|
y = year
|
||||||
|
a = y % 19
|
||||||
|
b = y / 100
|
||||||
|
c = y % 100
|
||||||
|
d = b / 4
|
||||||
|
e = b % 4
|
||||||
|
f = (b + 8) / 25
|
||||||
|
g = (b - f + 1) / 3
|
||||||
|
h = (19 * a + b - d - g + 15) % 30
|
||||||
|
i = c / 4
|
||||||
|
k = c % 4
|
||||||
|
l = (32 + 2 * e + 2 * i - h - k) % 7
|
||||||
|
m = (a + 11 * h + 22 * l) / 451
|
||||||
|
month = (h + l - 7 * m + 114) / 31
|
||||||
|
day = ((h + l - 7 * m + 114) % 31) + 1
|
||||||
|
Date.civil(year, month, day)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
#def self.calculate_mday(yr, mo, wday, int)
|
#def self.calculate_mday(yr, mo, wday, int)
|
||||||
# earliest = 1 + 7 * (int - 1)
|
# earliest = 1 + 7 * (int - 1)
|
||||||
|
|
||||||
|
@ -94,6 +156,21 @@ module Holidays
|
||||||
# earliest + off
|
# earliest + off
|
||||||
# end
|
# end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
class Date
|
||||||
|
include Holidays
|
||||||
|
|
||||||
|
# Check if the current date is a holiday.
|
||||||
|
#
|
||||||
|
# Date.civil('2008-01-01').is_holiday?(:ca)
|
||||||
|
# => true
|
||||||
|
def is_holiday?(regions = [:ca, :us])
|
||||||
|
holidays = Holidays.by_day(self, regions)
|
||||||
|
holidays.empty?
|
||||||
|
end
|
||||||
|
|
||||||
# Calculate the day of the month based on week and day of the week.
|
# Calculate the day of the month based on week and day of the week.
|
||||||
#
|
#
|
||||||
# First Monday of Jan, 2008
|
# First Monday of Jan, 2008
|
||||||
|
@ -107,43 +184,23 @@ module Holidays
|
||||||
#--
|
#--
|
||||||
# see http://www.irt.org/articles/js050/index.htm
|
# see http://www.irt.org/articles/js050/index.htm
|
||||||
def self.calculate_mday(year, month, week, wday)
|
def self.calculate_mday(year, month, week, wday)
|
||||||
raise ArgumentError, "Week paramater must be one of Holidays::WEEKS" unless WEEKS.include?(week)
|
raise ArgumentError, "Week paramater must be one of Holidays::WEEKS." unless WEEKS.include?(week)
|
||||||
|
|
||||||
nth = WEEKS[week]
|
week = WEEKS[week]
|
||||||
|
|
||||||
if nth > 0
|
# :first, :second, :third, :fourth or :fifth
|
||||||
return (nth-1)*7 + 1 + (7 + wday - Date.civil(year, month,(nth-1)*7 + 1).wday)%7
|
if week > 0
|
||||||
|
return ((week - 1) * 7) + 1 + ((7 + wday - Date.civil(year, month,(week-1)*7 + 1).wday) % 7)
|
||||||
end
|
end
|
||||||
|
|
||||||
days = MONTH_LENGTHS[month]
|
days = MONTH_LENGTHS[month-1]
|
||||||
if month == 2 and Date.civil(year,1,1).leap?
|
if month == 1 and Date.civil(year,1,1).leap?
|
||||||
days = 29
|
days = 29
|
||||||
end
|
end
|
||||||
|
|
||||||
return days - (Date.civil(year, month, days).wday - wday + 8)%7;
|
return days - ((Date.civil(year, month, days).wday - wday + 7) % 7)
|
||||||
end
|
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
|
end
|
34
rakefile.rb
34
rakefile.rb
|
@ -13,3 +13,37 @@ Rake::TestTask.new do |t|
|
||||||
t.verbose = false
|
t.verbose = false
|
||||||
end
|
end
|
||||||
|
|
||||||
|
desc 'Generate documentation.'
|
||||||
|
Rake::RDocTask.new(:rdoc) do |rdoc|
|
||||||
|
rdoc.rdoc_dir = 'doc'
|
||||||
|
rdoc.title = 'Ruby Holidays Gem'
|
||||||
|
rdoc.options << '--all' << '--inline-source' << '--line-numbers'
|
||||||
|
rdoc.rdoc_files.include('README')
|
||||||
|
#rdoc.rdoc_files.include('LICENSE')
|
||||||
|
rdoc.rdoc_files.include('lib/*.rb')
|
||||||
|
#rdoc.rdoc_files.include('lib/holidays/*.rb')
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
spec = Gem::Specification.new do |s|
|
||||||
|
s.name = "holidays"
|
||||||
|
s.version = "0.9.0"
|
||||||
|
s.author = "Alex Dunae"
|
||||||
|
s.homepage = "http://code.dunae.ca/holidays"
|
||||||
|
s.platform = Gem::Platform::RUBY
|
||||||
|
s.description = <<-EOF
|
||||||
|
A collection of Ruby methods to deal with statutory and other holidays. You deserve a holiday!
|
||||||
|
EOF
|
||||||
|
s.summary = "A collection of Ruby methods to deal with statutory and other holidays. You deserve a holiday!"
|
||||||
|
s.files = FileList["{lib}/**/*"].to_a
|
||||||
|
s.test_files = Dir.glob('test/test_*.rb')
|
||||||
|
s.has_rdoc = true
|
||||||
|
s.extra_rdoc_files = ["README", "LICENSE"]
|
||||||
|
s.rdoc_options << '--all' << '--inline-source' << '--line-numbers'
|
||||||
|
end
|
||||||
|
|
||||||
|
desc 'Build the gem.'
|
||||||
|
Rake::GemPackageTask.new(spec) do |pkg|
|
||||||
|
pkg.need_zip = true
|
||||||
|
pkg.need_tar = true
|
||||||
|
end
|
|
@ -11,57 +11,85 @@ class HolidayTests < Test::Unit::TestCase
|
||||||
assert @date.respond_to?('is_holiday?')
|
assert @date.respond_to?('is_holiday?')
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_calculating_mdays
|
def test_date_ranges
|
||||||
assert_equal 21, Holidays.calculate_mday(2008, 1, :third, 1)
|
holidays = Holidays.between(Date.civil(2008,1,1), Date.civil(2008,12,31), :au)
|
||||||
assert_equal 1, Holidays.calculate_mday(2007, 1, :first, 1)
|
holidays.each do |h|
|
||||||
assert_equal 2, Holidays.calculate_mday(2007, 3, :first, 5)
|
#puts h.inspect
|
||||||
assert_equal 25, Holidays.calculate_mday(2008, 5, :last, 1)
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_easter_dates
|
||||||
|
assert_equal '1800-04-13', Holidays.easter(1800).to_s
|
||||||
|
assert_equal '1899-04-02', Holidays.easter(1899).to_s
|
||||||
|
assert_equal '1900-04-15', Holidays.easter(1900).to_s
|
||||||
|
assert_equal '1999-04-04', Holidays.easter(1999).to_s
|
||||||
|
assert_equal '2000-04-23', Holidays.easter(2000).to_s
|
||||||
|
assert_equal '2025-04-20', Holidays.easter(2025).to_s
|
||||||
|
assert_equal '2035-03-25', Holidays.easter(2035).to_s
|
||||||
|
assert_equal '2067-04-03', Holidays.easter(2067).to_s
|
||||||
|
assert_equal '2099-04-12', Holidays.easter(2099).to_s
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
def test_calculating_mdays
|
||||||
|
# US Memorial day
|
||||||
|
assert_equal 29, Date.calculate_mday(2006, 5, :last, 1)
|
||||||
|
assert_equal 28, Date.calculate_mday(2007, 5, :last, 1)
|
||||||
|
assert_equal 26, Date.calculate_mday(2008, 5, :last, 1)
|
||||||
|
assert_equal 25, Date.calculate_mday(2009, 5, :last, 1)
|
||||||
|
assert_equal 31, Date.calculate_mday(2010, 5, :last, 1)
|
||||||
|
assert_equal 30, Date.calculate_mday(2011, 5, :last, 1)
|
||||||
|
|
||||||
# Labour day
|
# Labour day
|
||||||
assert_equal 3, Holidays.calculate_mday(2007, 9, :first, 1)
|
assert_equal 3, Date.calculate_mday(2007, 9, :first, 1)
|
||||||
assert_equal 1, Holidays.calculate_mday(2008, 9, :first, 1)
|
assert_equal 1, Date.calculate_mday(2008, 9, :first, 1)
|
||||||
assert_equal 7, Holidays.calculate_mday(2009, 9, :first, 1)
|
assert_equal 7, Date.calculate_mday(2009, 9, :first, 1)
|
||||||
assert_equal 5, Holidays.calculate_mday(2011, 9, :first, 1)
|
assert_equal 5, Date.calculate_mday(2011, 9, :first, 1)
|
||||||
assert_equal 5, Holidays.calculate_mday(2050, 9, :first, 1)
|
assert_equal 5, Date.calculate_mday(2050, 9, :first, 1)
|
||||||
assert_equal 4, Holidays.calculate_mday(2051, 9, :first, 1)
|
assert_equal 4, Date.calculate_mday(2051, 9, :first, 1)
|
||||||
|
|
||||||
# Canadian thanksgiving
|
# Canadian thanksgiving
|
||||||
assert_equal 8, Holidays.calculate_mday(2007, 10, :second, 1)
|
assert_equal 8, Date.calculate_mday(2007, 10, :second, 1)
|
||||||
assert_equal 13, Holidays.calculate_mday(2008, 10, :second, 1)
|
assert_equal 13, Date.calculate_mday(2008, 10, :second, 1)
|
||||||
assert_equal 12, Holidays.calculate_mday(2009, 10, :second, 1)
|
assert_equal 12, Date.calculate_mday(2009, 10, :second, 1)
|
||||||
assert_equal 11, Holidays.calculate_mday(2010, 10, :second, 1)
|
assert_equal 11, Date.calculate_mday(2010, 10, :second, 1)
|
||||||
|
|
||||||
|
# Misc
|
||||||
|
assert_equal 21, Date.calculate_mday(2008, 1, :third, 1)
|
||||||
|
assert_equal 1, Date.calculate_mday(2007, 1, :first, 1)
|
||||||
|
assert_equal 2, Date.calculate_mday(2007, 3, :first, 5)
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_region_params
|
def test_region_params
|
||||||
holidays = Holidays.lookup_holidays(@date, :us)
|
holidays = Holidays.by_day(@date, :us)
|
||||||
assert_equal 1, holidays.length
|
assert_equal 1, holidays.length
|
||||||
|
|
||||||
holidays = Holidays.lookup_holidays(@date, [:us,:ca])
|
holidays = Holidays.by_day(@date, [:us,:ca])
|
||||||
assert_equal 1, holidays.length
|
assert_equal 1, holidays.length
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_lookup_holidays_spot_checks
|
def test_by_day_spot_checks
|
||||||
h = Holidays.lookup_holidays(Date.civil(2008,5,1), :gr)
|
h = Holidays.by_day(Date.civil(2008,5,1), :gr)
|
||||||
assert_equal 'Labour Day', h[0][:name]
|
assert_equal 'Labour Day', h[0][:name]
|
||||||
|
|
||||||
h = Holidays.lookup_holidays(Date.civil(2045,11,1), :fr)
|
h = Holidays.by_day(Date.civil(2045,11,1), :fr)
|
||||||
assert_equal 'Touissant', h[0][:name]
|
assert_equal 'Touissant', h[0][:name]
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_lookup_holidays_and_iterate
|
def test_by_day_and_iterate
|
||||||
holidays = Holidays.lookup_holidays(@date, :ca)
|
holidays = Holidays.by_day(@date, :ca)
|
||||||
holidays.each do |h|
|
holidays.each do |h|
|
||||||
puts h[:name]
|
#puts h[:name]
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_lookup_holiday
|
def test_lookup_holiday
|
||||||
holidays = Holidays.lookup_holidays(Date.civil(2008,1,21), :ca)
|
holidays = Holidays.by_day(Date.civil(2008,1,21), :ca)
|
||||||
assert_equal 0, holidays.length
|
assert_equal 0, holidays.length
|
||||||
|
|
||||||
holidays = Holidays.lookup_holidays(Date.civil(2008,1,21), :us)
|
holidays = Holidays.by_day(Date.civil(2008,1,21), :us)
|
||||||
assert_equal 1, holidays.length
|
assert_equal 1, holidays.length
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue