Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.
I am the chief technical officer at InVision App, Inc - a prototyping and collaboration platform for designers, built by designers. I also rock out in JavaScript and ColdFusion 24x7.
Meanwhile on Twitter
Loading latest tweet...
Ben Nadel at cf.Objective() 2013 (Bloomington, MN) with:

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

By Ben Nadel on
Tags: ColdFusion

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.



Reader 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.

Reply to this Comment

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.

Reply to this Comment

@Gareth,

Cool idea. I hadn't even thought of that, but I can easily see that being really useful. Thanks.

Reply to this Comment

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.)

Reply to this Comment

@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.

Reply to this Comment

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.

Reply to this Comment

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".

Reply to this Comment

@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 :)

Reply to this Comment

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.

Reply to this Comment

@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.

Reply to this Comment

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;
}

Reply to this Comment

@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.

Reply to this Comment

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

Reply to this Comment

Post A Comment

You — Get Out Of My Dreams, Get Into My Comments
Live in the Now
Oops!
Comment Etiquette: Please do not post spam. Please keep the comments on-topic. Please do not post unrelated questions or large chunks of code. And, above all, please be nice to each other - we're trying to have a good conversation here.