Ask Ben: Appending Times To Date/Time Objects
In ColdFusion, how can I append a time to a date?
This question is a bit ambiguous so I will try to answer both questions it might be. I think you are asking one of the two following questions:
- How can I add or replace the time portion of a date/time object so that the resultant date/time object has the original date and the given time?
- How can I add a given amount of time to a given date/time object?
If you have been following my earlier posts, you will see that you can handle date/time objects in ColdFusion in many, many ways. This means that the problems above can be solved in many ways. To start with, I will discuss the possible solutions. Then, I will go over time testing so you can see which one is actually faster.
For starters, I am going to tackle the idea of replacing the time part of a date/time object with the given time parameters. This will not change the date portion, only the time portion. For this section, I am going to assume that we have the following date/time object:
<cfset dtNow = "2006-07-19 08:43:15 AM" />
This will give us a date/time object that has both a date and a time portion.
Solution A1: We can use the built-in ColdFusion method CreateDateTime(). This method allows us to create a date/time object by supplying all of the individual parts (year, month, day, hours, minutes, seconds). Since we only want to "create" the time part, you can pass in the year, month, and day from the existing date/time object:<br>
<!--- Create a new date with a time portion. --->
<cfset dtOne = CreateDateTime(
Year( dtNow ),
Month( dtNow ),
Day( dtNow ),
18, <!--- 6 PM. --->
00, <!--- 0 Minutes. --->
00 <!--- 0 Seconds. --->
) />
<!--- Dump out result. --->
<cfdump var="#CreateODBCDateTime( dtOne )#" /><br>
Here, you can see that we are only setting the time parts but keeping the old date parts. Also, I am using the ColdFusion method CreateODBCDateTime() here (and throughout the entry) because it will give us a consistent formatting of both the date and time parts of the date/time object with minimal coding. This is for display purposes only. I would not use this to format dates in my code.
Solution A2: We can treat the date/time object as a number whose integers are the days and whose decimals are the time part (day fractions). To do so, we can round out the date, creating a number that has no time, then add a time span to it:<br />
<!--- Create a new date based on the rounded date and a time span. --->
<cfset dtTwo = (
Round( dtNow ) +
CreateTimeSpan(
0, <!--- Days. --->
18, <!--- Hours. --->
0, <!--- Minutes. --->
0 <!--- Seconds. --->
)
) />
<!--- Dump out result. --->
<cfdump var="#CreateODBCDateTime( dtTwo )#" /><br>
This method has the advantage that you don't have to pass in the three parts of the original date object but instead just pass in the date object as a whole to the Round() method. While you cannot see it if you use my CFDump tag, keep in mind that when you Round() the date and use CreateTimeSpan(), ColdFusion is converting the date/time object to a floating point number. This is STILL a date, but you need to format it for display (can be used internally as a date without formatting).
Solutions A3: You can treat the date/time object as a formatting string and then just text-append the time part to the date part:<br />
<!--- Create a date/time as a string object. --->
<cfset dtThree = (
DateFormat( dtNow, "yyyy-mm-dd" ) &
" " &
"18:00:00"
) />
<!--- Dump out result. --->
<cfdump var="#CreateODBCDateTime( dtThree )#" /><br>
This method will create the string "2006-07-19 18:00:00" which ColdFusion will properly treat as a date/time object. Even before testing though, I can tell you that this is slow. DateFormat() is a slow function and string concatenation is slow.
That's all I can really think of for problem one. Now, let's talk about problem two, adding time to an existing date/time object so that both the date and the time parts are potentially updated. There's really only one way I can think about doing this that isn't entirely crappy and that is to simply add the time span to the date/time object:
<!--- Create a new date by adding a time span. --->
<cfset dtFour = (
dtNow +
CreateTimeSpan(
0, <!--- Days. --->
2, <!--- Hours. --->
0, <!--- Minutes. --->
0 <!--- Seconds. --->
)
) />
<!--- Dump out result. --->
<cfdump var="#CreateODBCDateTime( dtFour )#" /><br>
The equation above forces the date/time object (dtNow) to be converted to a floating point number so that it can play well with CreateTimeSpan(). Then, we add the time span (another floating point number) and get the resultant date as a floating point number (which we already discussed).
Keep in mind that while our examples have used the CreateTimeSpan() method to create time spans less than a day, if you had over 24 hours or set the day argument, the date portion of the resultant date/time object will also be altered.
One final note, you CANNOT use DateAdd() and CreateTimeSpan() to add fractions of a day. This would be nice, but DateAdd() requires the use of integer values and CreateTimeSpan() created floating point numbers.
Ok, so those are the potential solutions, now let's test which ones of the first set are the fastest.
<cftimer label="One" type="outline">
<cfloop index="intI" from="1" to="1000">
<cfset dtOne = CreateDateTime(
Year( dtNow ),
Month( dtNow ),
Day( dtNow ),
18, <!--- 6 PM. --->
00, <!--- 0 Minutes. --->
00 <!--- 0 Seconds. --->
) />
<cfset WriteOutput(
CreateODBCDateTime( dtOne )
) />
</cfloop>
</cftimer>
... This runs on average at about 540 ms.
<cftimer label="Two" type="outline">
<cfloop index="intI" from="1" to="1000">
<cfset dtTwo = (
Round( dtNow ) +
CreateTimeSpan(
0, <!--- Days. --->
18, <!--- Hours. --->
0, <!--- Minutes. --->
0 <!--- Seconds. --->
)
) />
<cfset WriteOutput(
CreateODBCDateTime( dtTwo )
) />
</cfloop>
</cftimer>
... This runs on average at about 250 ms. This is less than have the time of the CreateDateTime() method. Who knows what is going on underneath the ColdFusion hood, but you can see that the CreateDateTime() method uses 4 function calls where as the second solution is only two method calls. Hmmmm.
<cftimer label="Three" type="outline">
<cfloop index="intI" from="1" to="1000">
<cfset dtThree = (
DateFormat( dtNow, "yyyy-mm-dd" ) &
" " &
"18:00:00"
) />
<cfset WriteOutput(
CreateODBCDateTime( dtThree )
) />
</cfloop>
</cftimer>
... This runs on average at about 460 ms. First off, I am SHOCKED that the string-concatenation method runs faster than the CreateDateTime() method. SHOCKED! Especially as this one uses DateFormat() which is very slow.
Well, shock or no shock, it is clear that treating the date/time object as a number and then doing math on it is going to be the fastest solution. Math always is :) But, more than fast, you have to decide for yourself which is going to make your code most readable and therefore the most maintainable. Happy coding!
Want to use code from this post? Check out the license.
Reader Comments