UK Bank Holidays
NUnit Tests and USA Federal Holidays version. Updated for 2012 Golden Jubilee and 2011 Royal Wedding.
Bugs fixed, thanks to orestis.
using System;
using System.Collections.Generic;
namespace Library.PublicHoliday
{
/// <summary>
/// Finds UK Bank (public) Holidays. Adjusted for weekends.
/// <description>
/// UK Bank Holidays since 1971 Banking and Financial Dealings Act with additions and variations.
/// See http://www.dti.gov.uk/employment/bank-public-holidays/index.html
/// <para>Additions: 1974 New Years Day and 1978 May Day</para>
/// <para>Variations: 1995 VE Day May Day, 2002 Golden Jubilee, 2011 Royal Wedding, 2012 Diamond Jubilee</para>
/// <para>You can call by IsBankHoliday(date), get the specific holiday name
/// ( <see cref="Christmas"/>), or a list of dates for the year (<see cref="BankHolidays"/>)</para>
/// </description>
/// <example>
/// listBox1.DataSource = UKBankHoliday.BankHolidays(2006);
/// //fills listbox with 8 dates- 02/01, 14/04, 17/04, 01/05, 29/05, 28/08, 25/12, 26/12
///</example>
/// </summary>
public static class UKBankHoliday
{
#region Holiday Adjustments
private static DateTime FixWeekend(DateTime hol)
{
if (hol.DayOfWeek == DayOfWeek.Sunday)
hol = hol.AddDays(1);
else if (hol.DayOfWeek == DayOfWeek.Saturday)
hol = hol.AddDays(2);
return hol;
}
private static DateTime FindFirstMonday(DateTime hol)
{
while (hol.DayOfWeek != DayOfWeek.Monday)
{
hol = hol.AddDays(1);
}
return hol;
}
#endregion
#region Individual Holidays
/// <summary>
/// Christmas day
/// </summary>
/// <param name="year"></param>
/// <returns></returns>
public static DateTime Christmas(int year)
{
DateTime hol = new DateTime(year, 12, 25);
hol = FixWeekend(hol);
return hol;
}
/// <summary>
/// Boxing Day
/// </summary>
/// <param name="year"></param>
/// <returns></returns>
public static DateTime BoxingDay(int year)
{
DateTime hol = new DateTime(year, 12, 26);
//if Xmas=Sun, it's shifted to Mon and 26 also gets shifted
bool isSundayOrMonday =
hol.DayOfWeek == DayOfWeek.Sunday ||
hol.DayOfWeek == DayOfWeek.Monday;
hol = FixWeekend(hol);
if (isSundayOrMonday)
hol = hol.AddDays(1);
return hol;
}
/// <summary>
/// Date of New Year bank holiday. This is 1974 on only but will return pre 1974 dates.
/// </summary>
/// <param name="year"></param>
/// <returns></returns>
public static DateTime NewYear(int year)
{
//since 1974 only
DateTime hol = new DateTime(year, 1, 1);
hol = FixWeekend(hol);
return hol;
}
/// <summary>
/// Returns "Early Spring"/"May Day" holiday (first Monday in May). Created in 1978.
/// </summary>
/// <param name="year"></param>
/// <returns>(Nullable)date for Early May Bank Holiday (null before 1978)</returns>
public static DateTime? MayDay(int year)
{
//warning- should be null for < 1977
if (year < 1978) return null;
if (year == 1995)
return new DateTime(1995, 5, 8); //1995 moved for 50th anniversary of VE day
DateTime hol = new DateTime(year, 5, 1);
hol = FindFirstMonday(hol);
return hol;
}
/// <summary>
/// The Spring/Last Monday in May holiday (replaced variable Whit Monday in 1971)
/// </summary>
/// <param name="year"></param>
/// <returns></returns>
public static DateTime Spring(int year)
{
if (year == 2002) return new DateTime(2002, 6, 4); //Golden Jubilee of Elizabeth II
if (year == 2012) return new DateTime(2012, 6, 4); //Queen's Diamond Jubilee
DateTime hol = new DateTime(year, 5, 24);
hol = FindFirstMonday(hol);
return hol;
}
/// <summary>
/// Summer bank holiday (last Monday in August)
/// </summary>
/// <param name="year"></param>
/// <returns></returns>
public static DateTime Summer(int year)
{
DateTime hol = new DateTime(year, 8, 25);
hol = FindFirstMonday(hol);
return hol;
}
/// <summary>
/// Good Friday (Friday before Easter)
/// </summary>
/// <param name="year"></param>
/// <returns></returns>
public static DateTime GoodFriday(int year)
{
DateTime hol = GetEaster(year);
hol = hol.AddDays(-2);
return hol;
}
/// <summary>
/// Easter Monday (Monday after Easter)
/// </summary>
/// <param name="year"></param>
/// <returns></returns>
public static DateTime EasterMonday(int year)
{
DateTime hol = GetEaster(year);
hol = hol.AddDays(1);
return hol;
}
/// <summary>
/// Private overloads of GoodFriday and EasterMonday reusing Easter calculation
/// </summary>
private static DateTime GoodFriday(DateTime easter)
{
DateTime hol = easter.AddDays(-2);
return hol;
}
private static DateTime EasterMonday(DateTime easter)
{
DateTime hol = easter.AddDays(1);
return hol;
}
#endregion
/// <summary>
/// Get a list of dates for all holidays in a year.
/// </summary>
/// <param name="year">The year</param>
/// <returns>List of bank holidays</returns>
public static IList<DateTime> BankHolidays(int year)
{
List<DateTime> bHols = new List<DateTime>();
if (year > 1973)
bHols.Add(NewYear(year)); //New Year only in 1974
DateTime easter = GetEaster(year);
bHols.Add(GoodFriday(easter));
bHols.Add(EasterMonday(easter));
DateTime? dt = MayDay(year);
if (dt.HasValue)
bHols.Add(dt.Value);
bHols.Add(Spring(year));
if (year == 2002)
bHols.Add(new DateTime(2002, 6, 3)); //Golden Jubilee of Elizabeth II
if (year == 2011)
bHols.Add(new DateTime(2012, 4, 29)); //Royal Wedding
if (year == 2012)
bHols.Add(new DateTime(2012, 6, 5)); //Queen's Diamond Jubilee
bHols.Add(Summer(year));
bHols.Add(Christmas(year));
bHols.Add(BoxingDay(year));
return bHols;
}
/// <summary>
/// Get a list of dates for all holidays in a year.
/// </summary>
/// <param name="year">The year</param>
/// <returns>Dictionary of bank holidays</returns>
public static IDictionary<DateTime, string> BankHolidayNames(int year)
{
var bHols = new Dictionary<DateTime, string>();
if (year > 1973)
bHols.Add(NewYear(year), "New Year"); //New Year only in 1974
DateTime easter = GetEaster(year);
bHols.Add(GoodFriday(easter), "Good Friday");
bHols.Add(EasterMonday(easter), "Easter Monday");
if (year == 2011)
bHols.Add(new DateTime(2011, 4, 29), "Royal Wedding"); //Royal Wedding
DateTime? dt = MayDay(year);
if (dt.HasValue)
bHols.Add(dt.Value, "Early May");
bHols.Add(Spring(year), "Spring");
if (year == 2002)
bHols.Add(new DateTime(2002, 6, 3), "Golden Jubilee"); //Golden Jubilee of Elizabeth II
if (year == 2012)
bHols.Add(new DateTime(2012, 6, 5), "Queen's Diamond Jubilee"); //Queen's Diamond Jubilee
bHols.Add(Summer(year), "Summer");
bHols.Add(Christmas(year), "Christmas");
bHols.Add(BoxingDay(year), "Boxing Day");
return bHols;
}
/// <summary>
/// Returns the next working day (Mon-Fri, not bank holiday)
/// after the specified date (or the same date)
/// </summary>
/// <param name="dt">The date you wish to check</param>
/// <returns>A date that is a working day</returns>
public static DateTime NextWorkingDay(DateTime dt)
{
bool isWorkingDay = false;
//loops through
//(not ideal as a March/April date that hits Easter Friday
//will have to calculate Easter three times-
// first for the Friday, then for Easter Monday, then again to clear the Tuesday.
// Easter Monday will calculate twice.
// To avoid the recalculation, make these methods non-static and cache the date)
while (isWorkingDay == false)
{
//Mon-Fri and not bank holiday, it's okay
if (dt.DayOfWeek != DayOfWeek.Saturday &&
dt.DayOfWeek != DayOfWeek.Sunday &&
!IsBankHoliday(dt))
isWorkingDay = true;
//it's Saturday, so skip to Monday
else if (dt.DayOfWeek == DayOfWeek.Saturday)
dt = dt.AddDays(2);
//it's Sunday, so skip to Monday
else if (dt.DayOfWeek == DayOfWeek.Sunday)
dt = dt.AddDays(1);
//it's Friday (bank holiday), so skip to Monday
else if (dt.DayOfWeek == DayOfWeek.Friday)
dt = dt.AddDays(3);
//it's Mon-Thu (bank holiday), so next day
else
dt = dt.AddDays(1);
//any of the addDays should now loop and retest
//only Good Friday and Christmas Day should loop twice
//(2 adjacent bank holidays)
}
return dt;
}
/// <summary>
/// Check if a specific date is a bank holiday.
/// Obviously the BankHoliday list is more efficient for repeated checks
/// </summary>
/// <param name="dt">The date you wish to check</param>
/// <returns>True if date is a bank holiday (excluding weekends)</returns>
public static bool IsBankHoliday(DateTime dt)
{
int year = dt.Year;
switch (dt.Month)
{
case 1:
if ((year > 1973) && (NewYear(year) == dt))
return true;
break;
case 3:
case 4:
//only Mondays and Fridays are bank holidays
if (dt.DayOfWeek != DayOfWeek.Monday &&
dt.DayOfWeek != DayOfWeek.Friday)
return false;
//easter can be in March or April
DateTime easter = GetEaster(year);
if (GoodFriday(easter) == dt)
return true;
if (EasterMonday(easter) == dt)
return true;
break;
case 5:
if (dt.DayOfWeek != DayOfWeek.Monday)
return false;
if (MayDay(year) == dt)
return true;
if (Spring(year) == dt)
return true;
break;
case 8:
if (dt.DayOfWeek != DayOfWeek.Monday)
return false;
if (Summer(year) == dt)
return true;
break;
case 12:
if (Christmas(year) == dt)
return true;
if (BoxingDay(year) == dt)
return true;
break;
}
if ((year == 2002) &&
(dt.Month == 6) &&
((dt.Day == 3) || (dt.Day == 4)))
return true; //Golden Jubilee of Elizabeth II
if ((year == 2011) &&
(dt.Month == 4) &&
((dt.Day == 29)))
return true; //Royal Wedding
if ((year == 2012) &&
(dt.Month == 6) &&
((dt.Day == 5)))
return true; //Queen's Diamond Jubilee
return false;
}
/// <summary>
/// Work out the date for Easter Sunday for specified year
/// </summary>
/// <param name="year">The year as an integer</param>
/// <returns>Returns a datetime of Easter Sunday.</returns>
private static DateTime GetEaster(int year)
{
//should be
//Easter Monday 28 Mar 2005 17 Apr 2006 9 Apr 2007 24 Mar 2008
//Oudin's Algorithm - http://www.smart.net/~mmontes/oudin.html
int g = year % 19;
int c = year / 100;
int h = (c - c / 4 - (8 * c + 13) / 25 + 19 * g + 15) % 30;
int i = h - (h / 28) * (1 - (h / 28) * (29 / (h + 1)) * ((21 - g) / 11));
int j = (year + year / 4 + i + 2 - c + c / 4) % 7;
int p = i - j;
int easterDay = 1 + (p + 27 + (p + 6) / 40) % 31;
int easterMonth = 3 + (p + 26) / 30;
return new DateTime(year, easterMonth, easterDay);
}
}
}