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(&quot;32.8&quot;)
= YES, butisDate(&quot;31.8&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) |
#1 by Paolo - June 30, 2020 at 5:45 PM
instead for the isDate it seems that there is still something with CF2018..
isBoolean only works from CF2018
#2 by Mike - August 5, 2020 at 12:17 AM