This is a really good question!
TJ Crowder's answer has a great solution, but it made me think: what else can we do? How can we get around Date.prototype.setTime.call(yourFrozenDate) ?
First try: "Wrapper"
One direct way is to provide an AndrewDate function that completes the date. It has everything that a date has minus setters:
function AndrewDate(realDate) { var proto = Date.prototype; var propNames = Object.getOwnPropertyNames(proto) .filter(propName => !propName.startsWith('set')); return propNames.reduce((ret, propName) => { ret[propName] = proto[propName].bind(realDate); return ret; }, {}); } var date = AndrewDate(new Date()); date.setMonth(2);
What this means is to create an object that has all the properties that the actual date object has and uses Function.prototype.bind to set them to this .
This is not a stupid way to collect keys, but hopefully you can see my intent.
But wait ... looking at it a little further and here, we can see that there is a better way to do this.
Second try: Proxy
function SuperAndrewDate(realDate) { return new Proxy(realDate, { get(target, prop) { if (!prop.startsWith('set')) { return Reflect.get(target, prop); } } }); } var proxyDate = SuperAndrewDate(new Date());
And we decided it!
... view. See, Firefox is now the only one that implements proxies, and for some bizarre reasons, date objects cannot be approximated. In addition, you will notice that you can still do things like 'setDate' in proxyDate , and you will see improvements on the console. To overcome this, more traps must be provided; in particular, has , enumerate , ownKeys , getOwnPropertyDescriptor and who knows what strange edge cases there are!
... So, after thinking, this answer is almost meaningless. But at least we had fun, right?