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
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.
public static DateTime GetDateByOrdinalDay(this DateTime dt,
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.
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.
DateTime workingDate = new DateTime(year, month, 1);
DateTime lastDay = new DateTime(year += (month + 1 > 12) ? 1 : 0,
month += (month + 1 > 12)? - 11 : 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.
int gap = 0;
if (workingDate.DayOfWeek != dayOfWeek)
gap = (int)workingDate.DayOfWeek - (int)dayOfWeek;
gap = (gap < 0) ? Math.Abs(gap) : 7 - gap;
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.
if (ordinal > 1)
int daysToAdd = 7 * (ordinal - 1);
while (daysToAdd + gap > maxDays-1)
daysToAdd -= 7;
workingDate = workingDate.AddDays(daysToAdd);
Finally, we return the calculated date.
Usage is simple:
rDateTime thanksgiving = DateTime.GetDateByOrdinalDay(DateTime.Now.Year,
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.
12 Nov 2012 - Refactored the main method in the extension class because I found some issues under certain circumstances.
Aug 2012 - Original Article
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.