Click here to Skip to main content
15,891,136 members
Articles / Programming Languages / C#

Get the Nth Instance of a Weekday in the Specified Month

Rate me:
Please Sign up or sign in to vote.
4.90/5 (3 votes)
12 Nov 2012CPOL3 min read 31.8K   312   7  
Presenting a DateTime extension class that allows you to determine the actual date of the specified instance of the specified weekday within the specified month/year.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace MAHSetup.AppCode
{
	public static class DateTimeExtensions
	{

		//--------------------------------------------------------------------------------
		/// <summary>
		/// Gets the date of the specified instance of the specified day of week within 
		/// the specified month for the specified year
		/// </summary>
		/// <param name="dt">The parent object of this method</param>
		/// <param name="year">The year to calculate for</param>
		/// <param name="month">The month to calculate for</param>
		/// <param name="day">The day of week to find</param>
		/// <param name="ordinal">The instance of the day of week to find</param>
		/// <returns>The resulting DateTime</returns>
		/// <remarks>
		/// If the specified ordinal is smaller than 1, it's set to 1. If the ordinal is 
        /// greater than 5, it's set to 5 (there are no more than five instances of any 
        /// given weekday in any month). The month value is similarly normalized (at 1 
        /// and 12, of course). If the normalized ordinal results in a date that is 
        /// beyond the end of the specified month, the method will back the resulting 
        /// date off by 7 days. This happens until the resulting date falls within 
		/// the specified month. Since the max ordinal is 5, this recursive call should 
        /// only happen once at the very most (in February, depending on whether or not 
        /// the specified year is a leap year).</remarks>
		public static DateTime GetDateByOrdinalDay(this DateTime dt, int year, int month, DayOfWeek dayOfWeek, int ordinal)
		{
            // normalize some values to make sure we're within acceptable ranges
            ordinal = Math.Min(Math.Max(ordinal, 1), 5);
            month = Math.Min(Math.Max(month, 1), 12);

			// start out on the first of the specified month and of the current year
			DateTime workingDate = new DateTime(year, month, 1);

            DateTime lastDay = new DateTime(year += (month + 1 > 12) ? 1 : 0, 
                                            month += (month + 1 > 12)? - 11 : 1, 
                                            1).AddDays(-1);
			// find the number of days in the specified month
			int maxDays = (lastDay - workingDate).Days + 1;

            // assume that the month starts on the specifed day of week
            int gap = 0;

            // if the the day of week of the first day is NOT the specified day of week
			if (workingDate.DayOfWeek != dayOfWeek)
			{
				// determine the number of days between the first of the month and 
                // the first instance of the specified day of week
                gap = (int)workingDate.DayOfWeek - (int)dayOfWeek;
                gap = (gap < 0) ? Math.Abs(gap) : 7 - gap;

                // and set the date to the first instance of the specified day of week
                workingDate = workingDate.AddDays(gap);
			}

            // if we want something later than the first instance
            if (ordinal > 1)
            {
                // determine how many days we're going to add to the working date to 
                // satisfy the specified ordinal
			    int daysToAdd = 7 * (ordinal - 1);

                // now adjust back, just in  case the specified ordinal - this loop 
                // should only iterate once or twice
                while (daysToAdd + gap > maxDays-1)
                {
                    daysToAdd -= 7;
                }

                // finally we adjust the date by the number of days to add
                workingDate = workingDate.AddDays(daysToAdd);
            }

            // and return the date to the calling method
			return workingDate;
		}

		//--------------------------------------------------------------------------------
		/// <summary>
		/// Gets the date of the specified instance of the specified day of week within the specified month for the current year
		/// </summary>
		/// <param name="dt">The parent object of this method</param>
		/// <param name="month">The month to calculate for</param>
		/// <param name="day">The day of week to find</param>
		/// <param name="ordinal">The instance of the day of week to find</param>
		/// <returns>The resulting DateTime</returns>
		/// <remarks>
		/// If the specified ordinal is smaller than 1, it's set to 1. If the ordinal is 
        /// greater than 5, it's set to 5 (there are no more than five instances of any 
        /// given weekday in any month). The month value is similarly normalized (at 1 
        /// and 12, of course). If the normalized ordinal results in a date that is 
        /// beyond the end of the specified month, the method will back the resulting 
        /// date off by 7 days. This happens until the resulting date falls within 
		/// the specified month. Since the max ordinal is 5, this recursive call should 
        /// only happen once at the very most (in February, depending on whether or not 
        /// the specified year is a leap year).</remarks>
		public static DateTime GetDateByOrdinalDay(this DateTime dt, int month, DayOfWeek day, int ordinal)
		{
			return GetDateByOrdinalDay(dt, DateTime.Now.Year, month, day, ordinal);
		}

		//--------------------------------------------------------------------------------
		/// <summary>
		/// Sets this DateTime to the date as calculated by GetDateByOrdinalDay. No error checking is done 
		/// beyond making sure the ordinal is greater than 0 and month is between 1 and 12.
		/// </summary>
		/// <param name="dt">The parent object of this method</param>
		/// <param name="month">The month to calculate for</param>
		/// <param name="day">The day of week to find</param>
		/// <param name="ordinal">The instance of the day of week to find</param>
		/// <remarks>
		/// If the specified ordinal is smaller than 1, it's set to 1. If the ordinal is 
        /// greater than 5, it's set to 5 (there are no more than five instances of any 
        /// given weekday in any month). The month value is similarly normalized (at 1 
        /// and 12, of course). If the normalized ordinal results in a date that is 
        /// beyond the end of the specified month, the method will back the resulting 
        /// date off by 7 days. This happens until the resulting date falls within 
		/// the specified month. Since the max ordinal is 5, this recursive call should 
        /// only happen once at the very most (in February, depending on whether or not 
        /// the specified year is a leap year).</remarks>
		public static void SetDateByOrdinalDay(this DateTime dt, int month, DayOfWeek day, int ordinal)
		{
			dt = GetDateByOrdinalDay(dt, month, day, ordinal);
		}
	}
}


By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior) Paddedwall Software
United States United States
I've been paid as a programmer since 1982 with experience in Pascal, and C++ (both self-taught), and began writing Windows programs in 1991 using Visual C++ and MFC. In the 2nd half of 2007, I started writing C# Windows Forms and ASP.Net applications, and have since done WPF, Silverlight, WCF, web services, and Windows services.

My weakest point is that my moments of clarity are too brief to hold a meaningful conversation that requires more than 30 seconds to complete. Thankfully, grunts of agreement are all that is required to conduct most discussions without committing to any particular belief system.

Comments and Discussions