Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

Get the Nth Instance of a Weekday in the Specified Month

4.90/5 (3 votes)
12 Nov 2012CPOL3 min read 32.9K   314  
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.

Introduction 

While writing code for the Home Theater PC software I'm developing, I realized a need to be able to determine the date of the Nth day of a given month for the purpose of determining when some holidays occur. For example, Thanksgiving (in the US) is celebrated on the fourth Thursday of November. This requirement led me to develop the following extension methods for the DateTime class.

The Code

First, I needed to establish the necessary list of parameters. To make it as generic as possible at its most basic level, I needed to know the year, month, and day of week we're looking for, as well as the instance (ordinal) of that day of the week within the specified month.

C#
public static DateTime GetDateByOrdinalDay(this DateTime dt, 
                                                          int year, 
                                                          int month, 
                                                          DayOfWeek day, 
                                                          int ordinal)
{

Next, I needed to perform some sanity checks to make sure the programmer couldn't do something completely stupid (not that we programmers do that a lot). I also wanted the code to adjust appropriately for ordinals that were outside the allowable range. If an ordinal less than 1 is specified, the code self-corrects and assigns 1 to the variable. If the value is greater than 5, it makes the value 5. There are no months that have more than five of any given weekday, so I figure this is a reasonable move. If you want to interrupt the code, you could always modify it to throw an exception instead of correcting itself.

C#
ordinal = Math.Min(Math.Max(ordinal, 1), 5);
month = Math.Min(Math.Max(month, 1), 12);

After that, I create a DateTime object that I am free to thrash on (using the 1st day of the year/month), as well as determining the maximum number of days we can add to build the desired date. Since we're looking for the Nth instance of a given weekday in a given month, it doesn't make sense to return a date that falls outside that month. This value will help us later in the method.

C#
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);
maxDays = (lastDay - workingDate).Days + 1;

Next, we need to determine how many days to add to the current working date (remember, it represents the first day of the specified month). To start, we need to calculate the gap between the first occurrence of the specified DayOfWeek, and the first day of the month.

C#
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);
}

At this point, our working date is set to the first instance (ordinal=1) of the specified DayOfWeek, so all we have to do is add enough days to accommodate the specified ordinal value (and we only have to do it if the specified ordinal is greater than 1.

C#
// 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);
}

Finally, we return the calculated date.

Usage

Usage is simple:

C#
rDateTime thanksgiving = DateTime.GetDateByOrdinalDay(DateTime.Now.Year, 
                             11, 
                             System.DayOfWeek.Thursday, 
                             4);

Final Notes

The class included in the download includes an overloaded version of this method that assumes that the current year is to be used, as well as a Set... method to allow the calling object to set itself to the result.

The original version of the code used recursion, which I replaced with a while... loop. The reason for the sanity checks is that I try to avoid using exceptions if at all possible. Since there's no reason the code shouldn't be able to self-correct, that's the approach I took.

Have a ball.

History

12 Nov 2012 - Refactored the main method in the extension class because I found some issues under certain circumstances. Aug 2012 - Original Article

License

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