Skip to main content
Ben Nadel at the jQuery Conference 2010 (Boston, MA) with: John Hann
Ben Nadel at the jQuery Conference 2010 (Boston, MA) with: John Hann ( @johnmhann )

Getting the Nth Occurrence Of A Day Of The Week For A Given Month

By on
Tags:

Yesterday, I was trying to determine when to turn on and off day light savings time for a given date calculation. Based on some Googling (cause I don't know this kind of stuff off-hand), it looks like day light savings time in this country goes from the second Sunday in March to the first Sunday of November. Because of this, I had to come up with a way to find the first and second sunday of a month.

Sunday happens to be a really easy day to find since it's always at the beginning of the week. This allowed me to create a simple formula based on the first day of the month:

FDOM + ((8 - DayOfWeek( FDOM )) % 7)

Note: FDOM = First Day Of Month

Once I was done with that, I decided to expand this idea. I created a ColdFusion user defined function that would allow me to get the Nth occurrence of any given day of the week for a given month (ex. the second Tuesday of the month - NYCFUG meeting!). The logic is a little more complex than the above equation since the day of the week is not predictable. However, it's still quite straightforward:

<cffunction
	name="GetNthDayOfMonth"
	access="public"
	returntype="any"
	output="false"
	hint="I return the Nth instance of the given day of the week for the given month (ex. 2nd Sunday of the month).">

	<!--- Define arguments. --->
	<cfargument
		name="Month"
		type="date"
		required="true"
		hint="I am the month for which we are gathering date information."
		/>

	<cfargument
		name="DayOfWeek"
		type="numeric"
		required="true"
		hint="I am the day of the week (1-7) that we are locating."
		/>

	<cfargument
		name="Nth"
		type="numeric"
		required="false"
		default="1"
		hint="I am the Nth instance of the given day of the week for the given month."
		/>

	<!--- Define the local scope. --->
	<cfset var LOCAL = {} />

	<!---
		First, we need to make sure that the date we were given
		was actually the first of the month.
	--->
	<cfset ARGUMENTS.Month = CreateDate(
		Year( ARGUMENTS.Month ),
		Month( ARGUMENTS.Month ),
		1
		) />


	<!---
		Now that we have the correct start date of the month, we
		need to find the first instance of the given day of the
		week.
	--->
	<cfif (DayOfWeek( ARGUMENTS.Month ) LTE ARGUMENTS.DayOfWeek)>

		<!---
			The first of the month falls on or before the first
			instance of our target day of the week. This means we
			won't have to leave the current week to hit the first
			instance.
		--->
		<cfset LOCAL.Date = (
			ARGUMENTS.Month +
			(ARGUMENTS.DayOfWeek - DayOfWeek( ARGUMENTS.Month ))
			) />

	<cfelse>

		<!---
			The first of the month falls after the first instance
			of our target day of the week. This means we will
			have to move to the next week to hit the first target
			instance.
		--->
		<cfset LOCAL.Date = (
			ARGUMENTS.Month +
			(7 - DayOfWeek( ARGUMENTS.Month )) +
			ARGUMENTS.DayOfWeek
			) />

	</cfif>


	<!---
		At this point, our Date is the first occurrence of our
		target day of the week. Now, we have to navigate to the
		target occurence.
	--->
	<cfset LOCAL.Date += (7 * (ARGUMENTS.Nth - 1)) />

	<!---
		Return the given date. There is a chance that this date
		will be in the NEXT month of someone put in an Nth value
		that was too large for the current month to handle.
	--->
	<cfreturn DateFormat( LOCAL.Date ) />
</cffunction>

To test this, I gathered the first thee occurrences of each day of the week for this month (December 2008):

<!--- Loop over each day of the week. --->
<cfloop
	index="intDayOfWeek"
	from="1"
	to="7"
	step="1">

	<p>
		<strong>#DayOfWeekAsString( intDayOfWeek )#</strong><br />

		1st: #GetNthDayOfMonth( Now(), intDayOfWeek, 1 )#<br />
		2nd: #GetNthDayOfMonth( Now(), intDayOfWeek, 2 )#<br />
		3rd: #GetNthDayOfMonth( Now(), intDayOfWeek, 3 )#<br />
	</p>

</cfloop>

We really only need the first 2 occurences to test this since every subsequent occurence is gotten with the addition of a constant 7 days. And, since the number of days in a week is static, this will never fail if the 2nd occurrence is accurate. When we run this code, we get the following output:

Sunday
1st: 07-Dec-08
2nd: 14-Dec-08
3rd: 21-Dec-08

Monday
1st: 01-Dec-08
2nd: 08-Dec-08
3rd: 15-Dec-08

Tuesday
1st: 02-Dec-08
2nd: 09-Dec-08
3rd: 16-Dec-08

Wednesday
1st: 03-Dec-08
2nd: 10-Dec-08
3rd: 17-Dec-08

Thursday
1st: 04-Dec-08
2nd: 11-Dec-08
3rd: 18-Dec-08

Friday
1st: 05-Dec-08
2nd: 12-Dec-08
3rd: 19-Dec-08

Saturday
1st: 06-Dec-08
2nd: 13-Dec-08
3rd: 20-Dec-08

Based on the following calendar, this output appears to be accurate.

Calendar For December 2008 - Used To Test Nth Occurrence Of Day Of The Week Gathering.

The name of the ColdFusion UDF might not be the best. I played around with GetNthDayOfWeekOfMonth() but that just felt so wordy. I guess it can always be changed later on.

Want to use code from this post? Check out the license.

Reader Comments

15,640 Comments

@Ray,

Ha ha :) That's too funny. I need to get more in the habit of looking stuff up. The methods are slightly different - mine returns an actual date, his returns a numeric day; but yeah, basically they are performing the same logic.

111 Comments

One extra thing you may want to add is LAST (I just wrote a date utils for Flex that includes this...mine had an even weirder name dayOfWeekIterationOfMonth :) ). This gets the last occurrence of that day of the week for the month. A little easier in Flex to do this as you can assign a constant to that value, and have people pass in FlexDateUtils.LAST as the occurrence to get, but more logical than having to pass in 7 (for e.g.) to get the last occurrence of a day of the week.

153 Comments

Bah. I say keep on coding before you look stuff up. Some might tell you that your time is worth more than that, but not me. Anything that stretches the brain is a good thing.

(But do look it up when you are done and make sure your solution doesn't completely suck, of course. To err is human.)

16 Comments

@Rick: Yes, that. Absent a time constraint, I like poking around at a problem first. It's almost like homework, with the internet being the answers in the back of the book.

Of course, while many books are pretty solid, some can have sketchy solutions ... don't be afraid to question something if it doesn't match what you expected.

1 Comments

You probably already know this, but when (and who observes) daylight time savings occurs changes over time (most recent change is just a couple of years old). Any algorithm would need to have an effective (start/end) date if were to be used over a long period of time.

111 Comments

I didn't realize this was to account for DST (I finally read the whole post :) ). I actually wrote something to account for DST also. I made a DaylightSavingTimeUS class to handle the changes for the US.

I created a DSTStart method and DSTEnd method that accepts a date (or could be a year). These are the public methods, then I have 2 private methods that account for any differences in the DST prior to the current DST start and end. I called them 'DSTStartBefore2007' which is probably not the best name for them if/when they change in the future, but you get the idea :) Then whenever it changes, I just add the new changes to the "before" methods, and anyone calling the DSTStart or DSTEnd won't notice any difference.

I created my Flex version of this to try to calculate US Federal Holidays, which also need the "get X day of the week for X month".

15,640 Comments

@Ronald,

Yeah, from what I was googling, it looks like the DST keeps changing. I am not sure how this time gets selected and from what I read briefly, it seemed rather arbitrary (decided by committee rather that a function of predictability). I guess I will need to readdress as needed.

@Gareth,

Way to factor out what changes :)

16 Comments

As long as we're talking about it ... certainly DST for a global application is a pain, but even within the US there are challenges. Not everyone observes daylight saving time, and sometimes there are changes to that list that are not necessarily in sync with the changes to DST itself.

This is why you have the Indiana and Arizona time zones in Windows et al: currently only Arizona and Hawaii do not observe DST. Indiana began observing it in 2006. (My previous employer did a lot of phone surveys, so before 2006, we had semi-annual fun adjusting all appointments over the weekend and then explaining to our clients for the next month or so why our time was different.)

It's not a complication if you're displaying application time, but if you're trying to display local time, it can be tricky (which is why some apps simply ask for East/Central/Mountain/Pacific and DST on/off: fine for continental US, inadequate otherwise), and historical local time can be a nightmare.

15,640 Comments

@Dave,

We have an app that needs to calculate time based on a given Zip code. We have a zip code look-up table which has a yes/no flag for whether or not they observe daylight savings time. We are just going off that to keep it simple. But, as mentioned above, I guess the delimiters for DST will have to be re-adjusted from year to year.

2 Comments

I wrote this algorithm in PHP. It can probably pretty easily be converted to any language. Read the comments that I inserted in the code to get an idea of how the algorithm works.

This will give you any occurrence of a day of week. For example, the third Tuesday of the month, the last Friday of the month, the second Monday of the month… you get the picture.

$DOW = day of the week (0 = Sunday, 6 = Saturday).
$X = occurence (1 = first, 2 = third, etc..). If the given month does not have the occurrence, then it will return the last. For example, if you ask for the 7th occurrence of Friday, it will return the last Friday of the month.
$M = month
$Y = year

Example, get_Xth_DOW(2,3,7,2009) will return the third Tuesday of July 2009. It just returns the day. If you want the date you can just do
$D = get_Xth_DOW($DOW,$X,$M,$Y);
echo $M . "/" . $D . "/" . $Y;

The algorithm (bottom of post) is quick and simple.

It first gets the first occurrence. It can then add multiples of 7 to get the Xth occurrence.

function get_Xth_DOW($DOW,$X,$M,$Y) {
# Start at the first day of the month
$d = 1;

# Get the day of week for the first day of the month
# date() returns 0 = Sunday, through 6 = Saturday
$firstDOW = date('w',mktime(0,0,0,$M,1,$Y));

# Get the difference in the number of days between
# the dow of the first of the month and the dow that
# you are looking for.
$DOW_offset = $firstDOW - $DOW;

# Adjust the date (d) to get to the first occurrence
if($DOW_offset > 0) { $d += (7 - $DOW_offset); }
else if ($DOW_offset < 0) { $d += -1*$DOW_offset; }

# Add multiples of 7 to the first occurrence of the dow
# to get to the Xth occurrence
$add = 7*($X - 1);
$d = $d + $add;

# If you went over the number of days in the month,
# then it is time to backtrack to the last occurrence
# of dow.
# numDays is the number of days in the current Month and Year
$numDays = date('t',mktime(0,0,0,$M,1,$Y));
while($d > $numDays) { $d -= 7; }

# return the day.
return $d;
}

1 Comments

@Joe Dundas, thanks for your code. It's been very helpful to me. I'm working on a slight variation and I wonder if you have any ideas. What I'd like to do is determine what occurrence *today* is (e.g. today, March 10th, is the 2nd Tuesday of the month). So, I'd like to turn this logic around just a little bit and take today's day/date and return what nth occurrence it is in this month.

Ideas? Much appreciated.

2 Comments

Hi John,

Take the ceiling of $D/7. Where $D is the day of the month.

For example,

for 2/11/2009.
$D = 11;
$occurrence = ceil($D/7) = ceil(1.57) = 2

I believe in love. I believe in compassion. I believe in human rights. I believe that we can afford to give more of these gifts to the world around us because it costs us nothing to be decent and kind and understanding. And, I want you to know that when you land on this site, you are accepted for who you are, no matter how you identify, what truths you live, or whatever kind of goofy shit makes you feel alive! Rock on with your bad self!
Ben Nadel