4

This answer says,

The Date object will do what you want - construct one for each date, then compare them using the >, <, <= or >=.

I don't see this functionality mentioned on the referenced page.

  • Is this browser-specific behaviour, or a standard feature of the language?
  • If Date is a javascript "object", and javascript doesn't support operator overloading, then how is this behaviour of the >, <, <= or >= operator on Date objects implemented? I mean, is it defined on Date.prototype, does it happen because some implicit conversion is defined (e.g. from an object to a number or a string), is it a special-case for Date objects added to the Javascript language interpreter/run-time, or what?
Community
  • 1
  • 1
ChrisW
  • 54,973
  • 13
  • 116
  • 224

2 Answers2

5

Whenever you use an object where you'd usually expect a number (addition, subtraction, greater/lower comparison) the valueOf method is called to turn the object into a primitive:

{ valueOf: () => 2 }.valueOf() // 2
//or not explicitly called:
+ { valueOf: () => 2 } // 2
//using the compare operator:
{ valueOf: () => 2 } < { valueOf: ()=>3 } //true as 2 < 3

So when you compare Dates this happens too. Since the Date.prototype.valueOf method returns the Milliseconds since 1970, you can use it to compare two dates...

new Date().valueOf();
//or
+new Date(); 
//or
new Date() < new Date();
Jonas Wilms
  • 132,000
  • 20
  • 149
  • 151
  • Thank you. Also I found more details in the description of [The Abstract Relational Comparison Algorithm](http://www.ecma-international.org/ecma-262/5.1/#sec-11.8.5). – ChrisW May 08 '17 at 10:31
2

Is this browser-specific behaviour, or a standard feature of the language?

It's a standard feature.

If Date is a javascript "object", and javascript doesn't support operator overloading, then how is this behaviour of the >, <, <= or >= operator on Date objects implemented?

Because of the way Date implements valueOf and Symbol.toPrimitive (toString is related, but not for the specific operators you mentioned). Those three methods are used in the process of implicitly converting an object to a primitive value by the OrdinaryToPrimitive abstract operation in the specification, which is used by operators when they need a primitive value (like a string or number) but they receive an object.

In particular, Date implements valueOf by returning its underlying time value (milliseconds since Jan 1st 1970 at midnight GMT). So date1 >= date2 invokes valueOf on each of the dates, hinting that a number is preferred, which gets the time value (a number) from the dates, and then compares them. More below.

A duplicate of this question asked:

Can we do it for any class?

Yes, as of ES2015 this is all standard and Date is no longer special like it used to be (the way it was special was relatively minor, more below). For example:

class MyThing {
    constructor(value) {
        this.value = value;
    }
    
    valueOf() {
        return this.value;
    }

    toString() {
        return String(this.value);
    }
}

const a = new MyThing(27);
const b = new MyThing(42);
console.log(`a < b? ${a < b}`);   // a < b? true
console.log(`b < a? ${b < a}`);   // b < a? false
console.log(`b - a = ${b - a}`);  // b - a = 15
console.log(`a + b = ${a + b}`);  // a + b = 69

(The above leaves out something Date has, more in a moment.)

When an operator or similar needs to convert an object to a primitive, it uses the object's "to primitive" operation, optionally providing a "hint" about what it would prefer (a string, a number, or no preference). The relational operators prefer numbers, as do all the pure math operators like -. The + operator, being both addition and string concatenation, doesn't provide a hint so the object's default is used. Almost built-in objects default to number if there's no hint; Date and Symbol default to string instead. That used to just be built into the logic of the specification's operation (for Date; Symbol didn't exist then), but it's now handled via the Symbol.toPrimitive method instead, which an object can override to provide its own handling of being converted to a primitive.

If we wanted MyThing to default to string instead of number like Date does, we'd add the Symbol.toPrimitive method (I've also added console.logs to valueOf and toString to show which is used for the operations):

class MyThing {
    constructor(value) {
        this.value = value;
    }
    
    valueOf() {
        console.log("valueOf");
        return this.value;
    }

    toString() {
        console.log("toString");
        return String(this.value);
    }

    [Symbol.toPrimitive](hint) {
        if (hint === "number") {
            return this.valueOf();
        }
        // "default" or "number"
        return this.toString();
    }
}

const a = new MyThing(27);
const b = new MyThing(42);
console.log(`a < b? ${a < b}`);   // a < b? true
console.log(`b < a? ${b < a}`);   // b < a? false
console.log(`b - a = ${b - a}`);  // b - a = 15
console.log(`a + b = ${a + b}`);  // a + a = "2742"

Notice how a + b is doing string concatenation now, where in the first snippet it was doing addition. That's because we changed the default behavior to prefer strings over numbers.

T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875