require 'set' require 'date' module UK class BankHoliday # Test if date is a Bank Holiday, e.g. # # UK::BankHoliday === Date.new(2007, 1, 1) # => true # def self.===(date) !!self[date] end # Lookup information about a particular Bank Holiday, e.g. # # hol = UK::BankHoliday[Date.new(2007, 1, 1)] # # hol.name # => "New Year's Day" # def self.[](date) holidays.detect do |holiday| unless country_of_interest.nil? next unless holiday.celebrated_in?(country_of_interest) end holiday.matches? date end end # Find the next (n) Bank Holiday(s). # def self.next(n = 1) holidays, date = [], Date.today while n > 0 if holiday = self[date] holidays << [date, holiday] n -= 1 end date = date.succ end return *holidays.first if 1 == n return holidays end COUNTRIES = Set.new([:England, :Wales, :Scotland, :Northern_Ireland, :Republic_of_Ireland]) # Only consider holidays celebrated in a particular country, e.g. # # UK::BankHoliday === Date.new(2006, 3, 17) # => true # # UK::BankHoliday.country = :Scotland # # UK::BankHoliday === Date.new(2006, 3, 17) # => false # def self.country=(country) unless COUNTRIES.include? country raise "%p not in %p" % [country, COUNTRIES] end @@country_of_interest = country end attr_reader :name, :countries alias :to_s :name def initialize(name, restrictions = {}, &pattern) @name, @pattern = name, pattern @countries = if restrictions.has_key? :in Set.new(restrictions[:in]) else COUNTRIES - Set.new(restrictions[:except_in]) end end def celebrated_in?(country) @countries.include? country end def matches?(date) @pattern[date] end private def self.country_of_interest @@country_of_interest ||= nil end def self.holidays @@holidays ||= [] end def self.const_missing(symbol) name = symbol.to_s.downcase if month = Date::MONTHS[name] const_set symbol, month elsif day = Date::DAYS[name] const_set symbol, day else super symbol end end def self.celebrate(*args, &block) holidays << new(*args, &block) end def self.find_first(wday, month, year) date = Date.new(year, month, 1) until wday == date.wday date = date.succ end return date end def self.find_last(wday, month, year) date = Date.new(year, month + 1, 1) date = date - 1 until wday == date.wday date = date - 1 end return date end # c.f. http://en.wikipedia.org/wiki/Bank_Holiday#Current_Bank_Holidays celebrate 'New Year\'s Day' do |date| date.month == January && date.day == 1 end celebrate 'Second of January', :in => [:Scotland] do |date| date.month == January && date.day == 2 end celebrate 'St. Patrick\'s Day', :in => [:Northern_Ireland, :Republic_of_Ireland] do |date| date.month == March && date.day == 17 end celebrate 'Good Friday', :except_in => [:Republic_of_Ireland] do |date| (Computus.calculate(date.year) - 2) == date end celebrate 'Easter Monday', :except_in => [:Scotland] do |date| (Computus.calculate(date.year) + 1) == date end celebrate 'May Day' do |date| find_first(Monday, May, date.year) == date end celebrate 'Spring Bank Holiday', :except_in => [:Republic_of_Ireland] do |date| find_last(Monday, May, date.year) == date end celebrate 'June Bank Holiday', :in => [:Republic_of_Ireland] do |date| find_first(Monday, June, date.year) == date end celebrate 'Orangemen\'s Day', :in => [:Northern_Ireland] do |date| date.month == July && date.day == 12 end celebrate 'Summer Bank Holiday', :in => [:Scotland, :Republic_of_Ireland] do |date| find_first(Monday, August, date.year) == date end celebrate 'Summer Bank Holiday', :except_in => [:Scotland, :Republic_of_Ireland] do |date| find_last(Monday, August, date.year) == date end celebrate 'October Bank Holiday', :in => [:Republic_of_Ireland] do |date| find_last(Monday, October, date.year) == date end celebrate 'Christmas Day' do |date| date.month == December && date.day == 25 end celebrate 'Boxing Day' do |date| date.month == December && date.day == (date.wday == Monday ? 27 : 26) end end end module Computus # Meeus/Jones/Butcher Gregorian algorithm (valid for all Gregorian years). # # c.f. http://en.wikipedia.org/wiki/Computus # def self.calculate(year = Date.today.year) (@dates ||= {})[year] ||= begin a = year % 19 b = year / 100 c = year % 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 x = h + l - 7 * m + 114 month = x / 31 day = (x % 31) + 1 Date.new(year, month, day) end end end if __FILE__ == $0 then puts 'The next 3 UK Bank Holidays are:' UK::BankHoliday.next(3).each do |(date, holiday)| puts " [%s] %s (%s)" % [date, holiday.name, holiday.countries.map { |c| c.to_s[0,1] }.join(', ')] end end