Why can I do math with Date objects? [duplicate] - javascript

This question already has answers here:
Integer addition/subtraction issues with the Date object
(2 answers)
Closed 4 years ago.
When I subtract two Date-objects like this:
const startTime = new Date();
await someAsyncStuff();
const endTime = new Date();
const elapsedTime = endTime - startTime;
console.log(`The async stuff took ${elapsedTime} ms`);
Why does the Date objects end up being cast to milliseconds, which are then subtracted? I understand that they do, but I can't figure out what the actual sequence of events is that lead to this.

In general, JavaScript objects can define methods to convert the object to a String or a number (which you can customize by defining toString and valueOf methods). JavaScript will use those methods in numerical contexts (like 2 * a) or string contexts (like '' + a) to convert the object to the appropriate primitive.
In a context where it's ambiguous whether to use numerical or string conversion (like a + b), there's a default behavior, depending on the type of the object. Interestingly, Date are singled out among the default ECMAScript objects to convert to a String, instead of a number. Via the spec:
Date objects, are unique among built-in ECMAScript object in that they
treat "default" as being equivalent to "string", All other built-in
ECMAScript objects treat "default" as being equivalent to "number".
In the particular case of Date objects, the numerical conversion (the valueOf method) converts the time to epoch milliseconds, while the string conversion (the toString method) converts the object to a human-readable string. As #baao mentions in his answer, this can cause some issues when doing "arithmetic" with objects, due to automatic conversions of type.
In summary, Date (unlike most other objects) defaults to string conversion, but since subtraction requires two numbers for it to make sense, it converts the dates to numbers.
It's generally a good idea to explicitly define the behavior, in this case using valueOf, getTime or toString to make the code less ambiguous.
For more information of whether JavaScript chooses to use toString vs valueOf, see this question, the overall spec for addition (and subtraction), and the specific spec for Dates (mdn link), and #baao's answer for a more in depth look.
See Also:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/valueOf
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/valueOf

It's how javascript does automatic type conversion - just like arithmetic operations (that you are performing here). You were lucky that you subtracted, if you've added them you'd end up with a string holding two date strings in a row because of how toPrimitive (that gets called implicitly) works for Dates. Consider the following
// automatic casting
console.log("1" - "1"); // 0
// but
console.log("1" + "1"); // 11
// now with dates
// automatic casting
console.log(new Date() - new Date()); 0
console.log(new Date() + new Date()); // Mon Jun 11 2018 10:10:36 GMT+0200 (Mitteleuropäische Sommerzeit)Mon Jun 11 2018 10:10:36 GMT+0200 (Mitteleuropäische Sommerzeit)
The specification on the additional operator has the following hint that explains this further
All native ECMAScript objects except Date objects handle the absence of a hint as if the hint Number were given; Date objects handle the absence of a hint as if the hint String were given.
JavaScript converts your value to a primitive when using arithmetic opererators, the method that gets called here is
Date.prototype [ ##toPrimitive ] ( hint )
Date.prototype [ ##toPrimitive ] ( hint )
This function is called by ECMAScript language operators to convert a Date object to a primitive value. The allowed values for hint are "default", "number", and "string". Date objects, are unique among built-in ECMAScript object in that they treat "default" as being equivalent to "string", All other built-in ECMAScript objects treat "default" as being equivalent to "number".
That said. The reason why your code works how it works is the later auto conversion performed by the subtraction, which gives hint to toPrimitive to return a number.

It equals endTime.getTime() - startTime.getTime()
As you say, they cast to millisecond and that exactly shows the difference.

This is happening because JS asks for the primitive of the Date's object when performing such operation, through the method valueOf. The Date's object overrides valueOf methods, so that the value used is basically the same of getTime.
You can also try by yourself:
const o = { valueOf: () => 10 };
console.log(o + 1) // 11

Related

Why can i parse invalid date string? [duplicate]

This question already has answers here:
Why does Date.parse give incorrect results?
(11 answers)
Closed 3 years ago.
I use a function that check if entered value is a valid text for specific purpose in my application.
valid value is a string where it's not valid date or number neither true or false.
checkText(str) {
return isNaN(str) && isNaN(Date.parse(str)) && ['true', 'false'].indexOf(str) == -1;
}
It works properly, but i faced an issue with this string: "New Item 3".
Date.parse("New Item 3") returns a number, but why!!? also, if you changed 3 into any number less than 13 it will return number!
Anyone here can explain to me what happens?
Lesson learned: Date.parse is not a date validator.
Even MDN says:
It is not recommended to use Date.parse as until ES5, parsing of strings was entirely implementation dependent. There are still many differences in how different hosts parse date strings, therefore date strings should be manually parsed (a library can help if many different formats are to be accommodated).
And further down
The ECMAScript specification states: If the String does not conform to the standard format the function may fall back to any implementation–specific heuristics or implementation–specific parsing algorithm. Unrecognizable strings or dates containing illegal element values in ISO formatted strings shall cause Date.parse() to return NaN.
However, invalid values in date strings not recognized as simplified ISO format as defined by ECMA-262 may or may not result in NaN, depending on the browser and values provided
In fact the problem here is coming from Date.parse() method, if you check:
Date.parse("New Item 3");
It will return:
983401200000
console.log(Date.parse("New Item 3"));
So the fact here is that Date.parse() will behave according the browser specifications and may or not return a Number. It depends on the browser.
And you can see from the Date.parse() MDN reference that:
The ECMAScript specification states: If the String does not conform to the standard format the function may fall back to any implementation–specific heuristics or implementation–specific parsing algorithm. Unrecognizable strings or dates containing illegal element values in ISO formatted strings shall cause Date.parse() to return NaN.
However, invalid values in date strings not recognized as simplified
ISO format as defined by ECMA-262 may or may not result in NaN,
depending on the browser and values provided.

Equality comparison between Date and number doesn't work

According to the ECMA script standard, the following code should return true, but it doesn't:
d = new Date() ;
d.setTime(1436497200000) ;
alert( d == 1436497200000 ) ;
Section 11.9.3 says:
If Type(x) is either String or Number and Type(y) is Object, return the result of the comparison x == ToPrimitive(y).
Then, section 8.12.8 says that ToPrimitive retuns the result of the valueOf method. Which means that the last line in my example above should be equivalent to:
alert( d.valueOf() == 1436497200000 );
Which does return true, indeed.
Why does the first case not return true?
If you look at the spec at section 8.12.8, you will find this text near the end of the section:
When the [[DefaultValue]] internal method of O is called with no hint, then it behaves as if the hint were Number, unless O is a Date object (see 15.9.6), in which case it behaves as if the hint were String.
(Emphasis mine)
Now, in step 8 / 9 of the The Abstract Equality Comparison Algorithm [11.9.3], ToPrimitive(x) and ToPrimitive(y) are called without hint parameter.
The lack of this hint parameter, together with the above text, means that the ToPrimitive method returns the toString() value, on date objects.
As you're probably aware, (new Date()).toString() returns a string representation of the date in American English [source]:
"Wed Jul 01 2015 22:08:41 GMT+0200 (W. Europe Daylight Time)"
That a string like that doesn't equal 1436497200000 shouldn't come as a big surprise. ;-)
ToPrimitive(A) attempts to convert its object argument to a primitive value, by attempting to invoke varying sequences of A.toString and A.valueOf methods on A.
So if the toString() call succeeds, it won't call valueOf().

Why does parsing a locale date string result in an invalid date?

Can someone explain why the following snippets result in an invalid date object?
new Date(new Date().toLocaleString())
// or
Date.parse(new Date().toLocaleString())
This is expressly permitted by the ES5 specification's definition of Date.parse (emphasis mine):
...all of the following expressions should produce the same numeric value in that implementation, if all the properties referenced have their initial values:
x.valueOf()
Date.parse(x.toString())
Date.parse(x.toUTCString())
Date.parse(x.toISOString())
However, the expression
Date.parse(x.toLocaleString())
is not required to produce the same Number value as the preceding three expressions and, in general, the value produced by Date.parse is implementation-dependent when given any String value that does not conform to the Date Time String Format (15.9.1.15) and that could not be produced in that implementation by the toString or toUTCString method.
Since toLocaleString is not required to produce a string conformant to the Date Time String Format YYYY-MM-DDTHH:mm:ss.sssZ, it is allowable for its output not to be parsed correctly by Date.parse.
new Date().toLocaleString() returns the current date in a format new Date() can't parse, resulting in unexpected dates.

How to deserialize JSON text into a date type using Windows 8 JSON.parse?

I'm building a Windows 8 Metro app (aka "Modern UI Style" or "Windows Store app") in HTML5/JavaScript consuming JSON Web Services and I'm bumping into the following issue: in which format should my JSON Web Services serialize dates for the Windows 8 Metro JSON.parse method to deserialize those in a date type?
I tried:
sending dates using the ISO-8601 format, (JSON.parse returns a string),
sending dates such as "/Date(1198908717056)/" as explained here (same result).
I'm starting to doubt that Windows 8's JSON.parse method supports dates as even when parsing the output of its own JSON.stringify method does not return a date type.
Example:
var d = new Date(); // => a new date
var str = JSON.stringify(d); // str is a string => "\"2012-07-10T14:44:00.000Z\""
var date2 = JSON.parse(str); // date2 is a string => "2012-07-10T14:44:00.000Z"
Here's how I got this working in a generic way (though it I'd rather find a format supported out-of-the-box by Windows 8's JSON.parse method):
On the server, I'm serializing my strings using:
date1.ToString("s");
This uses the ISO 8601 date format which is always the same, regardless of the culture used or the format provider supplied (see here for more information).
On the client-side, I specified a "reviver" callback to JSON.parse which looks for dates using a regexp and converts them into a date object automatically.
In the end, the deserialized object will contain actual JavaScript date types and not strings.
Here's a code sample:
var responseResult = JSON.parse(request.responseText, function dateReviver(key, value) {
if (typeof value === 'string') {
var re = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)$/
var result = re.exec(value);
if (result) {
return new Date(Date.UTC(+result[1], +result[2] - 1, +result[3], +result[4],+result[5], +result[6]));
}
});
Hope this helps,
Carl
This is not something that's unique to Windows 8's JSON.parse – it's the by-design behavior of the ECMA standard JSON parser. Therefore, there is (and can be) no "out-of-the-box support" for dates.
Per spec, JSON values can only be a String, Number, Boolean, Array, Object, or null. Dates are not supported. (IMO, this is an oversight on the part of the spec, but it's what we have to live with.)
Since there is no date type, your app has to work out how to handle dates on its own. The best way to handle this is to send dates as ISO 8601 strings (yyyy-MM-dd'T'HH:mm:ss'Z') or as milliseconds since the epoch (Jan 1 1970 00:00:00 UTC). The important part here is to make sure time is in UTC.
If performance is important, I would not use a reviver callback with JSON.parse. I did a lot of testing, and the overhead involved with invoking a function for every single property in your object cuts performance in half.
On the other hand, I was honestly surprised with how well testing a regex against every string value stood up against only parsing known property names. Just make sure you define the regex once, outside the loop!
Obviously, the absolute fastest ways to turn JSON values into Dates is if you know exactly what properties need to be parsed for dates. However, given the surprisingly good performance of the regex-based search methods, I don't think it's worth the extra complexity unless you really need the extra performance.
A note on using ISO strings vs milliseconds since epoch: tested independently, milliseconds wins. In IE, there's no difference, but Firefox really seems to struggle with ISO strings. Also note that the Date constructor takes milliseconds in all browsers. It also takes a the ISO string, but not in IE ≤ 8.

Strange syntax in javascript: 'sth'+ +new Date

I'm reading through a jquery plugin, and find this interesting syntax:
'sth'+ +new Date
It creates a numeric string which the author used for a unique id: sth1237004731731
I'm curious what kind of syntax it is, and if there's some reading materials about it? Thanks!
It's using some side effects of JavaScript's type coercion to build a unique identifier (probably for an element). The confusing part is the +new Date. Here, new Date (or new Date()) returns a Date object. But putting a + or a - in front of this forces the JS interpreter to coerce this value to a Number object, and the way JS does Date > Number is by returning the timestamp (or getTime()).
So this code could be expressed differently like this:
var date = new Date(), // "Mon May 14 2012 10:03:58 GMT-0400 (EST)"
timestamp = date.getTime(), // 1337004238612
identifierString = "sth" + timestamp.toString();
You might reasonably claim that there's no need to be so verbose, so I personally would probably have written this as:
var identifier = "sth" + (new Date()).getTime();
However, please avoid coding things like your example if you ever expect someone might have to maintain your code. If it stopped you in your tracks, it probably will stop a lot of people. Coding style is not merely about expressing intent to the interpreter, but expressing intent to human developers. If the code works in the browser but fails with a syntax error in most experienced developers' heads, you've done it wrong, plain and simple.
This is an interesting use of the unary + operator. Basically, you can break down this expression into three separate parts, splitting at the binary + operator:
"sth", then +, then +new Date.
The first operand of the binary + is just a generic string literal. The second operand uses the unary + operator, which, as the linked standard states, converts its operand to a Number.
Because the new operator has the highest precedence, it "binds tighter" than the unary +, which means new Date will be evaluated first. So the operand of the unary + is, in turn, the result of the expression new Date. Of course, new Date simply creates a blank Date object. As per § 15.9.3.3 new Date():
The [[PrimitiveValue]] internal property of the newly constructed
object is set to the time value (UTC) identifying the current time.
In other words, a new Date will just be a Date object representing the current time. And, in turn, +new Date will convert a blank Date object to a number.
The Short Answer
The specification is long and hard to follow. In short, +new Date returns the UNIX timestamp associated with the current time.
The Long Answer: Following the Spec
The long answer, following the specification, is that the unary + calls ToNumber(GetValue(expr)) where expr is the evaluated operand. GetValue(dateObj) will simply return dateObj, so the expression then becomes ToNumber(dateObj).
The result of ToNumber depends on what the type of the argument is. In the case of an object, it returns ToNumber(ToPrimitive(input argument, hint Number)).
ToPrimitive will, in turn, call the valueOf property of the Date object. That returns a Number, which is the time value associated with the Date object: finally, what we were looking for! Then it goes back up the chain: ToNumber(num) simply returns num.
Of course, from there, the string "sth" and the result of +new Date are concatenated (you can find that in the spec if you so wish), which gives you the result you were looking for.

Categories

Resources