Twitter: raymondcamden


Address: Lafayette, LA, USA

Fascinating issue with createTimeSpan

06-18-2009 5,038 views ColdFusion

A reader sent in an interesting issue. He was using createTimeSpan to list out 5 minute intervals between two hours. Here is what he used:

view plain print about
1<cfset dtHour = CreateTimeSpan(
2 0, <!--- Days. --->
3 0, <!---
4Hours. --->

5 5, <!--- Minutes. --->
6 0 <!--- Seconds. --->
7 ) /
>

8
9<cfset thecount = 0>
10<cfloop from="9:00 AM" to="10:00 AM" step="#dtHour#" index="i">
11<cfset thecount+=1>
12 <cfoutput>#timeformat(i,"short")#<br /></cfoutput>
13</cfloop>
14
15<cfoutput>
16 #thecount#
17</cfoutput>

In the code above he creates a 5 minute time span and then loops from 9 AM to 10 AM. I rarely use createTimeSpan, and when I have used it, I only used it with query caching. This worked... until he added another loop:

view plain print about
1<cfset thecount2 = 0>
2<cfloop from="10:00 AM" to="11:00 AM" step="#dtHour#" index="i">
3 <cfset thecount2+=1>
4 <cfoutput>#timeformat(i,"short")# #i#<br /></cfoutput>
5</cfloop>
6<cfoutput>
7#thecount2#
8</cfoutput>

This should show the same results, just a different hour, right? Check out the results:

What the heck? (Actually when I ran this I said something a bit strong.) There are 13 counts in the first loop and 12 in the second. Also note the second stops at 10:55, not 11.

I was a bit lost at first, but then I remembered something. The interval value is actually a number. On a whim I modified the code to output the interval and the index within the loop:

view plain print about
1<cfset dtHour = CreateTimeSpan(
2 0, <!--- Days. --->
3 0, <!---
4Hours. --->

5 5, <!--- Minutes. --->
6 0 <!--- Seconds. --->
7 ) /
>

8<cfoutput>#dtHour#<p></cfoutput>
9<cfset thecount = 0>
10<cfloop from="9:00 AM" to="10:00 AM" step="#dtHour#" index="i">
11<cfset thecount+=1>
12 <cfoutput>#timeformat(i,"short")# #i#<br /></cfoutput>
13</cfloop>
14
15<cfoutput>
16 #thecount#
17</cfoutput>
18<br /><br />
19<cfset thecount2 = 0>
20<cfloop from="10:00 AM" to="11:00 AM" step="#dtHour#" index="i">
21 <cfset thecount2+=1>
22 <cfoutput>#timeformat(i,"short")# #i#<br /></cfoutput>
23</cfloop>
24<cfoutput>
25#thecount2#
26</cfoutput>

Check out the result:

Ah, floating point numbers. I think you can see here where the issue is coming up - rounding errors. Notice how even the values for 10AM (at the end of the first loop and the first entry of the second loop) don't match.

Nice, so now we know why, how can we rewrite this? Here is a slightly modified version using a conditional loop:

view plain print about
1<!--- number of minutes --->
2<cfset step = 5>
3
4<cfset theTime = "9:00 AM">
5<cfset toTime = "10:00 AM">
6
7<cfloop condition="dateCompare(theTime,toTime) lt 1">
8    <cfoutput>thetime=#timeFormat(thetime)#<br></cfoutput>
9    <cfset theTime = dateAdd("n", step, theTime)>
10</cfloop>
11
12<p/>
13
14<cfset theTime = "10:00 AM">
15<cfset toTime = "11:00 AM">
16
17<cfloop condition="dateCompare(theTime,toTime) lt 1">
18    <cfoutput>thetime=#timeFormat(thetime)#<br></cfoutput>
19    <cfset theTime = dateAdd("n", step, theTime)>
20</cfloop>

This uses a simple numeric value for the number and passes it to the dateAdd function. What's kind of cool about this code is that you could also do non-even steps as well. (Sorry, not even as an even/odd, but a step value that won't fit evenly into the interval.)