This morning, I got to share the magic of using CFLoop to easily loop over ColdFusion dates. A co-worker of mine was having trouble and resorted to using an index-loop and the DateAdd() function to step through a date span one day at a time:
<!--- Set default start date. ---> <cfset dtStart = Now() /> <!--- Set default iteration date. ---> <cfset dtToday = dtStart /> <!--- Loop over the number of days. ---> <cfloop index="intDayOffset" from="0" to="6" step="1"> <!--- Create the appropriate offset date. ---> <cfset dtToday = DateAdd( "d", intDayOffset, dtStart ) /> Today is: #dtToday#<br /> </cfloop>
Does that look all to familiar to you? It does to a lot of people (before today!). Luckily, I overheard his conversation and was able to step in.
Before getting into this, remember that ColdFusion, like SQL, can view dates as the amount of time that has passed since a given start date (as determined by ColdFusion to be the earliest date available). This number is a floating point number where the integer portion is days and the decimal portion is fractions of a day. I am not sure how dates are "stored" internally to the ?Java? date object underneath, but ColdFusion will automatically perform the cast to the floating point number when required.
So, let's take a look at the loop:
<cfloop index="dtToday" from="#dtStart#" to="#dtEnd#" step="#CreateTimeSpan( 1, 0, 0, 0 )#"> The numeric value of today is: #dtToday# The "friendly" value of today is: #DateFormat( dtToday, "mmm d, yyyy" )# </cfloop>
Let's look at this a piece at a time, staring with the tag attributes. The INDEX field is standard to all loops except Collection loops. This keeps track of the index value during each CFLoop iteration.
The FROM and TO fields contain the boundary dates. Now, remember how I said that ColdFusion will take care of the type cast automatically? That is what is happening here. For the FROM and TO fields, ColdFusion is automatically casting the dates to a numeric, floating-point value (ex. 34354.454345).
The STEP field determines the increment we will be making for each CFLoop iteration. We want to make a date-time increment, but, since our CFLoop is really dealing with the numeric representation of dates, our increment needs to be a numeric value. That is what CreateTimeSpan() does: it represents a time span in terms of the numeric equivalent:
CreateTimeSpan( 1, <!--- Days. ---> 0, <!--- Hours. ---> 0, <!--- Minutes. ---> 0 <!--- Seconds. ---> )
To help illustrate this idea, here are some common CreateTimeSpan() calls:
<!--- 1 Day: [ 1 ] ---> CreateTimeSpan( 1, 0, 0, 0 ) <!--- 1 Hour: [ 0.0416666666667 ] ---> CreateTimeSpan( 0, 1, 0, 0 ) <!--- 20 Minutes: [ 0.0138888888889 ] ---> CreateTimeSpan( 0, 0, 20, 0 )
With some quick math, you will see that since there are 24 hours in a day, one hour is 1/24 of the day value. Now, if you really know your ColdFusion tag default values, you will know that the STEP attribute defaults to 1, which means that if you leave out the STEP from a ColdFusion date/time loop, it will increment one day at a time.
But, to continue with the explanation, let's now take a look at what's inside the CFLoop. As stated before, dtToday contains the date value for each iteration. Because we have converted all of our dates to numbers, the value of dtToday is also numeric. That's why the first line:
The numeric value of today is: #dtToday#
... will display a value like 38911.6035301. We can, however, treat this as if it were a date and ColdFusion will perform the cast automatically. That is why, this line:
The "friendly" value of today is: #DateFormat( dtToday, "mmm d, yyyy" )#
... will display a value like "Jul 13, 2006."
One last thing to note is that while we are only displaying the date portion, the time values are also carried across CFLoop iterations. If your start date/time object has, for time, 2:00 PM, each dtToday value will have a different date, but that date will also be 2:00 PM.
Understanding this has not revolutionized the way I loop over dates, but it has certainly made my code for things such as event calendars 100 times easier to write. If you are interested in how this related to SQL, including date/time comparison, please check out my SQL date comparison post and my SQL time comparison post.
Want to use code from this post? Check out the license.