Skip to main content
Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.

Getting ColdFusion Date Objects From UTC Milliseconds In Lucee CFML 5.3.3.62

By Ben Nadel on
Tags: ColdFusion

The fun thing about maintaining a legacy code base is that you're constantly turning over stones to find new and exciting areas of code-rot and technical debt. Just yesterday, I discovered that one of my ColdFusion servers was deadlocked on some Date/Time code that was not thread-safe. The CFML code in question was attempting to get a Date/Time object from a UTC milliseconds offset (since Epoch). This is something that I looked at 7-years ago; but, since Date/Time manipulation in the context of various TimeZones is never something that I feel confident about, I figured it would be good practice to look at creating ColdFusion Date objects from UTC milliseconds in Lucee CFML 5.3.3.62.

Some quick context: shortly after a deployment, I received a DataDog alert that one of my JVM's CPUs was being exhausted:

Lucee CFML code in deadlock exhausting the JVM CPU.

Upon seeing this, I triggered several Thread-Dumps, which showed me that four of my application threads were stuck on the following call-stack:

"http-apr-8500-exec-17"
	java.lang.Thread.State: RUNNABLE
		at org.apache.commons.collections4.map.AbstractHashedMap.getEntry(AbstractHashedMap.java:461)
		at org.apache.commons.collections4.map.AbstractReferenceMap.getEntry(AbstractReferenceMap.java:427)
		at org.apache.commons.collections4.map.AbstractReferenceMap.get(AbstractReferenceMap.java:244)
		at lucee.runtime.reflection.storage.SoftMethodStorage.getMethods(SoftMethodStorage.java:48)
		at lucee.runtime.reflection.Reflector.getMethodInstanceEL(Reflector.java:493)
		at lucee.runtime.reflection.Reflector.getMethodInstance(Reflector.java:681)
		at lucee.runtime.java.JavaObject.call(JavaObject.java:243)
		at lucee.runtime.java.JavaObject.call(JavaObject.java:268)
		at lucee.runtime.util.VariableUtilImpl.callFunctionWithoutNamedValues(VariableUtilImpl.java:756)
		at lucee.runtime.PageContextImpl.getFunction(PageContextImpl.java:1718)
		at services.datehelper_cfc$cf.udfCall1(/invision/services/DateHelper.cfc:47)
		at services.datehelper_cfc$cf.udfCall(/invision/services/DateHelper.cfc)

This line of code in the DateHelper.cfc ColdFusion component was attempting to use the java.text.DateFormat Java class. However, when you look at the documentation for this class, it clearly states that it is not thread-safe:

Date formats are not synchronized. It is recommended to create separate format instances for each thread. If multiple threads access a format concurrently, it must be synchronized externally.

A quick git-blame shows that this code is 5-years old (I told you that legacy code was fun)!

The code in question was attempting to convert a UTC milliseconds value into a ColdFusion date object. Rewriting this to be thread-safe is easy. All we have to do is use the UTC milliseconds value to create an instance of java.util.Date.

That said, TimeZones are funny and ColdFusion has not always behaved the same way. It used to be that a Date/Time stamp in ColdFusion had the TimeZone "baked into" the date (so to speak). In Lucee (and I think modern releases of Adobe ColdFusion), Date/Time objects are now TimeZone-agnostic. Which means, you have to tell Lucee which TimeZone you want to use when formatting a given Date/Time value.

Of course, in a legacy code base, a lot of code still depends on legacy behavior. Which is why Lucee CFML includes the dateConvert() function. The dateConvert() function alters a Date/Time object to represent either the Local or UTC TimeZone as if the Date/Time object had the TimeZone baked into it.

To codify this concept, let's look at some Lucee CFML code:

<cfscript>

	/**
	* I return a Lucee DateTime object that represents the given point in time which is
	* the milliseconds delta since Epoch.
	* 
	* CAUTION: Lucee DateTime objects do NOT have a TimeZone associated with them.
	* 
	* @milliseconds I am the milliseconds since Epoch.
	*/
	public date function getDateFromMilliseconds( required numeric milliseconds ) {

		var datetime = createObject( "java", "java.util.Date" ).init(
			javaCast( "long", milliseconds )
		);

		// The No-Op dateAdd() call is needed to convert from the Java Date type super
		// class down to the Lucee Date Implementation class.
		return( datetime.add( "l", 0 ) );

	}


	/**
	* I return a Lucee DateTime object that represents the given point in time which is
	* the milliseconds since Epoch. The result is "adjusted" for UTC, which means that 
	* the delta between the server TimeZone and the UTC TimeZone has been applied to the
	* underlying date.
	* 
	* NOTE: Historically, ColdFusion dates have not always been TimeZone agnostic. As
	* such, this method is for code that expects the "old" ColdFusion behavior.
	* 
	* @milliseconds I am the milliseconds since Epoch.
	*/
	public date function getUtcDateFromMilliseconds( required numeric milliseconds ) {

		return( dateConvert( "local2utc", getDateFromMilliseconds( milliseconds ) ) );

	}

	// ------------------------------------------------------------------------------- //
	// ------------------------------------------------------------------------------- //

	// Output the current time information for the server. This will act as our anchor
	// for the rest of the data.
	// --
	// NOTE: I am running this demo through CommandBox, which started up the server in my
	// local TimeZone, which is US-EST.
	echo( "<p> Server TimeZone: #getTimeZone()#" );
	echo( "<p> Now: #now().timeFormat( 'HH:mm:ss' )#" );
	echo( "<p> Now (in UTC TZ): #now().timeFormat( 'HH:mm:ss', 'UTC' )#" );

	// Now, let's look at some derived dates using our User-Defined functions.
	rawDate = getDateFromMilliseconds( getTickCount() );
	utcDate = getUtcDateFromMilliseconds( getTickCount() );

	echo( "<p> Raw Date: #rawDate.timeFormat( 'HH:mm:ss' )#" );
	echo( "<p> Forced UTC Date: #utcDate.timeFormat( 'HH:mm:ss' )#" );

	// Since the Lucee DateTime objects don't have a TimeZone associated with them, we
	// can tell Lucee which TimeZone to use when formatting. In this case, we'll take the
	// raw date and format it using UTC.
	echo( "<p> Raw Date (in UTC TZ): #rawDate.timeFormat( 'HH:mm:ss', 'UTC' )#" );

</cfscript>

This demo has two methods:

  • getDateFromMilliseconds( milliseconds )
  • getUtcDateFromMilliseconds( milliseconds )

The first creates a Lucee CFML Date/Time object from the given UTC milliseconds offset. The returned value is not associated with any particular TimeZone.

The second creates a Lucee CFML Date/Time object that is being "adjusted" for the UTC TimeZone. This means that the returned value is in the "UTC" TimeZone for all intents and purposes, and doesn't need any additional information when being serialized (it will be formatted and serialized in UTC time).

Now, when we run the above Lucee CFML code, we get the following output:

Server TimeZone: America/New_York

Now: 07:34:55

Now (in UTC TZ): 12:34:55

Raw Date: 07:34:55

Forced UTC Date: 12:34:55

Raw Date (in UTC TZ): 12:34:55

What you can see from this output is that the "raw" date - the one not associated with any particular TimeZone - can be output using the server time by default (which is EST for my MacBook); or, by UTC as explicitly provided in the formatting method, .timeFormat().

The "adjusted" UTC date, on the other hand, is formatted in UTC time even without any additional information because the underlying date has been physically changed to represent the UTC TimeZone relative to my MacBook's clock (ie, the Server Clock).

Dealing with Date/Time objects in ColdFusion is super easy. As long as you never have to render those values for the user, persist them to a database, or serialize them for inter-process communication. Which is, of course, every single application you will ever build. It's crazy that after all this time working in web development, this kind of stuff never quite feels second-nature. Really, I'm only getting incrementally more confident when dealing with Date/Time values in ColdFusion and Lucee CFML. Which is why I like to try and examine all this stuff, even if I've done it before.



Reader Comments

After posting this, I was going to say that your life becomes easier if your Server is set to run in UTC (unlike my MacBook which runs in EST where I live). But, giving that a moment of reflection, how much easier does your life actually get? By which I mean, should your code ever assume that your server is running in UTC? Or should your code always be assuming that your server may be running "anywhere", and that the code should be responsible for converting to/from UTC as needed?

To be honest, I am not sure how I feel about this.

Reply to this Comment

I've never really understood how to deal with localisation, in relation to date time. Having looked through the documentation of moment.js, it is enough to give anyone a headache, although I quite like this JavaScript library.

Am I right in thinking that UTC, is a universal time. So, it takes an average of all the different time zones and then there is an offset that is added/subtracted to calculate local time.

And what did you mean when you said:


It used to be that a Date/Time stamp in ColdFusion had the TimeZone "baked into" the date (so to speak)

Are you saying that Coldfusion tried to work out what your locality was based on the machine clock? Do, we now have to supply the locale?

Reply to this Comment

@Charles,

Yeah, TimeZone and anything related to preparing data for a user's local experience is challenging. UTC is, the way I think about it, just a particular TimeZone. Then, all the other TimeZone are defined as an offset from UTC. So, I'm in EST, which -5 or -4 hours from UTC (depending on the daylight saving time). So, if its 12PM UTC, it's (12-5) EST ... or 8AM EST.

As far as my statement about the timezone being "baked into" old ColdFusion dates, what I meant was that calling something like dateConvert("local2utc") would actually change the underlying date by adding/subtracting the "local" UTC offset. So, instead of a date just having a TimeZone associated with it, the actual date value would be mutated to render the right value.

It's hard to explain because, frankly, I don't really understand it well enough to use the right words :D And, there was a ColdFusion setting that would turn this off. Here's some old information on the issue if you are curious:

Ultimately, the best thing is just to:

  • Make sure your ColdFusion server is running in UTC TimeZone.
  • Store all your dates in UTC TimeZone in the database.
Reply to this Comment

Thanks for this advice Ben.

Very useful, especially the part about, storing date/time as UTC ,and making sure that the ColdFusion server is running in UTC. So, after retrieving a date from the DB, we should then convert it to which ever locale is required, like:

someDate.timeFormat( 'HH:mm:ss', 'GMT' )

Interestingly, ACF, according to their documentation, does not support the third parameter for:

timeFormat(date,mask)
dateFormat(date,mask)

Instead, for ACF, we need to use LSTimeFormat and ** LSDateFormat**

Lucee:

timeFormat(date,mask,timezone)
dateFormat(date,mask,timezone)

Reply to this Comment

Post A Comment

You — Get Out Of My Dreams, Get Into My Blog
Live in the Now
Oops!
NEW: Some basic markdown formatting is now supported: bold, italic, blockquotes, lists, fenced code-blocks. Read more about markdown syntax »
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.