DEV Community

James Moberg
James Moberg

Posted on • Updated on

createIsoString() - A ColdFusion User-Defined Function (UDF) to replace dateTimeFormat("iso")

This ColdFusion UDF converts a date object or string into a UTC, ISO8601, RFC 339, ATOM or W3C string with optional millisecond precision and ability to specify timezone.

TLDR? Go straight to createIsoString UDF.

Here's some history of current date/time and timezone-related functions.

Built-in dateConvert(conversionType, date) and getTimezoneInfo()

Since 1999's release of ColdFusion 4, a built-in dateConvert function has been available to convert local to UTC time and vice-versa. getTimeZoneInfo has also been available to retrieve only the local time zone based on system settings. This functions are beneficial, but I'm not sure why it's limited to only supporting local time.

Lucee's getTimeZoneInfo function added support for timezone and locale back in May 2016. This function is a lot more useful that Adobe's.

My servers are located on the west coast and I have occassionally have requirements to output UTC strings for multiple timezones throughout the US using the local offset for the timezone. The dateConvert and getTimeZoneInfo function don't offer much time/conversion assistance for anything "non-local".

I've noticed some oddities of using converted dates. The function states that it returns "UTC or local-formatted time object" which can behave differently than values create using createDateTime. I was encountering what I believe to be comparison/diff bugs with ColdFusion 2016 and stopped using it as I couldn't wrap my head around what was happening.

Built-in dateTimeFormat(date [, mask [, timezone]])

A dateTimeFormat function was added in 2012's ColdFusion 10. Support for an iso mask was added over 4 years later starting with ColdFusion 2016. Lucee supported an iso8601 mask with v4.2.0.001 in 2014 and currently accepts either iso or iso8601.

The strings generated by dateTimeFormat support the system local timezone with by default. Both Adobe & Lucee allow a timezone to In addition, precision is dropped. ISO 8601, UTC, RFC 3339, ATOM & W3C all support fractional seconds, but you'd never know that by the lack of options available in the built-in function.

Built-in setTimezone(timezone) (CF2021+)

As of CF2021, the timezone can be manually "reset" within a request using setTimeZone. This function must have been copied from Lucee, but I'm not sure how long Lucee's had the feature. Lucee provides a list of all available timezones in the Lucee administrator (Settings/Regional).

I'm wondering what side-effects this may have. It's request-only, but would you need to reset the timezone back to the default after performing function-specific calculations that would require a different timezone?

Adobe shares this CF2021-only code snippet using java.util.TimeZone to retrieve the available 628 timezones on the function detail page to return a native array of timezones. NOTE: Java.util.TimeZone warns again using deprecated three-letter time zone IDs (such as "PST", "CTT", "AST") as these abbreviations are often used for multiple time zones.

tz_list = CreateObject("java", "java.util.TimeZone").getAvailableIDs(); // get a list of all timezone supplied by java util.
writedump(var=tz_list, label="java.util.TimeZone");

Enter fullscreen mode Exit fullscreen mode

You can also access java.time.ZoneId directly for this info.

zoneIds = listtoarray(createobject("java", "java.time.ZoneId").getAvailableZoneIds().toString().replaceAll("[\[\] ]",""));
writedump(var=zoneIds, label="java.time.ZoneId");
Enter fullscreen mode Exit fullscreen mode

TimeZone-CFC (Using ICU4J JAR)

Paul Hastings (from now-defunct blog at sustainableGIS․com) shared a TineZone-CFC component back in 2006 that uses the ICU4J library. I monitor & manually update this library and noticed that they are always making time zones updates with each release. It's much easier to upgrade this library rather than upgrade the entire JDK. For example, some time zones were updated by IANA on March 28, 2023 and this data is available in ICU 73 released on 2023-06-15. If you're using CF10-2021, do you know when the timezone tables you are using were last updated? (If you only care about local time, it probably doesn't matter.) [UPDATE 2023-07-20: Java 11.0.20 was just released and contains IANA time zone data 2023c with multiple DST changes.]

I recommended an update to support fractional seconds back in 2018, but this library hasn't received any updates since 2010.

This TimeZone-CFC has the following helpful methods and currently lists 637 timezones (if using ICU 73):

  • isDST determines if a given date & timezone are in DST. if no date or timezone is passed the method defaults to current date/time and server timezone. PUBLIC.
  • getAvailableTZ returns an array of available timezones on this server (ie according to server's icu4j version). PUBLIC.
  • getTZByOffset returns an array of available timezones on this server (ie according to server's icu4j version) with the same UTC offset. PUBLIC.
  • getTZByCountry returns an array of available timezones on this server (ie according to server's icu4j version) within a given country. PUBLIC.
  • isValidTZ determines if a given timezone is valid according to getAvailableTZ. PUBLIC.
  • usesDST determines if a given timezone uses DST. PUBLIC.
  • getRawOffset returns the raw (as opposed to DST) offset in hours for a given timezone. PUBLIC.
  • getTZOffset returns offset in hours for a given date/time & timezone, uses DST if timezone uses and is currently in DST. PUBLIC.
  • getDST returns DST savings for given timezone. PUBLIC.
  • castToUTC return UTC from given datetime in given timezone. required argument thisDate, optional argument thisTZ valid timezone ID, defaults to server timezone. PUBLIC.
  • castfromUTC return date in given timezone from UTC datetime. required argument thisDate, optional argument thisTZ valid timezone ID, defaults to server timezone. PUBLIC.
  • castToServer returns server datetime from given datetime in given timezone. required argument thisDate valid datetime, optional argument thisTZ valid timezone ID, defaults to server timezone. PUBLIC.
  • castfromServer return datetime in given timezone from server datetime. required argument thisDate valdi datetime, optional argument thisTZ valid timezone ID, defaults to server timezone. PUBLIC.
  • getServerTZ returns server timezone. PUBLIC
  • getServerTZShort returns "short" name for the server's timezone. PUBLIC
  • getServerId returns ID for the server's timezone. PUBLIC

getIsoTimeString(datetime[, convertToUTC]) UDF

Ben Nadel shared a getIsoTimeString UDF that he wrote for ColdFusion 9 back in 2013 that "takes your ColdFusion date/time object and returns a string that represents the date in ISO 8601 complaint format. This separates the date/time values with a 'T' and ends with the UTC-marker, 'Z'.".

As one commenter shared, the factional second data is not generated. Even though it's optional, the Constant Contact APIv2 required it. (I've used dateTimeFormat with a yyyy-mm-dd'T'HH:nn:ss.lllZ mask to overcome the truncation.)

Introducing createIsoString() UDF

I've developed a UDF for internal use to support a couple different functions. I wanted to:

  • support local, UTC and other timezones when calculating the offset
    • built-in dateTimeFormat("iso") only generates values for local server offset. No other option is available.
  • support date/time truncation to DAYS, HALF_DAYS, HOURS, MINUTES, SECONDS & MILLIS (or no rounding/precision truncation).
    • built-in dateTimeFormat("iso") rounds to the nearest second. No other option is available.
    • if I was adding support for fractional seconds, I may as well extend support to truncate the time using built-in ChronoUnit
  • explicitly output strings to support various ISO/RFC/ATOM/W3C standards (ie, +0000, +00:00 or Z)
    • built-in dateTimeFormat("iso") outputs "Z" for GMT. No other option (+0000 or +00:00) is available.
  • validate timezone string since Java is case-sensitive & requires explicit case. (I found using regex to validate timezone values was up to 25x faster than using arrayFindNoCase.)
  • add local & server timezone options to fallback to whatever the system timezone is currently set to.
  • throw an error or ignore when optional values are invalid. (Invalid dates will return an empty string while all other options will simply ignore the invalid option.)

It

Here's a table comparison of outputs:

dateTimeFormat("iso") createIsoString() createIsoString(timezone="utc") createIsoString(timezone="US/Eastern") createIsoString(format="8601 or atom")
2015-04-11T19:02:34-07:00 2015-04-11T19:02:34.274-07:00 2015-04-12T02:02:34Z 2015-04-11T22:02:34.274-04:00 2015-04-11T19:02:34.274-07:00
2023-07-16T15:39:03-07:00 2023-07-16T15:39:03.552-07:00 2023-07-16T22:39:03Z 2023-07-16T18:39:03.552-04:00 2023-07-16T15:39:03.552-07:00

Here's the source code. Enjoy!

https://gist.github.com/JamoCA/3e825f773d3bbb45f5c36ee85793e10e

Top comments (0)