Need code for finding the last working day of the month (with holidays taken into account)

I need some .NET code (a function) which returns the last working day of any given month and year. This needs to also take into account any holidays so that it selects the actual last working day (or if it returns a series of working-day dates, that would work too - then I can just select the one I'm after). I've hunted around and came up dry. Such code must exist though I'm sure (I just didn't want to go reinvent the wheel if it's already out there).
LVL 15
David L. HansenProgrammer AnalystAsked:
Who is Participating?
I wear a lot of hats...

"The solutions and answers provided on Experts Exchange have been extremely helpful to me over the last few years. I wear a lot of hats - Developer, Database Administrator, Help Desk, etc., so I know a lot of things but not a lot about one thing. Experts Exchange gives me answers from people who do know a lot about one thing, in a easy to use platform." -Todd S.

käµfm³d 👽Commented:
Coincidentally enough, I've got some code that I've been working on for an article write-up that I'll be adding to EE. When I get home this evening, I'll post some of the code that should help you.
0
David L. HansenProgrammer AnalystAuthor Commented:
Thanks, Kaufmed! You are a life saver :)
0
käµfm³d 👽Commented:
To confirm:  You mention taking holidays into account, but you consider weekends (i.e. Saturday and Sunday) to be non-working days, yes? The route that I am taking in my code considers weekends and holidays to be non-working days.
0
Cloud Class® Course: C++ 11 Fundamentals

This course will introduce you to C++ 11 and teach you about syntax fundamentals.

David L. HansenProgrammer AnalystAuthor Commented:
Yes, that's right. Exactly.

Kaufmed, one other thing (it is by no means a deal breaker). States and Providences will often have their own "extra" observed holidays in addition to the Federal ones -- I figure a custom "holiday" enum or database table would need to be used to capture these as well. Anyway, will your solution deal with this issue as well? Again, this is by no means a major concern.
0
it_saigeDeveloperCommented:
Based on the work completed by Lonnie Franklin (http://lonniefranklin.com/c-helpful-datetime-extensions-two-holiday-classes/).  I have this implementation that I use from time to time:
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Runtime.InteropServices;

namespace System
{
	[ComVisible(true)]
	[Serializable]
	public enum MonthOfYear
	{
		January,
		February,
		March,
		April,
		May,
		June,
		July,
		August,
		September,
		October,
		November,
		December
	}

	[ComVisible(true)]
	[Serializable]
	public enum WeekOfMonth
	{
		First = 0,
		Second = 1,
		Third = 2,
		Fourth = 3,
		Last = 4
	}
}

namespace EE_Q28694411
{
	class Program
	{
		static void Main(string[] args)
		{
			for (int i = 0; i < 12; i++)
				Console.WriteLine("The last working day in {0} is? {1}", (MonthOfYear)i, new DateTime(2015, i + 1, 1).GetLastWorkingDay().ToShortDateString());
			Console.ReadLine();
		}
	}

	static class Extensions
	{
		/// <summary>Gets the next date from the year, month and day of current value.</summary>
		/// <param name="value">The value.</param>
		/// <param name="dayOfWeek">The day of week.</param>
		/// <returns>DateTime.</returns>
		public static DateTime Get(this DateTime value, DayOfWeek dayOfWeek)
		{
			var e = (int)dayOfWeek;
			var s = (int)value.DayOfWeek;

			return s < e ? value.AddDays(e - s) : value.AddDays(-(s - e)).AddDays(7);
		}

		/// <summary>Gets the next date from the year and month of current date.</summary>
		/// <param name="value">The value.</param>
		/// <param name="weekOfMonth">The week of month.</param>
		/// <param name="dayOfWeek">The day of week.</param>
		/// <returns>DateTime.</returns>
		public static DateTime Get(this DateTime value, WeekOfMonth weekOfMonth, DayOfWeek dayOfWeek)
		{
			var startDate = new DateTime(value.Year, value.Month, 1);
			var date = startDate;
			var i = (int)weekOfMonth;

			if (date.DayOfWeek == dayOfWeek && weekOfMonth == WeekOfMonth.First)
				return date;

			if (date.DayOfWeek == dayOfWeek)
				return date.AddDays((int)weekOfMonth * 7);

			var e = (int)dayOfWeek;
			var s = (int)date.DayOfWeek;

			if (s < e)
				date = date.AddDays(e - s);
			else
			{
				date = date.AddDays(-s);

				if (date.Month != startDate.Month)
					i++;

				date = date.AddDays(e);
			}

			date = date.AddDays(i * 7);

			return date.Month != startDate.Month ? date.AddDays(-7) : date;
		}

		/// <summary>Gets the next date from the year of current date.</summary>
		/// <param name="value">The value.</param>
		/// <param name="monthOfYear">The month of year.</param>
		/// <param name="weekOfMonth">The week of month.</param>
		/// <param name="dayOfWeek">The day of week.</param>
		/// <returns>DateTime.</returns>
		public static DateTime Get(this DateTime value, MonthOfYear monthOfYear, WeekOfMonth weekOfMonth, DayOfWeek dayOfWeek)
		{
			var dateTime = new DateTime(value.Year, (int)monthOfYear + 1, 1);
			return dateTime.Get(weekOfMonth, dayOfWeek);
		}

		/// <summary>Gets the next date.</summary>
		/// <param name="value">The value.</param>
		/// <param name="year">The year.</param>
		/// <param name="monthOfYear">The month of year.</param>
		/// <param name="weekOfMonth">The week of month.</param>
		/// <param name="dayOfWeek">The day of week.</param>
		/// <returns>DateTime.</returns>
		public static DateTime Get(this DateTime value, int year, MonthOfYear monthOfYear, WeekOfMonth weekOfMonth, DayOfWeek dayOfWeek)
		{
			var dateTime = new DateTime(year, (int)monthOfYear + 1, 1);
			return dateTime.Get(weekOfMonth, dayOfWeek);
		}

		/// <summary>Gets the observed day.</summary>
		/// <param name="value">The value.</param>
		/// <returns></returns>
		public static DateTime GetObservedDay(this DateTime value)
		{
			var date = new DateTime(value.Year, value.Month, value.Day);
 			
			switch (value.DayOfWeek)
			{
				case DayOfWeek.Sunday:
					date = date.AddDays(1);
					break;
				case DayOfWeek.Saturday:
					date = date.AddDays(-1);
					break;
			}
			return date;
		}

		/// <summary>Gets Good Friday.</summary>
		/// <param name="value">The value.</param>
		/// <param name="year">The year.</param>
		/// <returns>DateTime.</returns>
		/// <value>The good friday.</value>
		public static DateTime GetGoodFriday(this GregorianCalendar value, int year)
		{
			return value.GetEasterSunday(year).AddDays(-2);
		}

		/// <summary>Gets Easter Sunday.
		/// As noted on : http://www.assa.org.au/edm.html#Computer.
		/// Slightly modified for C#.</summary>
		/// <param name="value">The value.</param>
		/// <param name="year">The year.</param>
		/// <returns>DateTime.</returns>
		/// <value>The Easter Sunday.</value>
		public static DateTime GetEasterSunday(this GregorianCalendar value, int year)
		{
			// EASTER DATE CALCULATION FOR YEARS 1583 TO 4099

			//first 2 digits of year
			int firstDig = year / 100;
			//remainder of year / 19
			int remain19 = year % 19;

			// calculate PFM date
			int temp = (firstDig - 15) / 2 + 202 - 11 * remain19;

			switch (firstDig)
			{
				case 21:
				case 24:
				case 25:
				case 27:
				case 28:
				case 29:
				case 30:
				case 31:
				case 32:
				case 34:
				case 35:
				case 38:
					temp = temp - 1;
					break;
				case 33:
				case 36:
				case 37:
				case 39:
				case 40:
					temp = temp - 2;
					break;
			}
			temp = temp % 30;

			//table A to E results
			int tA = temp + 21;
			if ((temp == 29) || (temp == 28 & remain19 > 10))
				tA -= 1;

			int tB = (tA - 19) % 7;

			int tC = (40 - firstDig) % 4;
			if (tC == 3 || tC > 1)
				tC++;

			temp = year % 100;
			int tD = (temp + temp / 4) % 7;

			int tE = ((20 - tB - tC - tD) % 7) + 1;

			//find the next Sunday
			int d = tA + tE;
			int m = 3;

			if (d > 31)
			{
				d -= 31;
				m++;
			}

			return new DateTime(year, m, d);
		}

		public static DateTime GetLastWorkingDay(this DateTime date)
		{
			var holidaysInMonth = (from holiday in new UnitedStatesHolidaySchedule(UnitedStatesHolidayScheduleTypes.Federal, date.Year).GetObservedHolidays() 
							   where holiday.Month.Equals(date.Month) 
							   select holiday);
			var lastWorkingDay = (from day in Enumerable.Range(1, DateTime.DaysInMonth(date.Year, date.Month)) 
							  let now = new DateTime(date.Year, date.Month, day) 
							  where (!now.DayOfWeek.Equals(DayOfWeek.Saturday) || !now.DayOfWeek.Equals(DayOfWeek.Sunday)) && !holidaysInMonth.Contains(now) 
							  select now).LastOrDefault();
			return lastWorkingDay;
		}
	}

	/// <summary>Specifies the Holiday Calendar type used in the United States</summary>
	[ComVisible(true)]
	[Serializable]
	public enum UnitedStatesHolidayScheduleTypes : byte
	{
		/// <summary>Indicates the Federal holiday schedule.</summary>
		Federal = 0,
		/// <summary>Indicates the Stock Market holiday schedule.</summary>
		StockMarket = 1
	}

	public class UnitedStatesHolidaySchedule : GregorianCalendar
	{
		private readonly List<DateTime> _holidays;
		private readonly List<DateTime> _observedHolidays;

		/// <summary>Gets or sets the year.</summary>
		/// <value>The year.</value>
		public int Year { get; protected set; }

		/// <summary>Gets or sets the <see cref="T:System.Globalization.GregorianCalendarTypes" /> value that denotes the language version of the current <see cref="T:System.Globalization.GregorianCalendar" />.</summary>
		/// <value>The type of the united states holiday schedule.</value>
		public UnitedStatesHolidayScheduleTypes UnitedStatesHolidayScheduleType { get; protected set; }

		/// <summary>Initializes a new instance of the <see cref="UnitedStatesHolidaySchedule"/> class.</summary>
		protected UnitedStatesHolidaySchedule()
		{
			_holidays = new List<DateTime>();
			_observedHolidays = new List<DateTime>();
		}

		/// <summary>Initializes a new instance of the <see cref="UnitedStatesHolidaySchedule" /> class.</summary>
		/// <param name="unitedStatesHolidayScheduleType">Type of the calendar.</param>
		/// <param name="year">The year.</param>
		public UnitedStatesHolidaySchedule(UnitedStatesHolidayScheduleTypes unitedStatesHolidayScheduleType, int year) : this()
		{
			Year = year;
			UnitedStatesHolidayScheduleType = unitedStatesHolidayScheduleType;

			if (unitedStatesHolidayScheduleType == UnitedStatesHolidayScheduleTypes.StockMarket)
			{
				#region StockMarket
				/*
                U.S. stock markets are closed on nine regularly scheduled holidays each year:
                 1.  New Years Day - first of January for Monday through Saturday.
                      If it falls on Sunday, market is closed on Monday January second.
                      Every N years new years day falls on a Saturday, when this
                      happens there is NO closing of U.S stock markets for new years day.
                 2.  Dr. Martin Luther King day - third Monday in January (15-21).
                 3.  President's Day - always the third Monday in February (15-21).
                 4.  Good Friday - always on a Friday - the Friday before Easter Sunday.
                      Varies from late March to mid April.
                 5.  Memorial Day - always the last Monday in May (25-31).
                 6.  Independence Day - fourth of July for Monday through Friday.
                      If it falls on Saturday, market is closed on Friday the third.
                      If it falls on Sunday, market is closed on Monday the fifth.
                 7.  Labor Day - always on the first Monday in September (1-7).
                 8.  Thanksgiving Day - always on the fourth Thursday in November (22-28).
                 9.  Christmas Day - twenty-fifth of December for Monday through Friday.
                      If it falls on Saturday, market is closed on Friday the twenty-fourth.
                      If it falls on Sunday, market is closed on Monday the twenty-sixth.
                 */

				_holidays.Add(new DateTime(Year, 1, 1, 0, 0, 0));
				_holidays.Add(new DateTime(Year, 1, 1, 0, 0, 0).Get(WeekOfMonth.Third, DayOfWeek.Monday));
				_holidays.Add(new DateTime(Year, 2, 1, 0, 0, 0).Get(WeekOfMonth.Third, DayOfWeek.Monday));
				_holidays.Add((new GregorianCalendar()).GetGoodFriday(year));
				_holidays.Add(new DateTime(Year, 5, 1, 0, 0, 0).Get(WeekOfMonth.Last, DayOfWeek.Monday));
				_holidays.Add(new DateTime(Year, 7, 4, 0, 0, 0));
				_holidays.Add(new DateTime(Year, 9, 1, 0, 0, 0).Get(WeekOfMonth.First, DayOfWeek.Monday));
				_holidays.Add(new DateTime(Year, 11, 1, 0, 0, 0).Get(WeekOfMonth.Fourth, DayOfWeek.Thursday));
				_holidays.Add(new DateTime(Year, 11, 1, 0, 0, 0).Get(WeekOfMonth.Fourth, DayOfWeek.Friday));
				_holidays.Add(new DateTime(Year, 12, 25, 0, 0, 0));

				foreach (var dateTime in _holidays)
				{
					if ((dateTime.Month == 1 && dateTime.Day == 1 && dateTime.DayOfWeek == DayOfWeek.Saturday))
						continue;

					_observedHolidays.Add(dateTime.GetObservedDay());
				}
				#endregion
			}
			else //Federal
			{
				#region Federal
				/*
                http://www.opm.gov/operating_status_schedules/fedhol/2012.asp
                1.  New Years Day - first of January for Monday through Saturday.
                     If it falls on Saturday, Friday is observed
                     If it falls on Sunday, Monday is observed
                2.  Dr. Martin Luther King day - third Monday in January (15-21).
                3.  President's Day/Washington's B Day - always the third Monday in February (15-21).
                4.  Memorial Day - always the last Monday in May (25-31).
                5.  Independence Day - fourth of July for Monday through Friday.
                     If it falls on Saturday, observed on Friday the third.
                     If it falls on Sunday, observed on Monday the fifth.
                6.  Labor Day - always on the first Monday in September (1-7).
                7.  Columbus Day - always 2nd monday of october
                8 . Veterans Day - always 11/11
                9.  Thanksgiving Day - always on the fourth Thursday in November (22-28).
                10. Christmas Day - twenty-fifth of December
                     If it falls on Saturday, observed on Friday the twenty-fourth.
                     If it falls on Sunday, observed on Monday the twenty-sixth.
 
                */

				_holidays.Add(new DateTime(Year, 1, 1, 0, 0, 0));
				_holidays.Add(new DateTime(Year, 1, 1, 0, 0, 0).Get(WeekOfMonth.Third, DayOfWeek.Monday));
				_holidays.Add(new DateTime(Year, 2, 1, 0, 0, 0).Get(WeekOfMonth.Third, DayOfWeek.Monday));
				_holidays.Add(new DateTime(Year, 5, 1, 0, 0, 0).Get(WeekOfMonth.Last, DayOfWeek.Monday));
				_holidays.Add(new DateTime(Year, 7, 4, 0, 0, 0));
				_holidays.Add(new DateTime(Year, 9, 1, 0, 0, 0).Get(WeekOfMonth.First, DayOfWeek.Monday));
				_holidays.Add(new DateTime(Year, 10, 1, 0, 0, 0).Get(WeekOfMonth.Second, DayOfWeek.Monday));
				_holidays.Add(new DateTime(Year, 11, 11, 0, 0, 0));
				_holidays.Add(new DateTime(Year, 11, 1, 0, 0, 0).Get(WeekOfMonth.Fourth, DayOfWeek.Thursday));
				_holidays.Add(new DateTime(Year, 11, 1, 0, 0, 0).Get(WeekOfMonth.Fourth, DayOfWeek.Friday));
				_holidays.Add(new DateTime(Year, 12, 25, 0, 0, 0));

				foreach (var dateTime in _holidays)
					_observedHolidays.Add(dateTime.GetObservedDay());

				#endregion
			}
		}

		/// <summary>
		/// Gets or sets the holidays.
		/// </summary>
		/// <value>The holidays.</value>
		public List<DateTime> GetHolidays()
		{
			return _holidays;
		}

		/// <summary>
		/// Gets or sets the observed holidays.
		/// </summary>
		/// <value>The observed holidays.</value>
		public List<DateTime> GetObservedHolidays()
		{
			return _observedHolidays;
		}
	}
}

Open in new window

Which produces the following output -Capture.JPGKaufmed may have a better solution though.
-saige-
0
David L. HansenProgrammer AnalystAuthor Commented:
Saige,

Excellent contribution to the thread - Thanks. I wonder what kaufmed will present. One thing is for sure: I'll have great material right from the get-go!

A question for either/both of you: what are your thoughts on the issue I mentioned above regarding "extra" holidays? And I've thought of another twist; where I work, President's Day is observed some years and not others. These issues keep me leaning towards a database solution (a many-to-many relationship between holidays and years, which of course, resolves to the standard 3 many-to-one-to-many tables). Forgive me for typing as I think, but perhaps we can avoid using a database and keep it namespace/code-based. The trick would be that the function would have two optional parameters (at least) dealing with extra or ignored holidays (they could be date arrays or collections as parameter types).
0
it_saigeDeveloperCommented:
In the solution I present, you can add days to the list in the HolidaySchedule class, although a database implementation isn't a bad idea either.

That being said, I did find an error in the last presented implementation.  Here is an updated example.  For the year 2019 we would anticipate that the last working day for November would be 11/27 (since 11/28 is the fourth Thursday (Thanksgiving) and the Friday after Thanksgiving is historically observed).
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Runtime.InteropServices;

namespace System
{
	[ComVisible(true)]
	[Serializable]
	public enum MonthOfYear
	{
		January,
		February,
		March,
		April,
		May,
		June,
		July,
		August,
		September,
		October,
		November,
		December
	}

	[ComVisible(true)]
	[Serializable]
	public enum WeekOfMonth
	{
		First = 0,
		Second = 1,
		Third = 2,
		Fourth = 3,
		Last = 4
	}
}

namespace EE_Q28694411
{
	class Program
	{
		static void Main(string[] args)
		{
			for (int i = 0; i < 12; i++)
				Console.WriteLine("The last working day in {0} is? {1}", (MonthOfYear)i, new DateTime(2019, i + 1, 1).GetLastWorkingDay().ToShortDateString());
			Console.ReadLine();
		}
	}

	static class Extensions
	{
		/// <summary>Gets the next date from the year, month and day of current value.</summary>
		/// <param name="value">The value.</param>
		/// <param name="dayOfWeek">The day of week.</param>
		/// <returns>DateTime.</returns>
		public static DateTime Get(this DateTime value, DayOfWeek dayOfWeek)
		{
			var e = (int)dayOfWeek;
			var s = (int)value.DayOfWeek;

			return s < e ? value.AddDays(e - s) : value.AddDays(-(s - e)).AddDays(7);
		}

		/// <summary>Gets the next date from the year and month of current date.</summary>
		/// <param name="value">The value.</param>
		/// <param name="weekOfMonth">The week of month.</param>
		/// <param name="dayOfWeek">The day of week.</param>
		/// <returns>DateTime.</returns>
		public static DateTime Get(this DateTime value, WeekOfMonth weekOfMonth, DayOfWeek dayOfWeek)
		{
			var startDate = new DateTime(value.Year, value.Month, 1);
			var date = startDate;
			var i = (int)weekOfMonth;

			if (date.DayOfWeek == dayOfWeek && weekOfMonth == WeekOfMonth.First)
				return date;

			if (date.DayOfWeek == dayOfWeek)
				return date.AddDays((int)weekOfMonth * 7);

			var e = (int)dayOfWeek;
			var s = (int)date.DayOfWeek;

			if (s < e)
				date = date.AddDays(e - s);
			else
			{
				date = date.AddDays(-s);

				if (date.Month != startDate.Month)
					i++;

				date = date.AddDays(e);
			}

			date = date.AddDays(i * 7);

			return date.Month != startDate.Month ? date.AddDays(-7) : date;
		}

		/// <summary>Gets the next date from the year of current date.</summary>
		/// <param name="value">The value.</param>
		/// <param name="monthOfYear">The month of year.</param>
		/// <param name="weekOfMonth">The week of month.</param>
		/// <param name="dayOfWeek">The day of week.</param>
		/// <returns>DateTime.</returns>
		public static DateTime Get(this DateTime value, MonthOfYear monthOfYear, WeekOfMonth weekOfMonth, DayOfWeek dayOfWeek)
		{
			var dateTime = new DateTime(value.Year, (int)monthOfYear + 1, 1);
			return dateTime.Get(weekOfMonth, dayOfWeek);
		}

		/// <summary>Gets the next date.</summary>
		/// <param name="value">The value.</param>
		/// <param name="year">The year.</param>
		/// <param name="monthOfYear">The month of year.</param>
		/// <param name="weekOfMonth">The week of month.</param>
		/// <param name="dayOfWeek">The day of week.</param>
		/// <returns>DateTime.</returns>
		public static DateTime Get(this DateTime value, int year, MonthOfYear monthOfYear, WeekOfMonth weekOfMonth, DayOfWeek dayOfWeek)
		{
			var dateTime = new DateTime(year, (int)monthOfYear + 1, 1);
			return dateTime.Get(weekOfMonth, dayOfWeek);
		}

		/// <summary>Gets the observed day.</summary>
		/// <param name="value">The value.</param>
		/// <returns></returns>
		public static DateTime GetObservedDay(this DateTime value)
		{
			var date = new DateTime(value.Year, value.Month, value.Day);
 			
			switch (value.DayOfWeek)
			{
				case DayOfWeek.Sunday:
					date = date.AddDays(1);
					break;
				case DayOfWeek.Saturday:
					date = date.AddDays(-1);
					break;
			}
			return date;
		}

		/// <summary>Gets Good Friday.</summary>
		/// <param name="value">The value.</param>
		/// <param name="year">The year.</param>
		/// <returns>DateTime.</returns>
		/// <value>The good friday.</value>
		public static DateTime GetGoodFriday(this GregorianCalendar value, int year)
		{
			return value.GetEasterSunday(year).AddDays(-2);
		}

		/// <summary>Gets Easter Sunday.
		/// As noted on : http://www.assa.org.au/edm.html#Computer.
		/// Slightly modified for C#.</summary>
		/// <param name="value">The value.</param>
		/// <param name="year">The year.</param>
		/// <returns>DateTime.</returns>
		/// <value>The Easter Sunday.</value>
		public static DateTime GetEasterSunday(this GregorianCalendar value, int year)
		{
			// EASTER DATE CALCULATION FOR YEARS 1583 TO 4099

			//first 2 digits of year
			int firstDig = year / 100;
			//remainder of year / 19
			int remain19 = year % 19;

			// calculate PFM date
			int temp = (firstDig - 15) / 2 + 202 - 11 * remain19;

			switch (firstDig)
			{
				case 21:
				case 24:
				case 25:
				case 27:
				case 28:
				case 29:
				case 30:
				case 31:
				case 32:
				case 34:
				case 35:
				case 38:
					temp = temp - 1;
					break;
				case 33:
				case 36:
				case 37:
				case 39:
				case 40:
					temp = temp - 2;
					break;
			}
			temp = temp % 30;

			//table A to E results
			int tA = temp + 21;
			if ((temp == 29) || (temp == 28 & remain19 > 10))
				tA -= 1;

			int tB = (tA - 19) % 7;

			int tC = (40 - firstDig) % 4;
			if (tC == 3 || tC > 1)
				tC++;

			temp = year % 100;
			int tD = (temp + temp / 4) % 7;

			int tE = ((20 - tB - tC - tD) % 7) + 1;

			//find the next Sunday
			int d = tA + tE;
			int m = 3;

			if (d > 31)
			{
				d -= 31;
				m++;
			}

			return new DateTime(year, m, d);
		}

		public static DateTime GetLastWorkingDay(this DateTime date)
		{
			var holidaysInMonth = (from holiday in new UnitedStatesHolidaySchedule(UnitedStatesHolidayScheduleTypes.Federal, date.Year).GetObservedHolidays() 
							   where holiday.Month.Equals(date.Month) 
							   select holiday);
			var lastWorkingDay = (from day in Enumerable.Range(1, DateTime.DaysInMonth(date.Year, date.Month)) 
							  let now = new DateTime(date.Year, date.Month, day)
							  where !holidaysInMonth.Contains(now) && now.DayOfWeek != DayOfWeek.Saturday && now.DayOfWeek != DayOfWeek.Sunday
							  select now).LastOrDefault();
			return lastWorkingDay;
		}
	}

	/// <summary>Specifies the Holiday Calendar type used in the United States</summary>
	[ComVisible(true)]
	[Serializable]
	public enum UnitedStatesHolidayScheduleTypes : byte
	{
		/// <summary>Indicates the Federal holiday schedule.</summary>
		Federal = 0,
		/// <summary>Indicates the Stock Market holiday schedule.</summary>
		StockMarket = 1
	}

	public class UnitedStatesHolidaySchedule : GregorianCalendar
	{
		private readonly List<DateTime> _holidays;
		private readonly List<DateTime> _observedHolidays;

		/// <summary>Gets or sets the year.</summary>
		/// <value>The year.</value>
		public int Year { get; protected set; }

		/// <summary>Gets or sets the <see cref="T:System.Globalization.GregorianCalendarTypes" /> value that denotes the language version of the current <see cref="T:System.Globalization.GregorianCalendar" />.</summary>
		/// <value>The type of the united states holiday schedule.</value>
		public UnitedStatesHolidayScheduleTypes UnitedStatesHolidayScheduleType { get; protected set; }

		/// <summary>Initializes a new instance of the <see cref="UnitedStatesHolidaySchedule"/> class.</summary>
		protected UnitedStatesHolidaySchedule()
		{
			_holidays = new List<DateTime>();
			_observedHolidays = new List<DateTime>();
		}

		/// <summary>Initializes a new instance of the <see cref="UnitedStatesHolidaySchedule" /> class.</summary>
		/// <param name="unitedStatesHolidayScheduleType">Type of the calendar.</param>
		/// <param name="year">The year.</param>
		public UnitedStatesHolidaySchedule(UnitedStatesHolidayScheduleTypes unitedStatesHolidayScheduleType, int year) : this()
		{
			Year = year;
			UnitedStatesHolidayScheduleType = unitedStatesHolidayScheduleType;

			if (unitedStatesHolidayScheduleType == UnitedStatesHolidayScheduleTypes.StockMarket)
			{
				#region StockMarket
				/*
                U.S. stock markets are closed on nine regularly scheduled holidays each year:
                 1.  New Years Day - first of January for Monday through Saturday.
                      If it falls on Sunday, market is closed on Monday January second.
                      Every N years new years day falls on a Saturday, when this
                      happens there is NO closing of U.S stock markets for new years day.
                 2.  Dr. Martin Luther King day - third Monday in January (15-21).
                 3.  President's Day - always the third Monday in February (15-21).
                 4.  Good Friday - always on a Friday - the Friday before Easter Sunday.
                      Varies from late March to mid April.
                 5.  Memorial Day - always the last Monday in May (25-31).
                 6.  Independence Day - fourth of July for Monday through Friday.
                      If it falls on Saturday, market is closed on Friday the third.
                      If it falls on Sunday, market is closed on Monday the fifth.
                 7.  Labor Day - always on the first Monday in September (1-7).
                 8.  Thanksgiving Day - always on the fourth Thursday in November (22-28).
                 9.  Christmas Day - twenty-fifth of December for Monday through Friday.
                      If it falls on Saturday, market is closed on Friday the twenty-fourth.
                      If it falls on Sunday, market is closed on Monday the twenty-sixth.
                 */

				_holidays.Add(new DateTime(Year, 1, 1, 0, 0, 0));
				_holidays.Add(new DateTime(Year, 1, 1, 0, 0, 0).Get(WeekOfMonth.Third, DayOfWeek.Monday));
				_holidays.Add(new DateTime(Year, 2, 1, 0, 0, 0).Get(WeekOfMonth.Third, DayOfWeek.Monday));
				_holidays.Add((new GregorianCalendar()).GetGoodFriday(year));
				_holidays.Add(new DateTime(Year, 5, 1, 0, 0, 0).Get(WeekOfMonth.Last, DayOfWeek.Monday));
				_holidays.Add(new DateTime(Year, 7, 4, 0, 0, 0));
				_holidays.Add(new DateTime(Year, 9, 1, 0, 0, 0).Get(WeekOfMonth.First, DayOfWeek.Monday));
				_holidays.Add(new DateTime(Year, 11, 1, 0, 0, 0).Get(WeekOfMonth.Fourth, DayOfWeek.Thursday));
				_holidays.Add(new DateTime(Year, 11, 1, 0, 0, 0).Get(WeekOfMonth.Fourth, DayOfWeek.Thursday).AddDays(1));
				_holidays.Add(new DateTime(Year, 12, 25, 0, 0, 0));

				foreach (var dateTime in _holidays)
				{
					if ((dateTime.Month == 1 && dateTime.Day == 1 && dateTime.DayOfWeek == DayOfWeek.Saturday))
						continue;

					_observedHolidays.Add(dateTime.GetObservedDay());
				}
				#endregion
			}
			else //Federal
			{
				#region Federal
				/*
                http://www.opm.gov/operating_status_schedules/fedhol/2012.asp
                1.  New Years Day - first of January for Monday through Saturday.
                     If it falls on Saturday, Friday is observed
                     If it falls on Sunday, Monday is observed
                2.  Dr. Martin Luther King day - third Monday in January (15-21).
                3.  President's Day/Washington's B Day - always the third Monday in February (15-21).
                4.  Memorial Day - always the last Monday in May (25-31).
                5.  Independence Day - fourth of July for Monday through Friday.
                     If it falls on Saturday, observed on Friday the third.
                     If it falls on Sunday, observed on Monday the fifth.
                6.  Labor Day - always on the first Monday in September (1-7).
                7.  Columbus Day - always 2nd monday of october
                8 . Veterans Day - always 11/11
                9.  Thanksgiving Day - always on the fourth Thursday in November (22-28).
                10. Christmas Day - twenty-fifth of December
                     If it falls on Saturday, observed on Friday the twenty-fourth.
                     If it falls on Sunday, observed on Monday the twenty-sixth.
 
                */

				_holidays.Add(new DateTime(Year, 1, 1, 0, 0, 0));
				_holidays.Add(new DateTime(Year, 1, 1, 0, 0, 0).Get(WeekOfMonth.Third, DayOfWeek.Monday));
				_holidays.Add(new DateTime(Year, 2, 1, 0, 0, 0).Get(WeekOfMonth.Third, DayOfWeek.Monday));
				_holidays.Add(new DateTime(Year, 5, 1, 0, 0, 0).Get(WeekOfMonth.Last, DayOfWeek.Monday));
				_holidays.Add(new DateTime(Year, 7, 4, 0, 0, 0));
				_holidays.Add(new DateTime(Year, 9, 1, 0, 0, 0).Get(WeekOfMonth.First, DayOfWeek.Monday));
				_holidays.Add(new DateTime(Year, 10, 1, 0, 0, 0).Get(WeekOfMonth.Second, DayOfWeek.Monday));
				_holidays.Add(new DateTime(Year, 11, 11, 0, 0, 0));
				_holidays.Add(new DateTime(Year, 11, 1, 0, 0, 0).Get(WeekOfMonth.Fourth, DayOfWeek.Thursday));
				_holidays.Add(new DateTime(Year, 11, 1, 0, 0, 0).Get(WeekOfMonth.Fourth, DayOfWeek.Thursday).AddDays(1));
				_holidays.Add(new DateTime(Year, 12, 25, 0, 0, 0));

				foreach (var dateTime in _holidays)
					_observedHolidays.Add(dateTime.GetObservedDay());

				#endregion
			}
		}

		/// <summary>
		/// Gets or sets the holidays.
		/// </summary>
		/// <value>The holidays.</value>
		public List<DateTime> GetHolidays()
		{
			return _holidays;
		}

		/// <summary>
		/// Gets or sets the observed holidays.
		/// </summary>
		/// <value>The observed holidays.</value>
		public List<DateTime> GetObservedHolidays()
		{
			return _observedHolidays;
		}
	}
}

Open in new window

Which now produces the following results -Capture.JPGI, too, am looking forward to Kaufmeds implementation.  ;)

-saige-
0
käµfm³d 👽Commented:
Keep in mind that this is still kind of in progress, but it should be stable enough for use.


If you have any issues viewing that page, make sure that you are logged in. While not supported directly by EE, ee-stuff.com was created by a couple of members of EE, and they were given permission to use EE's authentication for logins.


The stuff you are after would be in the WorkingDay class. In my estimation, what you would want to do in order to calculate last working day of a particular month would be to pass the GetPreviousWorkingDay method a date that is the first of the successive month. So, if we were talking about the last working day of January, 2015, you would simply pass in February 1st to the method:

DateTime lastWorkingDayOfJanuary = WorkingDay.GetPreviousWorkingDay(new DateTime(2015, 2, 1));

Open in new window


Holidays are currently "hard-coded" to what you can see in the Holiday class' IsHoliday method, which checks all of the currently defined holiday methods also defined in that class. Within this class is where you could extend what other holidays you consider for your region. Furthermore, you could also provide your own holiday calculations by using the overload of GetPreviousWorkingDay which takes in a Func. You can define your own function that dictates what dates are actually holidays, and internally GetPreviousWorkingDay would execute that function when determining which days are working days.

I was planning on writing this up over the long weekend (in the U.S.), but there's no guarantee on that bit. So if you plan on using the above, and you have any other questions, feel free to post back.
0

Experts Exchange Solution brought to you by

Your issues matter to us.

Facing a tech roadblock? Get the help and guidance you need from experienced professionals who care. Ask your question anytime, anywhere, with no hassle.

Start your 7-day free trial
käµfm³d 👽Commented:
Also of note, the DateHelper class' GetOrdinalDate method was designed to be a handy way of retrieving the nth day of a month. You can see that I use this method internally to make holiday calculations read the way that they are defined by the real world. Speaking of which, the rules for holidays  were taken from information found across the web--primarily:  http://www.timeanddate.com/holidays/us/
0
käµfm³d 👽Commented:
And I've thought of another twist; where I work, President's Day is observed some years and not others. These issues keep me leaning towards a database solution (a many-to-many relationship between holidays and years, which of course, resolves to the standard 3 many-to-one-to-many tables).
Keeping holiday dates in the database seems to be a common solution. I don't particularly like it, but honestly, I cannot say why. It just doesn't feel correct to me...but it works. It's interesting that you celebrate President's day only sometimes; maybe for that you could have a "exceptional" holidays table that supplements the code offerings above. That begs the question:  If you're going to have one table, why not just do the whole thing in a table? I don't have a compelling argument one way or the other. I would simply say whichever method you feel would be the most maintainable (and understandable) for your organization.
0
David L. HansenProgrammer AnalystAuthor Commented:
Sorry to drop this; the holiday weekend got in the way -- oh the irony! :)

Kaufmed, I agree with you that it just feels wrong to use a database table to handle this. I thinks that's because we tend to view holidays as predictable occurrences. As such, simple functions should suffice, right? In practice, they do act like another fluid construct that bites you as soon as you think you've got it "set in stone." Wouldn't you agree?

And a Thank you to you both!
0
it_saigeDeveloperCommented:
I agree since Holi-*dates* are not set in stone.  The very nature that holidays (the observance of and the celebration of) can occur on varying days and/or dates (depending upon type) means that the holiday has to be a calculable object.  This is more why I, personally, like Kaufmed's implementation (with your permission Kaufmed, I would like to utilize this library).

His implementation, better allows for the drift of the calendar than the one written by Lonnie (more so because he calculates the ordinal, which I personally like better).

-saige-
0
käµfm³d 👽Commented:
Certainly. That's why I'm posting it. I'll have the write-up for it soon. I wanted to add some more unit tests and possibly some more functionality before I did that, though.
0
käµfm³d 👽Commented:
Do note:  There's a bug in Holiday.AdjustSunday method. The condition is backwards. I thought I caught this earlier, but I guess not. The logic just needs to be swapped in the if/else:

private static DateTime AdjustSunday(DateTime date, HolidayOptions safeOptions)
{
    DateTime adjusted = date;

    if (safeOptions.Direction == ObservanceDirection.PostWeekend || safeOptions.Direction == ObservanceDirection.Split)
    {
        adjusted = WorkingDay.GetNextWorkingDay(adjusted, safeOptions);
    }
    else
    {
        adjusted = WorkingDay.GetPreviousWorkingDay(adjusted, safeOptions);
    }

    return adjusted;
}

Open in new window

0
käµfm³d 👽Commented:
@sl8rz (et al.)

I made some changes to the original code base. There was another issue in the code that I ran across, but I can't remember what it was because I fixed it months ago. I also added more unit tests. The updated project can be downloaded at:  https://filedb.experts-exchange.com/incoming/ee-stuff/8432-WorkingDayCalculator.zip
0
It's more than this solution.Get answers and train to solve all your tech problems - anytime, anywhere.Try it for free Edge Out The Competitionfor your dream job with proven skills and certifications.Get started today Stand Outas the employee with proven skills.Start learning today for free Move Your Career Forwardwith certification training in the latest technologies.Start your trial today
Visual Basic.NET

From novice to tech pro — start learning today.

Question has a verified solution.

Are you are experiencing a similar issue? Get a personalized answer when you ask a related question.

Have a better answer? Share it in a comment.