How to extend the Javascript Date object?

19,154

Solution 1

Looking at the v8 code, in date.js:

function DateGetHours() {
  var t = DATE_VALUE(this);
  if (NUMBER_IS_NAN(t)) return t;
  return HOUR_FROM_TIME(LocalTimeNoCheck(t));
}

And looks like DATE_VALUE is a macro that does this:

DATE_VALUE(arg) = (%_ClassOf(arg) === 'Date' ? %_ValueOf(arg) : ThrowDateTypeError());

So, seems like v8 won't let you subclass Date.

Solution 2

This can be done in ES5. It requires modifying the prototype chain directly. This is done using __proto__ or Object.setPrototypeOf(). I'm using __proto__ in the sample code since that's most widely supported (although the standard is Object.setPrototypeOf).

function XDate(a, b, c, d, e, f, g) {
  var x;
  switch (arguments.length) {
    case 0:
      x = new Date();
      break;
    case 1:
      x = new Date(a);
      break;
    case 2:
      x = new Date(a, b);
      break;
    case 3:
      x = new Date(a, b, c);
      break;
    case 4:
      x = new Date(a, b, c, d);
      break;
    case 5:
      x = new Date(a, b, c, d, e);
      break;
    case 6:
      x = new Date(a, b, c, d, e, f);
      break;
    default:
      x = new Date(a, b, c, d, e, f, g);
  }
  x.__proto__ = XDate.prototype;
  return x;
}

XDate.prototype.__proto__ = Date.prototype;

XDate.prototype.foo = function() {
  return 'bar';
};

The trick is that we actually instantiate a Date object (with the correct number of arguments) which gives us an object with it's internal [[Class]] set correctly. Then we modify it's prototype chain to make it an instance of XDate.

So, we can verify all this by doing:

var date = new XDate(2015, 5, 18)
console.log(date instanceof Date) //true
console.log(date instanceof XDate) //true
console.log(Object.prototype.toString.call(date)) //[object Date]
console.log(date.foo()) //bar
console.log('' + date) //Thu Jun 18 2015 00:00:00 GMT-0700 (PDT)

This is the only way I know of to subclass date because the Date() constructor does some magic to set the internal [[Class]] and most date methods require that to be set. This will work in Node, IE 9+ and almost all other JS engines.

Similar approach can be used for subclassing Array.

Solution 3

Check out the MDC docs on Date specifically:

Note: Note that Date objects can only be instantiated by calling Date or using it as a constructor; unlike other JavaScript object types, Date objects have no literal syntax.

It seems like the Date object isn't really a JS object at all. When I was writing an extension library, I ended up doing the following:

function MyDate() {
   var _d=new Date();
   function init(that) {
      var i;
      var which=['getDate','getDay','getFullYear','getHours',/*...*/,'toString'];
      for (i=0;i<which.length;i++) {
         that[which[i]]=_d[which[i]]; 
      }
   }
   init(this);
   this.doSomething=function() {
    console.log("DO");
   }
}

At least I did that first. The limitations of the JS Date object in the end got the better of me and I switched to my own data storage approach (eg. why does getDate=day of year?)

Solution 4

In ES6, it will be possible to subclass built-in constructors (Array, Date, and Error) - reference

Problem is there is no way to do this with current ES5 engines, as Babel indicates and will require a browser with native ES6 support.

The current ES6 browser support for subclassing is pretty weak / non-existant as of today (2015-04-15).

Solution 5

Section 15.9.5 of the EcmaScript spec says:

In following descriptions of functions that are properties of the Date prototype object, the phrase 'this Date object' refers to the object that is the this value for the invocation of the function. Unless explicitly noted otherwise, none of these functions are generic; a TypeError exception is thrown if the this value is not an object for which the value of the [[Class]] internal property is "Date". Also, the phrase 'this time value' refers to the Number value for the time represented by this Date object, that is, the value of the [[PrimitiveValue]] internal property of this Date object.

Note specifically the bit that says "none of these functions are generic" which, unlike for String or Array, means that the methods cannot be applied to non-Dates.

Whether something is a Date depends on whether its [[Class]] is "Date". For your subclass the [[Class]] is "Object".

Share:
19,154
evilcelery
Author by

evilcelery

Updated on June 14, 2022

Comments

  • evilcelery
    evilcelery about 2 years

    I'm trying to subclass/extend the native Date object, without modifying the native object itself.

    I've tried this:

        var util = require('util');
    
        function MyDate() {
            Date.call(this);
        }
        util.inherits(MyDate, Date);
    
        MyDate.prototype.doSomething = function() {
            console.log('Doing something...');
        };        
    
        var date = new MyDate();
        date.doSomething();
    
        console.log(date);
        console.log(date.getHours());
    

    and this:

    function MyDate() {
    
        }
    
        MyDate.prototype = new Date();
    
        MyDate.prototype.doSomething = function() {
            console.log("DO");
        }
    
        var date = new MyDate();
        date.doSomething();
        console.log(date);
    

    In both cases, the date.doSomething() works, but when I call any of the native methods such as date.getHours() or even console.log(date), I get 'TypeError: this is not a Date object.'

    Any ideas? Or am I stuck to extending the top-level Date object?