isDate("2.3") returns true

There are a number of tickets related to this, but LDEV-2955 most describes the problem that I have. I basically have a serializer that replaces datetime objects with ISO datetime strings in GMT timezone. Every date that I want to replace with ISO format is a datetime object, not a string.

I’m forced to use isInstanceOf(input, “java.util.Date”) instead of isDate(input). I wonder if isDate should take an optional parameter to accomplish the same thing. Or, if nothing else, maybe add a note in the documentation of isDate() that explains that it is quite liberal and that you might want to use isInstanceOf in some cases.

isDate(“2.3”) returns true

Generally speaking, the isDate() BIF in CF asks the question, “Can this input be co-erced into a date?” which is true of all the 'is" functions-- isNumeric(), isBoolean(), isSimpleValue(), isStruct(), isArray() and matches the loose typing of CF. None of those BIFs are strictly an instance check. CFML has a separate BIF for that as you pointed out (isInstanceOf())

I’m not sure what you mean when you say you are “forced”. You are wanting to explicitly perform an instance type check, so you are going to need to use the CFML BIF which is documented to do that. That’s not being “forced” so much as just using the language as designed. :slight_smile:

I’m all for shortcuts, but I’m not quite sold on the value given that CFML already has a BIF meant for the purpose of doing type checks.

The description on IsDate() :: Lucee Documentation is as follows:

Determines whether a string or Java object can be converted to a date/time value.

And that seems pretty clear about what the BIF does. It wouldn’t hurt to add a note saying "If you want to check if a variable contains an actual Java Date object, use isInstance(), but I doubt most people coming to the isDate() docs have that use case in mind, even though I understand it was your use case.

1 Like

I understand your point that isNumeric and isBoolean is pretty loose, but I would argue that isSimpleValue, isStruct, and isArray are much closer to isInstanceOf.

The problem that I see is a date is a very specific type that has specific methods that you can use on it, such as format(). In the same way that I would check if a variable is a struct with isStruct() or is an array with isArray(), I expected isDate() to work. And it does work this way in Adobe ColdFusion (even if that is more of a bug with ACF than a feature).

I just feel like there should be a high-level function to determine if a variable is a date object. I have the ability to figure out that a lucee date object represents a java.util.Date, but that is not entirely obvious and very confusing to young developer. The isInstanceOf function works for what I need and my code has already been updated, however I feel like there is room for improvement so that other developers don’t run into trouble.

1 Like

I think that may be the core of your misunderstanding. In CFML dates are not objects, they are just strings. Now, the fact that some Lucee BIFs may internally represent a date as an object is more of an implementation detail. All date BIFs will accept just any old string which can be coerced into a date. Dates also never had any sort of methods on them outside of java String methods until fairly recently but I’m unclear how much Lucee actually supports here. For example, this is valid in Adobe CF, but not Lucee

'1/2/2023'.dayOfweek()

which is calling a date-specific member function, but you can see there’s no “date object”, it’s just a string which can be converted into a date.

To be clear, isDate() does work exactly in the manner it’s documented to do so, which is:

Determines whether a string or Java object can be converted to a date/time value.

The idea of checking a variable to see if it was a specific instance of java object is an objective you came up with on your own which is not the remit of the isDate() function.

How so? Adobe’s docs live here IsDate and say this:

Determines whether a string or Java object can be converted to a date/time value.

It seems to be Adobe’s isDate() advertises to do the same thing Lucee’s does. Variations in the output of the BIF can usually be chalked up to slightly different implementations of how it parses a string or a number as a date.

I just feel like there should be a high-level function to determine if a variable is a date object.

Again, there is. It’s called isInstanceOf(). In the language of CFML, dates are nothing more than strings and any underlying Java objects which may or may not be used to represent them are largely undocumented and an implementation detail. CFML considers any variable which can be converted to a date to be a date for all practical purposes. If you have a further requirement of wanting ot know if a given variable is a specific instance of a Java class, that is over and above CFML’s definition of a date and the isInstanceOf() BIF is what you want there.

Honestly, it’s an invisible detail. That class can be seamlessly used as a string as well so the developer will normally never know nor care that their date at some point may have actually been anything other than a string. Calling underlying methods on Java classes is always something a little dangerous since CFML generally doesn’t guarantee any particular Java class will be used and and may break in the future. What you’re doing is likely not an issue, but it’s an advanced use case. I’m actually curious what feature of the underlying Java class you’re needing to call that you can’t already do with CFML’s built in functions.

1 Like

I by-and-large agree 100% with Brad, bar one point. In CFML a date def is type of object, and this is important:

writeDump([
    now().getClass().getName(),
    createDate(2011,3,24).getClass().getName(),
    createDateTime(2011,3,24,9,2,0).getClass().getName(),
    lsParseDateTime("2011-03-24").getClass().getName()
])

(all lucee.runtime.type.dt.DateTimeImpl on Lucee; coldfusion.runtime.OleDateTime on CF).

The reason why strings work in some situations is simply down to CFML’s loose & dynamic typefulness. If you give a function that’s expecting a date something that can be converted to a date: it’ll be converted to a date. This works for some strings depending on their format, eg:

function f(date d) {
    return dateAdd("d", 1, d)
}

writeDump(f("3/5/2023").format("mmmm d")) // March 6

Obviously because I’m not a lunatic, the answer there ought to be May 4, but CFML can only guess at ambiguously-formatted strings. And thanks to our USAn friends, a/b/cccc is an ambiguous string format, for values of b 1-12. Thanks guys.

So anyway, that’s one example of why it’s always best to use actual dates when working with dates.

Equally, when a function expects a string or a number, then CFML will do its best to treat a date object appropriately:

d = now()
f = d + 1
writeOutput(f) // 44997.45065489583

writeOutput(now()) // {ts '2023-03-11 10:49:33'}

And this also explains what Brad said about not being able to call date methods on strings-that-look-like-dates. If dates actually were simply specially-formatted strings in CFML, then one would legitimately expect "2011-03-24".add("d",1) to work, given "2011-03-24" would qualify as one of those “specially-formatted-strings-that-are-dates-in-CFML”. But they’re not, so it doesn’t.


Now. According to CFML… isDate("2.3"). Is it?

This is a another good example of why one should not use strings or numbers when dates are expected. Check this.

writeDump([
    isDate("2.3")
    dateAdd("d", 0, "2.3") // reminder: datepart, number, date
])

Lucee:
true
{ts ‘2023-02-03 00:00:00’}

So “2.3” is interpreted as “m.d”. I’m not sure that’s a date format anyone uses, but… OK.

But… CF:
No
{ts ‘1900-01-01 07:12:00’}

What’s going on here?

Like me, CF goes “no, ‘2.3’ is not a date, you weirdo”.

But then it seems to contradict itself and goes (I’m guessing) “OK well "2.3" isn’t a date, but it’s a string representation of 2.3, and I can interpret that as 2.3 days after my zero date (which is 1899-12-30. Don’t ask)”.

This isn’t a contradiction, although it’s perhaps not entirely expected, either.

And demonstrates… do not use strings or numbers when you are supposed to be dealing with dates.

Also: those are two bugs in Lucee there.


Bottom line: all CFML devs (and Lucee devs, it seems :wink: ) need to be really comfortable with how CFML’s loose/dynamic typing works. It’s fundamental to our jobs.

2 Likes

I guess that’s the main issue. That Lucee is using Java as the underlying language is an implementation detail and shouldn’t be required to do a type check.

While I can understand that the is*() functions don’t do type checking, there should still be a way to do that without having to know the Java class behind them.

To do that, I see three possibilities

  1. Add Lucee internal keywords for all the variable types to isInstanceOf(), so one could write isInstanceOf(input, "date").
  2. Add a new parameter to the is*() functions that does type comparison. E.g. isDate(input, "strict").
  3. Create a new function for type comparison.

Sebastian

I’m not sure what you mean by “that” when you say “a way to do that”. I assume, given the context of the conversation, you mean “detect if a variable is an instance of a specific Java class”. So, if we fully realize your sentence, what you’ve seemed to say is

there should still be a way to [detect if a variable is an instance of a specific Java class] without having to know the Java class behind them.

But now the requirement is sort of self-defeating because you’ve ruled out the very thing you’re trying to achieve! It’s also not even that simple. When I say, “I want to know if this date is represnted by a Java object OTHER than java.lang.String, or java.lang.Double, (or any of the other “numeric” types)”, there could be several things you mean:

  • lucee.runtime.type.dt.DateTimeImpl which extends…
  • lucee.runtime.type.dt.DateTime which extends…
  • java.util.Date (which can change based on your JDK version)

Each of the first two bullets contain methods that may not exist in the lower bullets and if you have poked around in the Lucee or JDK source/javadocs and wish to call one of those underlying methods, you need to be rather specific in your checks, which go above and beyond CFML’s surface level definition of a date. For example, Lucee would call any of those class instances above a “date”, but only one of them has an addOffset() method.

The long and short of this is, if you are wanting to know if a variable contains a specific instance of a Java class, then there’s not one simple answer (for dates, or numbers, or arrays, or structs) and there are actually more than one Java class which may fit the definition of a date. Furthermore, if you need this information because you wish to call a specific method on that Java class, then you need to be extremely specific in your check! And to do that, you use isInstanceOf() and you provide the exact class name you need to check for. And if you just want to know if the variable’s class extends Java’s java.util.Date class in some fashion, then the correct answer is to check to see if it’s an instance of java.util.Date (which is what the OP is already doing).

To gloss over the differences between the different Java “date” classes and implement a generic sort of strict check in CFML would be inherently ambiguous and catch people off guard in different ways. For example, if CFML added a “strict” check that specifically checked for DateTimeImpl instance, would return false for values that were a java.util.Date, but to have a “strict” check which returned true for any class extending java.util.Date would return true in cases where the method .castToString() from the DateTimeImpl class didn’t exist! You’d invariably confuse half of the people trying to use it with different assumptions. This is why I say-- when it comes to the underlying Java-- if you want to know if a variable contains an instance of a specific Java class, then you check for that class explicitly. Then it always works :slight_smile:

With “that” I meant strict type comparisons. And the point behind that is to ensure someone can call all the Lucee internal member functions related to a specific type.

Simple example:

date = now();
isDate(date); // true
date.day(); // fine

but

date = '2023-19-03 00:00';
isDate(date) // true
date.day(); // throws

The latter obviously is a string and not a date object. So you either have to avoid member functions altogether and always use normal functions (i.e. day(date)` in the example) or you have to check for the Java class, which is an implementation detail and may break in the future as you pointed out earlier.

To a Lucee developer it shouldn’t matter what Java class is behind a specific variable but they need to know which Lucee internal type (string, struct, array, query, date, etc.) a specific variable has to be able to use Lucee internal member functions on it.

Sebastian

Ahh, yes well— CFML member functions are another matter entirely, and one unrelated to the original post on this thread. You are 100% correct that variables which can be treated as type XXX should support member functions of type XXX. The same would be true of this code, which can’t be expected to reliably work in Lucee either (errors for numbers or booleans)

if( simpleValue( myVar ) ) {
  myVar.len()
}

Not everyone agrees (Adam being one, and he’ll likely chime in on it :slight_smile: ) but it’s a major PITA that CFML member functions can’t be relied on to work with variables which CFML claims can be used as a certain type. And furthermore, there is no CFML BIF (outside of isInstanceOf() ) which can reliably tell you if a given variable will support any given member function.

But to be clear, this is not a deficiency of CFML’s type checking IMO. This is a fault of the member function implementation. I’ve spent many hours of my life arguing with Adobe’s and Lucee’s engineers alike on this issue. Adobe largely mostly somewhat fixed their tickets as code like this works fine in ACF 2021, but still errors on Lucee 5 (claimed to be fixed in Lucee 6)

num = 5;
bool = false;

writeDump( isSimpleValue( num ) ) // true
writeDump( isSimpleValue( bool ) ) // true

writeDump( num.len() ) // 1
writeDump( num.trim() ) // 5

writeDump( bool.len() ) // 5
writeDump( bool.trim() ) // false

But even Adobe 2021 fails with examples like this

date = now();

writeDump( isSimpleValue( date ) ) // true

writeDump( date.len() ) // throws exception
writeDump( date.trim() ) // throws exception

But anyway, all of this mess goes back to the headless BIFs auto-casting as necessary, but the member functions only being implemented on specific underlying Java classes. It’s annoying and it’s spotty, but I think the issue is with the member function implementation, not the isXXX() BIFs. So, instead of adding a new BIF to CFML that tells me if a given variable can be used with XXX member functions, I’d rather all variables that pass the isXXX() check just automatically support myVar.XXXMemberFunction().

Lets rewrite ColdFusion completely in python with standard PHP style notion and basic like line arguments… Who is with me?

All joking aside, cftag format works as expected in AFC as well as Lucee. Some where along the way of being efficient, cool, whatever the case maybe someone forgot that ColdFusion was designed to be like HTML in design. Want your code to run for 10 years and not miss a beat, write it in tag format.

I am sure nearly every long term developer will be repulsed by the idea, keep your long overly played comments to yourselves.

<cfset date = Now()>

<cfoutput>
    <cfdump var="#isSimpleValue(date)#"> <!--- true --->

    <cfdump var="#len(date)#"> <!--- script version throws exception --->
    <cfdump var="#trim(date)#"> <!--- script version throws exception --->
</cfoutput>

That actually has nothing to do with tags. It works because you’re using the headless functions instead of the member functions, which force casting on the inputs and would do the same in script.

date = Now()

dump( isSimpleValue(date) ) // true

dump( len(date) ) // member version throws exception 
dump( trim(date) ) // member version throws exception

That is because the tag equivalent to what you have written is:

<cfset date = Now()>
<cfoutput>
    <cfdump var="#isSimpleValue(date)#"> <!--- true --->
    <cfdump var="#date.len()#"> <!--- This is invalid you can not use LEN against DATETIME DATA--->
    <cfdump var="#date.trim()#"> <!--- TRIM LIKE LEN IS A STRING FUNCTION--->
</cfoutput>

Len (Length) is for strings not for data time data.
You would first need to make your isSimpleValue(date) a string that is what you should be comparing.

Now if I rewrite your code to treat your date time object as a String Object I get it work

<cfset date = Now()>

<cfoutput>
    <cfdump var="#isSimpleValue(date)#"> <!--- true --->

    <cfdump var="#len(DateFormat(date))#"> <!--- display length of date value --->
    <cfdump var="#trim(date)#"> <!--- I know what to look for now, as above so I work --->
</cfoutput>

You’re still confusing two different concepts here. Headless BIFs vs members functions are orthogonal to tags and script. There is no “tag equivalent” to a CFML expression.

len( myVar ) // headless BIF
myVar.len() // member function

Whether you put the above in a script block or in a <cfset > tag is completely irrelevant to how they function.

Yes, and that is the point of this thread. A date IS A simple value (which is basically “stringy” in CFML) as evidenced by the fact that isSimpleValue() returns true. The facts that some strings have semantic meaning which can be parsed to a date and or that some dates in CFML are stored in a Java class other than java.lang.String does not affect the fact they are “simple values” (i.e. can be used as stringy) and there is therefore a reasonable expectation that string member functions would work on them.

This is nonsensical. If CFML has determined a given variable is already a simple value, nothing should need done to it to operate on it as a simple value.

Yes, of course that works. The point being discussed here is how Lucee should work, not what workarounds exist to trick its currently implementation into not failing.

I guess I see this differently than you.

I see this problem as Java Developer tries to overload a class like its JAVA, when its NOT JAVA, its a subscript dialect of Java that was never designed implicitly to allow overloading of functions.

We can both agree to disagree and then drink beer and yell at each other at the nuances of Java vs sub scripting languages like ColdFusion, but the general poke at everyone above is that, you are a kick ass java developer but the confusion of Language Java vs subscript language that ColdFusion is.

This is no matter which way you choose to arrive at the value, comes down to the basic code of what Coldfusion is doing.

import java.util.Date;

public class DateUtils {
    public static boolean isSimpleValue(Object value) {
        return value == null || value instanceof String || value instanceof Number || value instanceof Boolean || value instanceof Date;
    }

    public static int getDateLength(Date date) {
        return date.toString().length();
    }
}

According to CfDocs, and AFC the Date Function does not natively understand how to arrive at a string value as its a binary value.

I understand you want to OVERLOAD the function, like many other languages, but coldfusion being a left to right top down language reads it as
date (Binary value) what is your STRING length
That is what your code in essence is doing
now rewrite it with date as string, get string length and it works.

Again, I know many on here including you are great java developers, but it is not an ERROR of a language that it doesn’t sit around trying to figure out the illogical parsing of what you are calling as you have language bias.

I am positive someone can hack the core engine to make it wait until all objects are thrown into memory and then do what you want, but at its core, if it didn’t work in TAG (yes, TAG) you missed the point of using ColdFusion to do it.

Link: Data type conversion

CONVERTING BINARY DATA
ColdFusion cannot automatically convert binary data to other data types.