Casting Between Dates And Numeric Dates In Lucee CFML 5.3.7.48
One of the curious features of ColdFusion is the fact that you can represent Date/Time values as numbers. These numbers reference the fractional days since the "ColdFusion Epoch", which is 12/30/1899 00:00:00
for "reasons". I don't use these "numeric dates" very often. But, just this past week, I had to group a bunch of date/time values into "day buckets"; and, I found it quite convenient to call floor( date )
in order to get a normalized, numeric version. This brought up fond memories of the 2000-aughts when I was fascinated by "Date Math" in ColdFusion. As such, I wanted to take a moment and wax nostalgic about casting between Dates and Numbers in Lucee CFML 5.3.7.48.
A numeric date in ColdFusion is a Double
in which the integer portion is the number of days since the ColdFusion Epoch; and, the decimal portion is the factional time of the day. So, when I mentioned above that I was calling:
floor( dateTimeValue )
... this was performing two operations:
An implicit casting of the
dateTimeValue
input to aDouble
.An explicit stripping of the fractional portion, leaving me with an
Integer
number of days.
With that said, there are a number of ways to convert Date/Time values to Numbers and Numbers to Date/Time values. First, let's look at casting Date/Time values to numbers:
<cfscript>
echoLine( now() );
// Use the official method for converting dates to numeric representation.
echoLine( getNumericDate( now() ) );
// Use implicit casting of dates to numeric representation.
echoLine( now() * 1 );
echoLine( + now() );
// Dip down into the Java implementation to grab the numeric representation.
// --
// CAUTION: Only works if the value is an actual Date/Time instance - the value will
// not implicitly get cast to a Date/Time instance from other types.
echoLine( now().toDoubleValue() );
// Dip down into the Lucee implementation layer. This is actually what the
// getNumericDate() function is doing under the hood.
echoLine( createObject( "java", "lucee.runtime.op.Caster" ).toDoubleValue( now() ) );
// ------------------------------------------------------------------------------- //
// ------------------------------------------------------------------------------- //
public void function echoLine( required any input ) {
echo( input & "<br />" );
}
</cfscript>
The getNumericDate()
function is the "official" way to cast a Date/Time value to its numeric representation. But, as you can see, by treating a Date/Time value as a "number", Lucee CFML will automatically cast the Date/Time value to a numeric date as part of the expression evaluation. Then, of course, there are undocumented ways to dip into the Java layer.
That said, when we run this ColdFusion code, we get the following output:
{ts '2021-03-20 06:56:23'}
44275.28916663194
44275.28916663194
44275.28916663194
44275.28916663194
44275.28916663194
As you can see, we get the same output for each casting operation.
When it comes to casting a numeric date back into a Date/Time value, there doesn't appear to be an "official" way to do this. Meaning, there's no inverse of the getNumericDate()
function - at least none that I could find. As such, we have to get a little trickier:
<cfscript>
// Numeric representation of "2021-03-20 06:08:44".
value = 44275.25607354167;
// By adding ZERO days to the given value, Lucee will implicitly cast the numeric
// representation back to a date/time value.
echoLine( dateAdd( "d", 0, value ) );
// The dateTimeFormat() function will implicitly cast the numeric representation to a
// string representation; then, we can parse that string representation back to an
// actual date/time value.
echoLine( parseDateTime( dateTimeFormat( value ) ) );
// We can explicitly create a date/time value by plucking the relevant date parts out
// of the numeric representation.
echoLine(
createDateTime(
datePart( "yyyy", value ),
datePart( "m", value ),
datePart( "d", value ),
datePart( "h", value ),
datePart( "n", value ),
datePart( "s", value )
)
);
// Dip down into the Lucee implementation layer.
echoLine( createObject( "java", "lucee.runtime.op.Caster" ).toDate( value ) );
// ------------------------------------------------------------------------------- //
// ------------------------------------------------------------------------------- //
public void function echoLine( required any input ) {
echo( input & "<br />" );
}
</cfscript>
My goto approach for this is the dateAdd()
function wherein I just add zero-days to the given numeric date. This will implicitly cast the value back into a Date/Time instance. That said, we can format it and parse; and, dip down into the Java layer. And, when we run this ColdFusion code, we get the following output:
{ts '2021-03-20 06:08:44'}
{ts '2021-03-20 06:08:44'}
{ts '2021-03-20 06:08:44'}
{ts '2021-03-20 06:08:44'}
As you can see, we get the same output for each casting operation.
Once you embrace the notion that you can represent Date/Time values as numbers, you can start to do some interesting things. For example, you can randomly select a time during the day. Remember, since the numeric representation is a Double
in which the decimal portion is the fractional day, we can generate random times by adding a random decimal to an integer.
To see this in action, let's pick 10 random times today:
<cfscript>
// Get the Integer portion of today's numeric date.
today = fix( now() );
loop times = 10 {
// Rand() is a pseudo-random number generator that returns a decimal value
// between 0-1. Which, when combined with "numeric dates", gives us a way to
// generate random "fractional days".
randomTimeOfDay = ( today + rand() );
echo( dateTimeFormat( randomTimeOfDay ) & "<br />" );
}
</cfscript>
As you can see, we're using the rand()
function to generate random "fractional days", which we're then adding to the numeric representation of 12AM
this morning. And, when we run this ColdFusion code, we get the following output:
20-Mar-2021 19:22:14
20-Mar-2021 21:21:42
20-Mar-2021 20:49:17
20-Mar-2021 19:50:24
20-Mar-2021 10:20:12
20-Mar-2021 14:32:26
20-Mar-2021 20:06:08
20-Mar-2021 16:55:17
20-Mar-2021 14:42:46
20-Mar-2021 13:30:49
That's pretty cool!
Like I said, I don't actually use the numeric representation of dates all that often in ColdFusion. But, understanding that there is a numeric representation of dates means that it's a tool you can reach for if and when you need it. And, understanding how it works allows you to do some interesting things, like generating random dates.
Epilogue: Not To Be Confused With The Milliseconds Representation Of Dates
Just to be clear, "numeric dates" in ColdFusion are a separate concept from the fact that Date/Time values are all essentially facades that represent the number of milliseconds since Unix Epoch / Unix Time. This is true in most programming languages.
ASIDE: In JavaScript, you can do
Date.now()
ornew Date().getTime()
to get the current UTC milliseconds.
In Lucee CFML, you can access the milliseconds representation of a Date/Time value by calling .getTime()
on it. And, you can always access the current milliseconds using getTickCount()
- which I use all the time!
<cfscript>
// getTickCount() returns the current Unix time. I USE THIS ALL THE TIME (no pun intended).
echoLine( getTickCount() );
// For actual Date/Time objects, you can dip down into the Java layer and grab the
// underlying milliseconds representation of the date.
echoLine( now().getTime() );
// To use high-level ColdFusion, you can get the difference between a date and Epoch.
// That said, dateDiff() only has SECONDS granularity, which requires us to multiple
// by 1,000 to obtain milliseconds.
echoLine( dateDiff( "s", dateConvert( "utc2local", "1970-01-01 00:00:00" ), now() ) * 1000 );
// To get milliseconds from a non-Date/Time value, you just have to cast the value to
// a native Date/Time first.
echoLine( parseDateTime( "2021-03-20 08:05:00" ).getTime() );
// ------------------------------------------------------------------------------- //
// ------------------------------------------------------------------------------- //
public void function echoLine( required any input ) {
echo( input & "<br />" );
}
</cfscript>
Running this ColdFusion code, we get the following output:
1616241900915
1616241900915
1616241900000
1616241900000
Parsing date/time strings always get a little confusing when having to convert back-and-forth between UTC and the server's Local time.
Want to use code from this post? Check out the license.
Reader Comments
Are there any good ColdFusion (or at least pro-coldfusion) conventions or training coming up this year that you recommend? I would want advanced training in scripting in particular. Using both Adobe and Lucee
thanks in advance
@Bill,
I know the people on the Modernize or Die podcast are always talking about various conferences and training. The MOD podcast is run by Ortus Solutions that, itself, does lots of training and workshops around ColdFusion (and all of the ColdBox-related products). Look in the show-notes of one of their latest episodes: Episode 148. They also list out a number Adobe conferences and workshops as well.