Date Looping Using ColdFusion Custom DateLoop Tag

Posted August 3, 2007 at 3:01 PM by Ben Nadel

Tags: ColdFusion

I have talked at length about date looping in ColdFusion both in terms of calendars as well as plain old indexed loops. Looping works particularly well if you want to do some sort of day-based increment for your loop. This is because in ColdFusion (and many other technologies), a Day can be represented as 1 (or a fraction of 1). Once you get out of a day-based increment, things get a bit more tricky. Incrementing by weeks, week days, years, or any other non-consistent value makes things more complicated.

To try and keep this very easy and simple, I created this really simple ColdFusion custom tag that elegantly handles looping through dates in a highly intuitive way. This ColdFusion custom tag, DateLoop.cfm, basically is a modified version of the CFLoop tag that ColdFusion already uses. In addition to the normal Index, From, To, and Step attributes, DateLoop.cfm also provides a DatePart attribute. This attribute can hold any value that is valid as the "date part" argument for the DateAdd() ColdFusion function.

Here is how it can be used; here, we are going to loop over all days in August:

  • <!--- Loop through all the days in august. --->
  • <cf_dateloop
  • index="dtDay"
  • from="8/1/2007"
  • to="8/31/2007">
  •  
  • #dtDay#<br />
  •  
  • </cf_dateloop>

This gives us the following (abridged) output:

{ts '2007-08-01 00:00:00'}
{ts '2007-08-02 00:00:00'}
....
{ts '2007-08-30 00:00:00'}
{ts '2007-08-31 00:00:00'}

As you can see, we are invoke the ColdFusion custom tag using the "cf_" construct. Of course, we are only adding a day at a time - nothing special. In fact, this is exactly how a CFLoop index loop would work (minus the date formatting). The power of a ColdFusion custom tag like this is that you can now start to use different DatePart values.

In this example, we are going to loop over August, but only include week days:

  • <!--- Loop through all the week days in august. --->
  • <cf_dateloop
  • index="dtDay"
  • from="8/1/2007"
  • to="8/31/2007"
  • datepart="w">
  •  
  • #dtDay#<br />
  •  
  • </cf_dateloop>

Notice that our <cf_dateloop /> tag now has the attribute DatePart set to "w". This gives us the following output:

{ts '2007-08-01 00:00:00'}
{ts '2007-08-02 00:00:00'}
{ts '2007-08-03 00:00:00'}
{ts '2007-08-06 00:00:00'}
{ts '2007-08-07 00:00:00'}
{ts '2007-08-08 00:00:00'}
{ts '2007-08-09 00:00:00'}
{ts '2007-08-10 00:00:00'}
{ts '2007-08-13 00:00:00'}
{ts '2007-08-14 00:00:00'}
{ts '2007-08-15 00:00:00'}
{ts '2007-08-16 00:00:00'}
{ts '2007-08-17 00:00:00'}
{ts '2007-08-20 00:00:00'}
{ts '2007-08-21 00:00:00'}
{ts '2007-08-22 00:00:00'}
{ts '2007-08-23 00:00:00'}
{ts '2007-08-24 00:00:00'}
{ts '2007-08-27 00:00:00'}
{ts '2007-08-28 00:00:00'}
{ts '2007-08-29 00:00:00'}
{ts '2007-08-30 00:00:00'}
{ts '2007-08-31 00:00:00'}

Notice that weekend days (ex. 08/04, 08/05, 08/11) are skipped over.

I think this adds some really intuitive functionality for Date-based looping. Here is the ColdFusion custom tag that makes this possible:

  • <!--- Kill extra output. --->
  • <cfsilent>
  •  
  • <!---
  • Check to see which tag execution mode we are in.
  • We have acitons that can / should only be done in
  • one or the other.
  • --->
  • <cfswitch expression="#THISTAG.ExecutionMode#">
  •  
  • <cfcase value="Start">
  •  
  • <!---
  • In the start mode, we are going to need to
  • param the tag attributes.
  • --->
  •  
  • <!---
  • This is the name of the caller-scoped variable
  • into which we want to store the iteration value.
  • --->
  • <cfparam
  • name="ATTRIBUTES.Index"
  • type="string"
  • />
  •  
  • <!---
  • This is the value at which we will start the
  • date looping.
  • --->
  • <cfparam
  • name="ATTRIBUTES.From"
  • type="numeric"
  • />
  •  
  • <!---
  • This is the value at which we will end the
  • date looping (value is inclusive in loop).
  • --->
  • <cfparam
  • name="ATTRIBUTES.To"
  • type="numeric"
  • />
  •  
  • <!---
  • This is the amount by which we will incrememnt
  • the loop for each iteration. How this actually
  • translates will be dependent on the DatePart.
  • --->
  • <cfparam
  • name="ATTRIBUTES.Step"
  • type="numeric"
  • default="1"
  • />
  •  
  • <!---
  • This is how the step increment value is applied
  • to the iteration. By default, we will add a day
  • for each increment. This value can be anything
  • used in DateAdd():
  •  
  • yyyy: Year
  • q: Quarter
  • m: Month
  • y: Day of year
  • d: Day
  • w: Weekday
  • ww: Week
  • h: Hour
  • n: Minute
  • s: Second
  • l: Millisecond
  • --->
  • <cfparam
  • name="ATTRIBUTES.DatePart"
  • type="string"
  • default="d"
  • />
  •  
  •  
  • <!---
  • Now that we have paramed all of our attributes,
  • we have to validate the data.
  • --->
  • <cfif (NOT Fix( ATTRIBUTES.Step ))>
  •  
  • <!---
  • The step value must be a non-zero number to
  • prevent infinite looping.
  • --->
  • <cfthrow
  • type="DateLoop.InvalidAttributeValue"
  • message="Step must be a non-zero number."
  • detail="The Step value you provide [#UCase( ATTRIBUTES.Step )#] must be non-zero number to prevent infinite looping."
  • />
  •  
  • </cfif>
  •  
  •  
  • <!---
  • ASSERT: If we have made it this far than we have
  • all the required attributes and valid data.
  • --->
  •  
  • <!--- Initialize the loop sequence. --->
  • <cfset THISTAG.Day = ATTRIBUTES.From />
  •  
  • <!--- Store the current value into the caller. --->
  • <cfset "CALLER.#ATTRIBUTES.Index#" = ParseDateTime(
  • DateFormat( THISTAG.Day, "mm/dd/yyyy" ) & " " &
  • TimeFormat( THISTAG.Day, "HH:mm:ss" )
  • ) />
  •  
  •  
  • <!---
  • Before we even start looping, let's check to see
  • if we haven't already met our final condition.
  • --->
  • <cfif (
  • <!--- Incrementing. --->
  • (
  • (ATTRIBUTES.Step GT 0) AND
  • (THISTAG.Day GT ATTRIBUTES.To)
  • ) OR
  •  
  • <!--- Decrementing. --->
  • (
  • (ATTRIBUTES.Step LT 0) AND
  • (THISTAG.Day LT ATTRIBUTES.To)
  • ))>
  •  
  • <!---
  • We have already met the final condition.
  • Exit out before the loop even starts.
  • --->
  • <cfexit method="EXITTAG" />
  •  
  • </cfif>
  •  
  • </cfcase>
  •  
  •  
  • <cfcase value="End">
  •  
  • <!---
  • Increment the index value using the specified
  • increment and date part.
  • --->
  • <cfset THISTAG.Day = DateAdd(
  • ATTRIBUTES.DatePart,
  • ATTRIBUTES.Step,
  • THISTAG.Day
  • ) />
  •  
  •  
  • <!--- Store the current value into the caller. --->
  • <cfset "CALLER.#ATTRIBUTES.Index#" = THISTAG.Day />
  •  
  •  
  • <!---
  • Check to see if we should continue to loop this
  • value. When checking this, it depends on how the
  • From and To values relate to each other.
  • --->
  • <cfif (
  • <!--- Incrementing. --->
  • (
  • (ATTRIBUTES.Step GT 0) AND
  • (THISTAG.Day LTE ATTRIBUTES.To)
  • ) OR
  •  
  • <!--- Decrementing. --->
  • (
  • (ATTRIBUTES.Step LT 0) AND
  • (THISTAG.Day GTE ATTRIBUTES.To)
  • ))>
  •  
  • <!---
  • We are not done looping. Exit out using the
  • LOOP type so that the EndTag will execute
  • at least one more time.
  • --->
  • <cfexit method="LOOP" />
  •  
  • <cfelse>
  •  
  • <!---
  • We are done looping. Exit out of the
  • tag fully.
  • --->
  • <cfexit method="EXITTAG" />
  •  
  • </cfif>
  •  
  • </cfcase>
  •  
  • </cfswitch>
  •  
  • </cfsilent>

The only drawback to the DateLoop.cfm ColdFusion custom tag is that because it is using the ColdFusion function DateAdd(), it is limited by the DateAdd() rules. In particular, the "step" value must be an integer. So, where as with a standard CFLoop you could have incremented by ".5", DateLoop.cfm requires an integer "step" value.




Reader Comments

Aug 3, 2007 at 4:12 PM // reply »
111 Comments

Ooooohhh, I like :-> Gotta play with this when I get back to calendars!


Aug 3, 2007 at 4:14 PM // reply »
10,640 Comments

Cool. Let me know what you think.


Ed
Aug 15, 2007 at 6:34 AM // reply »
18 Comments

This custom tag is super Ben, one question though, how do I make the to dates dynamic? It only seems to work if I hard-code the date in the to="" field of the custom tag, if I try and create a variable for today using the dateformat function and then insert that variable into the custom tag it doesn't work. What am I doing wrong?


Ed
Aug 15, 2007 at 7:04 AM // reply »
18 Comments

I figured it out. Sorry to have bothered you mate. I was being a complete muppet


Aug 15, 2007 at 7:17 AM // reply »
10,640 Comments

No worries. Next time you run into a roadblock, just post your start tag code here.


Feb 20, 2009 at 2:25 PM // reply »
1 Comments

Awesome tag Ben! This is exactly what I needed. Trying to implement recurring calendar events without it is much harder! Thanks a bunch.


Sep 29, 2009 at 7:53 AM // reply »
2 Comments

This tag is exactly what I was looking for! I have one question though. Is there a way I can use this to somehow get only the weekend days? Days 1, 7?


Sep 29, 2009 at 7:59 AM // reply »
10,640 Comments

@Chris,

In the tag logic, you'd have to add just a bit of logic when incrementing the date. You'd probably also have to add either a new type of date increment (for weekend days) or add some sort of dayIndexes attribute which could be a list of day indexes, ex: dayindexes="1,7".


Sep 29, 2009 at 10:54 AM // reply »
2 Comments

Thank you. I will play around with it and see what I come up with.


Jun 10, 2010 at 8:55 AM // reply »
1 Comments

FANTASTIC!

What a useful custom tag.

However...

At my work they do not let us use custom tags, so I've made a component function version of this if anyone wants one.

Cheers, Michael


Jun 10, 2010 at 9:12 AM // reply »
10,640 Comments

@Michael,

Thanks my man. Date looping and dates in general in ColdFusion are kind of cool. Glad you're getting some value out of this approach. Wrapping it up in a CFC sounds like a good thing too :)


Jun 10, 2010 at 3:38 PM // reply »
1 Comments

Man, i'm going to start searching here instead of googling. I always end up here anyway.

Your last name is now a verb.

"I don't know how to do that, i'm gonna have to go and Nadel it."


Jun 10, 2010 at 4:00 PM // reply »
10,640 Comments

@Paul,

Ha ha ha ha, that is the most awesome thing I've heard all week :)


Jun 22, 2011 at 5:31 PM // reply »
1 Comments

This is the third time that ColdFusion code that you have written has saved me time.

The first two are the POIUtility.cfc which I used for making batch updates to a web database through a web form admin tool.

And second was for precessing an external XML feed: http://www.bennadel.com/blog/494-Stripping-XML-Name-Spaces-And-Node-Prefixes-From-ColdFusion-XML-Data-To-Simplify-XPath-.htm



Post A Comment

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.

Please review the following issues:

Author Name:


Author Email:

Author Website:

Comment:

Supported HTML tags for formatting: <strong>bold</strong>   <em>italic</em>   <code>code</code>







  • Help Wanted - Find Your Next ColdFusion Job
InVision App - Prototyping Made Beautiful With Prototyping Tools Ben Nadel's Company - Epicenter Consulting Recent Blog Comments
Feb 12, 2012 at 3:37 AM
Learning ColdFusion 8: CFImage Part III - Watermarks And Transparency
Hi Ben, Just to ask currently it is placed bottom right corner, if i need to replace the same rendered image on the bottom left side or in the bottom center, how that can be calculated. bottom ce ... read »
Feb 11, 2012 at 9:29 PM
Use jQuery's SlideDown() With Fixed-Width Elements To Prevent Jumping
I can't say how glad I am that I found your post. Thank you very much. ... read »
Feb 10, 2012 at 7:21 PM
jQuery AJAX Strips Script Tags And Inserts Them After Parent-Most Elements
Update! Instead of $(eval(options.insertAfter)).after(data['insertData']); I now use: var ajaxNode = document.createElement('span'); var parent = $(eval(options.insertAfter))[0].parentNode; ... read »
Feb 10, 2012 at 6:18 PM
jQuery AJAX Strips Script Tags And Inserts Them After Parent-Most Elements
encountered this same, what I consider, jQuery bug last week. I'm building a site in which I load some content via AJAX. This content contains Linkedin share button placeholders which Linkedin API ne ... read »
Feb 10, 2012 at 11:30 AM
Cross-Origin Resource Sharing (CORS) AJAX Requests Between jQuery And Node.js
After you understand the concepts here, this is an awesome cheatsheet for enabling CORS in just about anything http://enable-cors.org/ ... read »
JM
Feb 10, 2012 at 9:10 AM
My Safari Browser SQLite Database Hello World Example
@Amy, Here is a very good tutorial on how to use JOIN: http://www.sqltutorial.org/sqljoin-innerjoin.aspx ... read »
Feb 10, 2012 at 4:42 AM
Building A Twitter-Inspired RESTful API Architecture In ColdFusion
This is great, very useful Ben. I spotted a small typo in the api.cgm listing: <cfthrow type="Unauthroized" /> Cheers Stefan ... read »
Feb 9, 2012 at 10:35 PM
CFDirectory Filtering Uses Pipe Character For Multiple Filters (Thanks Steve Withington)
I was wondering if there would be a filter you could apply so that you got everything but what you included in the filter. As in show me all docs that are not a .pdf. ... read »