The horror of IsDate() in ColdFusion and Lucee

Very recently, a small number rounding change had been applied to the business I work at. The new rounding was done with the CFML numberFormat() function, which returns a string representation of the number.
For example, #numberFormat(8.831, "_.0")# would return the string "8.8", and #numberFormat(4.49, "_.0")# would return the string "4.5" (rounded up).

Now, despite testing efforts at the backend, a UI bug emerged after going live with this change. It turned out, the rounded number was shown on-screen as a timestamp. Say what?! Yes, as a timestamp.

After digging into the code, it turned out we used a custom generic function for formatting API output. This function used the check isDate() to determine if the value needed to be rendered as a date string. And that is what triggered the bug: our rounded number, now a string, was seen as a date!

I went to tryCF.com to do some checks, and what I always kinda knew, "don't trust isDate in CFML", now became absolutely clear...

I created the following code to check many isDate formats:

<cfoutput>
  <h3>#server.coldfusion.productName#
    #structKeyExists(server, "lucee") ? server.lucee.version : server.coldfusion.productVersion#</h3>
  <h3>Locale: #getLocale()#</h3>

    #renderIsDate("8.8")#
    #renderIsDate(8.8)#
    #renderIsDate("8-8")#
    #renderIsDate("8-8-8")#
    #renderIsDate("2000.8")#
    #renderIsDate("1900.8")#
    #renderIsDate("1800.8")#
    #renderIsDate("1100.8")#
    #renderIsDate("100.8")#
    #renderIsDate("99.8")#
    #renderIsDate("10.8")#
    #renderIsDate((1 eq 1))#
    #renderIsDate((1 eq 0))#
    #renderIsDate(now())#
    #renderIsDate("2000-01-01")#
    #renderIsDate("20-01-01")#
    #renderIsDate("13-13-13")#
    #renderIsDate("13-13-12")#
    #renderIsDate("13.13.12")#
    #renderIsDate("13-12-13")#
    #renderIsDate("13.12.13")#
    #renderIsDate("12-13-13")#
    #renderIsDate("12.13.13")#
    #renderIsDate("2000")#
</cfoutput>

<cfscript>
    function renderIsDate(any value){
        return "isDate(#serializeJson(value)#) = "
            & isDate(value)
            & " (type=#replace(value.getClass().getName(), 'java.lang.', '')#)<br>";
    }
</cfscript>

I checked the results for Adobe ColdFusion 2018, 2016, 11, and version 10. And of course, on Lucee 5.3 and 4.5.

The results

I color-coded the results with my assumption of a valid date:

  • Object is either some Java Date type,
  • Or is a string which has 3 numeric parts divided by one of: "/", "-", "."
  • In case of 3 numeric parts, it also must match one of 3 valid date-part patterns: DMY, MDY, YMD, and must translate to an existing date (2000-02-30 does not exist, as February has 28/29 days)

Conclusions drawn from the data

  • Adobe CF 2018 is winner! (come on Lucee, catch up please!)
  • Adobe CF 2018 changed it's IsDate() implementation
  • Adobe CF 2018 has an inconsistency: isDate(&amp;quot;32.8&amp;quot;) = YES, but isDate(&amp;quot;31.8&amp;quot;) = NO
  • Lucee has the same behavior as Adobe CF versions 2016 and earlier, except where it differentiates from Adobe CF, by keeping 8.8 as a number instead of a string (and thus not seeing the period in the number as a delimiter).

And as a bonus, I found:

  • Adobe CF 2018 finally treats numbers as numbers, instead of as strings.
        ColdFusion Server 10,0,16,293499    
        ColdFusion Server 11,0,14,307976 Lucee 5.3.6.61
    ColdFusion Server 2018,0,09,318650 ColdFusion Server 2016,0,15,318650 Lucee 4.5.5.016
    Locale: English (US) Locale: English (US) Locale: english (us)
Is a 3-part valid-ish date, or a real date            
#isDate("8.8")#   NO (type=String) YES (type=String) true (type=String)
#isDate(8.8)#   NO (type=coldfusion.runtime.CFDouble) YES (type=String) false (type=Double)
#isDate("8-8")#   NO (type=String) YES (type=String) true (type=String)
#isDate("8-8-8")# yes YES (type=String) YES (type=String) true (type=String)
#isDate("2000.8")#   YES (type=String) YES (type=String) true (type=String)
#isDate("1900.8")#   YES (type=String) YES (type=String) true (type=String)
#isDate("1800.8")#   YES (type=String) YES (type=String) true (type=String)
#isDate("1100.8")#   YES (type=String) YES (type=String) true (type=String)
#isDate("100.8")#   YES (type=String) YES (type=String) true (type=String)
#isDate("99.8")#   YES (type=String) YES (type=String) true (type=String)
#isDate("10.8")#   NO (type=String) YES (type=String) true (type=String)
#isDate((1 eq 1))#   NO (type=Boolean) NO (type=Boolean) false (type=Boolean)
#isDate((1 eq 0))#   NO (type=Boolean) NO (type=Boolean) false (type=Boolean)
#isDate(now())# yes YES (type=coldfusion. runtime. OleDateTime) YES (type=coldfusion. runtime. OleDateTime) true (type=lucee. runtime. type. dt. DateTimeImpl)
#isDate("2000-01-01")# yes YES (type=String) YES (type=String) true (type=String)
#isDate("20-01-01")# yes YES (type=String) YES (type=String) true (type=String)
#isDate("13-13-13")#   NO (type=String) NO (type=String) false (type=String)
#isDate("13-13-12")#   NO (type=String) NO (type=String) false (type=String)
#isDate("13.13.12")#   NO (type=String) NO (type=String) false (type=String)
#isDate("13-12-13")# yes YES (type=String) YES (type=String) true (type=String)
#isDate("13.12.13")# yes YES (type=String) YES (type=String) true (type=String)
#isDate("12-13-13")# yes YES (type=String) YES (type=String) true (type=String)
#isDate("12.13.13")# yes YES (type=String) YES (type=String) true (type=String)
#isDate("2000")#   NO (type=String) NO (type=String) false (type=String)
del.icio.us Digg StumbleUpon Facebook Technorati Fav reddit Google Bookmarks
| Viewed 6249 times
  1. Paolo

    #1 by Paolo - June 30, 2020 at 5:45 PM

    it looks like the issue I reported on the isBoolean https://tracker.adobe.com/#/view/CF-4198218

    instead for the isDate it seems that there is still something with CF2018..

    isBoolean only works from CF2018
  2. Mike

    #2 by Mike - August 5, 2020 at 12:17 AM

    When using isDate(), I strip out commas, periods and dashes by doing nested replace(). That seems to overcome the horror of this inadequate function.
(will not be published)
Leave this field empty

denominative-lowborn