Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.
I am the chief technical officer at InVision App, Inc - a prototyping and collaboration platform for designers, built by designers. I also rock out in JavaScript and ColdFusion 24x7.
Meanwhile on Twitter
Loading latest tweet...
Ben Nadel at CFUNITED 2008 (Washington, D.C.) with:

Converting ColdFusion Date/Time Values Into ISO 8601 Time Strings

By Ben Nadel on
Tags: ColdFusion

Yesterday, I was experimenting with a feature of Amazon's Simple Storage Service (S3) that allows users to upload files directly to S3 using a normal, client-side HTTP Form POST. As part of the request, you (as the server-side programmer) need to provide a policy that defines an expiration date in ISO 8601 time format. This is easy enough to do inline; but I thought it would be nice if this kind of time format could be obtained using something like ColdFusion's getHttpTimeString().

To mimic the naming and behavior of the getHttpTimeString() function, I created a user-defined function (UDF) called getIsoTimeString(). This function takes your date/time object and returns the ISO time string using the UTC timezone. Unless otherwise flagged, the getIsoTimeString() will assume your date/time object is in server-local time and convert it to UTC before formatting it:

  • <cfscript>
  •  
  •  
  • // I take the given date/time object and return the string that
  • // reprsents the date/time using the ISO 8601 format standard.
  • // The returned value is always in the context of UTC and therefore
  • // uses the special UTC designator ("Z"). The function will
  • // implicitly convert your date/time object to UTC (as part of
  • // the formatting) unless you explicitly ask it not to.
  • string function getIsoTimeString(
  • required date datetime,
  • boolean convertToUTC = true
  • ) {
  •  
  • if ( convertToUTC ) {
  •  
  • datetime = dateConvert( "local2utc", datetime );
  •  
  • }
  •  
  • // When formatting the time, make sure to use "HH" so that the
  • // time is formatted using 24-hour time.
  • return(
  • dateFormat( datetime, "yyyy-mm-dd" ) &
  • "T" &
  • timeFormat( datetime, "HH:mm:ss" ) &
  • "Z"
  • );
  •  
  • }
  •  
  •  
  • // ------------------------------------------------------ //
  • // ------------------------------------------------------ //
  •  
  •  
  • currentTime = now();
  •  
  • // Compare the HTTP time to the ISO time.
  • writeOutput( "HTTP Time: " & getHttpTimeString( currentTime ) );
  • writeOutput( "<br />" );
  • writeOutput( "ISO Time: " & getIsoTimeString( currentTime ) );
  •  
  •  
  • </cfscript>

When we run the above code, we get the following HTTP and ISO time formats:

HTTP Time: Mon, 29 Jul 2013 13:14:52 GMT
ISO Time: 2013-07-29T13:14:52Z

I'm sure this kind of function has been written a million times - this is mostly here for my own personal reference.



Looking For A New Job?

100% of job board revenue is donated to Kiva. Loans that change livesFind out more »

Reader Comments

You might test to ensure that dateConvert() is accounting for DST correctly. I have found in the past (at least as recently as CF8,) that dateConvert() applies the DST offset based on the DST offset of the current date and time...not the actual offset for date and time submitted to the function.

Example: Now() is Jul. 29 @ 2:00pm EDT. Make sure that if you submit a datetime NOT in the local DST range (say Nov. 4 @ 2:00pm EST) that convertDate() doesn't try to use the current local DST offset when it does the conversion.

I have written a fairly elaborate dateTime conversion function that first determines whether DST applies for the dateTime passed (based on the timezone and DST rules for locality in question,) then does the conversion taking into account that info.

I haven't verified this in CF10, but it might be worth checking out.

Reply to this Comment

I recently updated some code Constant Contact's API v2. In scheduling an email, I provide a ISO 8601 date. Although I believe the standard makes it optional, the API required the trailing decimals on the seconds. I just hardcoded ".000" to get:

2013-07-29T13:14:52.000Z

Reply to this Comment

I've been meaning to standardize my date/times in my application. Currently, I just "know" my servers are in CST so I account for that in code, but I would love to use UTC as a standard and base all calculations off that. Is there an easy way to determine the local timezone offset so that the ISO 8601 date can be converted to local time?

Reply to this Comment

@David, would a copy of that more elaborate function happen to be available somewhere? I'm in a position where an application is going to switch from using a single timezone to UTC and we have several years of old data that will need to be converted back from local time to UTC but will need to take the former time zones into account as well.

Reply to this Comment

@David,

Very interesting. I had thought that UTC time didn't take Daylight Saving Time (DST) into account. I thought that that was part of the reason everyone converts to it for inter-service communication - that's its always standard. That said, "timezone hell" is called "hell" for a reason :(

Reply to this Comment

@Matt,

Ah, thanks for the tip. The example I was going off of didn't have the .000; but, I am sure I'd be able to add it without messing anything up.

Reply to this Comment

@Chris,

At InVision, we do a lot of JavaScript-oriented work with loads of API calls to the server (basically we have a few single-page apps (SPAs)). We recently starting asking the Browser to report its local timezone offset when communicating to the server. We use the timezone offset in the Date object:

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/getTimezoneOffset

In the AJAX request/response interceptor, we inject the timezone with each AJAX request. Something like this:

  • var now = new Date();
  •  
  • // Make sure that this browser actually supports this feature.
  • if ( now.getTimezoneOffset ) {
  •  
  • headers[ "X-Timezone-Offset" ] = -now.getTimezoneOffset();
  •  
  • }

Then, on the server, we check to see if that header value is different from the one we currently have stored (for that user).

It's not perfect, and I'm sure there are flaws in the logic; but, so far, it's been working out pretty well.

NOTE: We negate the timezone offset because, on the server-side, we end up adding it to UTC time to get the user's local time.

Reply to this Comment

@Justin,

We had to deal with that a few months ago. "Luckily" all our dates were in EST to begin with, so no one but east-coasters had accurate time anyway. That made the conversion much easier :D We simply converted all EST time to UTC time.

Good luck with your conversion. It definitely makes some things easier and some things harder (to think about).

Reply to this Comment

@Ben,

Correct, UTC itself is not affected by DST, but since local time is (or can be,) it still matters whenever you are converting from one to the other. (Example: EST is UTC -5, but EDT is UTC -4.) We store everything in UTC wherever possible, but some clients prefer to see time stamps in their local time. So the only time we really have to worry about it is during output / display.

@Justin,

If I can find the function in question and sanitize it a bit, I'd be happy to share.

David L.

Reply to this Comment

@David,

Ah gotcha - I see what you mean. Yeah, we've run into that a bit, for sure. Like I was saying in an earlier comment to Chris, we store the timezone offset defined by the browser; honestly, I have no idea if that takes DST into account... and, even if it does, there's no saying that it's accurate at the time we go to use it ... heck if the user was in a hotel one day in one timezone, the computer may have automatically updated and then the next time we go to send an email, we'd be off.

That said, we do try to render the date/times on the client-side as much as possible. To do that, we pass the date/time values (over AJAX) as UTC milliseconds. Then, on the client, we let the browser convert that into a local Date object (pseudo code):

  • // CreatedAt is passed as UTC-milliseconds.
  • var date = new Date( response.createdAt );
  •  
  • something.dateLabel = formatDate( date );

This way, at least we pass the burden of proper timezone calculations to the client.

Of course, that is ONLY when we deal with the browser. When we send out an email, things definitely get a bit more fuzzy :)

Reply to this Comment

@Ben

Very interesting. I like the idea of storing the offset with the user's profile for later use, though you could also let them choose as well.

As for the negation of the offset, I'm lost. Isn't the offset already related to UTC? For instance UTC-8:00 is PST (California for instance) whereas UTC+8:00 would be somewhere in Taiwan. If you negate it, wouldn't that reverse them (two negatives make a positive)?

I imagine that if the stored offset differs from the injected offset, you go with the injected offset and update the stored value?

Reply to this Comment

@Chris,

The negation of the timezone offset is a strange thing and it took me a while to wrap my head around. The problem is that we're not using the offset to get from LOCAL time to UTC time; instead, we're using the offset to get from UTC time (stored in the database) to the user's LOCAL time.

So, I'm in the EST timezone, which is currently (according to my browser) in GMT-4 time. The "-4" there means that if I add -4 hours to GMT, I can EST.... which is what you are saying.

However, if I get my current timzone offset:

( new Date() ).getTimezoneOffset()

... I get "240" - a positive number. The notion here being that if I ADD 240 to my EST time, I'll get the current UTC time.

But, I don't want to go from EST to UTC - I want to go from UTC to EST. As such, I need to negate the "240" that the browser reported, so that I can get this:

UTC-Time + -240 == EST

Does that make more sense?

Reply to this Comment

Aha! That does make sense. So, if you were GMT+4, then getTimeOffset() would return -240 (a negative number). Two negatives being a positive, you'd get

UTC-Time + --240 (a positive number) == GMT+4

Reply to this Comment

@Chris,

Yeah, exactly! The good thing is, once you get it in place, you don't have to think about it again :)

Reply to this Comment

you can add characters by escaping them with an apostrophe, so it can be as simple as:

  • #DateTimeFormat( now(), "yyyy-mm-dd'T'HH:nn:ss" )#

I only tested this in Railo 4.1 but I expect it to work the same in ACF10 as both simply pass the mask to Java's java.text.SimpleDateFormat and the apostrophe is supported there.

to add the TimeZone try the following mask:

  • #DateTimeFormat( now(), "yyyy-mm-dd'T'HH:nn:ss'Z'Z" )#

Reply to this Comment

@Igal,

That would be cool, but it doesn't work in ColdFusion 9 (the version I have running now). I haven't tested in CF10.

Reply to this Comment

UPDATE: from Railo 4.2.0.001 forward you can use:

  • #DateTimeFormat( now(), "ISO8601" )#

Reply to this Comment

Post A Comment

You — Get Out Of My Dreams, Get Into My Comments
Live in the Now
Oops!
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.