Thursday, 19 July 2012

According to ColdFusion "0,6" == "6,0". And both are integers to boot

G'day
If you participate on StackOverflow or the Adobe ColdFusion Forums, you might be aware of this already.  But I thought I'd write it up again, and also solicit people's opinions on the topic.

Consider this code:

<cfset list1 = "0,6"> 
<cfset list2 = "6,0">
<cfoutput>

<cfif list1 eq list2>
    #list1# equals #list2#<br />
<cfelse>
    #list1# does not equal #list2#<br />
</cfif>

What would you expect this to output?  Would you be surprised to here it outputs this:

0,6 equals 6,0

I was, initially.  My reaction was something along the lines of "WTF?"



Normally - having become fairly used to ColdFusion's "idiosyncracies", I'm able to work out what is going on in these situations, but this time I drew a blank.  Fortunately I had the ear of one of the ColdFusion Team at that moment, so I was able to ask them what the story was, and they explained the story to me.

You see: both "0,6" and "6,0" - for the purposes of comparison via the EQ operator - are considered to be... wait for it... dates.

(and what date?  Well the 0th of June, obviously.  Or you might be more familiar with it as "the 31st of May".  I kid you not).

My reaction remained "WTF?", to be honest.  With a small amount of recoiling with horror.  I don't think this is an unreasonable reaction to that explanation.

I continued my line of enquiry to try to get an explanation as to why it is they thought "0,6" (or, hey, "6,0") could legitimately be considered a date.  I observed that I was unaware of any culture or locale on the planet in which "d,m" or "m,d" is considered a date, so I was puzzled why ColdFusion would think it is.  I did not get an explanation for this.

I suggested it was - therefore - a bug, and I raised it as such: 3134331.

I've just knocked together some test code which demonstrates the issue more egregiously:

<cfset list1 = "0,6"> 
<cfset list2 = "6,0">

<cfoutput>
    <cfif list1 eq list2>
        #list1# equals #list2#<br />
    <cfelse>
        #list1# does not equal #list2#<br />
    </cfif>
    <hr />

    isDate():<br />
    list1: #isDate(list1)#<br />
    list2: #isDate(list2)#<br />
    <hr />

    isValid("date"):<br />
    list1: #isValid("date", list1)#<br />
    list2: #isValid("date", list2)#<br />
    <hr />

    isValid("eurodate"):<br />
    list1: #isValid("eurodate", list1)#<br />
    list2: #isValid("eurodate", list2)#<br />
    <hr />

    parseDateTime():<br />
    list1: #parseDateTime(list1)#<br />
    list2: #parseDateTime(list2)#<br />
    <hr />

    parseDateTime():<br />
    <cftry>
    list1: #lsParseDateTime(list1)#<br />
    <cfcatch>
        #cfcatch.message#<br />
    </cfcatch>
    </cftry>
    <cftry>
    list2: #lsParseDateTime(list2)#<br />
    <cfcatch>
        #cfcatch.message#<br />
    </cfcatch>
    </cftry>
    <hr />

    isValid("integer"):<br />
    list1: #isValid("integer", list1)#<br />
    list2: #isValid("integer", list2)#<br />
    <hr />

    isValid("integer", 6.0): #isValid("integer", 6.0)#<br />
    <hr />
</cfoutput>


Note the last coupla tests are for integers, not dates. I'll get to that next.

This outputs:
 0,6 equals 6,0

isDate():
list1: YES
list2: YES

isValid("date"):
list1: YES
list2: YES

isValid("eurodate"):
list1: NO
list2: NO

parseDateTime():
list1: {ts '2012-05-31 00:00:00'}
list2: {ts '2012-05-31 00:00:00'}

parseDateTime():
list1: 0,6 is an invalid date or time string.
list2: 6,0 is an invalid date or time string.

isValid("integer"):
list1: YES
list2: YES

isValid("integer", 6.0): NO


OK, this narrows the field down to "d,m" and "m,d" being a US date format, apparently: neither the - so-called - "eurodate" validation test, not a parsing in my current locale ("English (UK)") consider those strings valid as dates.  I extended the test to see if any locales consider those dates valid as strings:

<cfset myQuoteDateUnquote = "0,6"> 
<cfoutput>
    
    <cfloop index="locale" list="#server.coldfusion.supportedLocales#">
        <cftry>
            #locale#:
            <cfset anActualDate = lsParseDateTime(myQuoteDateUnquote, locale)>
            <span style="color:green">#anActualDate#</span>
            <cfcatch>
                <span style="color:red">#cfcatch.message#</span>
            </cfcatch>
        </cftry>
        <br />
    </cfloop>
</cfoutput>

I'll spare you the full output because it's rather predictable, but needless to say, not a single locale considers "d,m" as a date format (I altered the code to test "m,d", and that had the same results).  Here's an extract of the list:

[...]
English (US): 0,6 is an invalid date or time string. 
[...]
en: 0,6 is an invalid date or time string. 
[...]
en_US: 0,6 is an invalid date or time string. 
[...]

So that's interesting.  If even the US locale doesn't accept that pattern, why is it that isValid() accepts it?

OK, so that's dates... the heading of this article says "integers".

Well this goes back to a related bug, summarised as follow:

Problem Description: If you run isValid("integer") it allows commas and currency symbols to pass. This causes errors if for instance, you try to pass the value to a database. 

This is also reflected in the latter of my two test cases above:

Where this:

isValid("integer"):<br />
list1: #isValid("integer", list1)#<br />
list2: #isValid("integer", list2)#<br />


Outputs this:

isValid("integer"):
list1: YES
list2: YES

But this:
isValid("integer", 6.0): #isValid("integer", 6.0)#<br />


Outputs this:

isValid("integer", 6.0): NO

To me, the latter has more of a case for being considered an integer than the former.

What really gets my goat here is Adobe's reaction to the bug:

This has always been the behavior and changing this would result in backward compatibility issue. It will not be fixed.

To which my posted reaction was this:
Rupesh, that's a bit facile I'm afraid. No-one is going to be using a function that validates for an INTEGER to validate for something that is not an integer. No-one will have code doing that. Using your "logic" you would never fix *any* bugs because once they got into the wild there is a theoretical (but completely unrealistic) chance someone might be using it as a square peg to fill a round hole.

The function does not do what it says on the tin. It's bugged. You ought to fix it. Please re-open the ticket so it can be triaged for the next release.

I am really underwhelmed by Adobe here.

I'd like to get a sense of what the community thinks about all this, so am going to try to work out how to create a wee poll... [time passes]... cool, yeah that was easy.  Please do my survey!  Some of the questions allow for an "other" option where I might not have thought of the best options, but if you have any general comments, pls post 'em here.

Cheers.

--
Adam