A JavaScript Date is an adventure to deal with. Here’s a short introduction to its ridiculousness.
Here the other day, a (very clever) junior engineer asked my why I wrote +new Date()
to get the milliseconds of the current moment in time. Which got me thinking — why in the world would I ever do that? 🤔
This article is going to guide you through how to do math with dates in JavaScript, and why it’s a terrible idea.
If you want the timestamp — the number of milliseconds since January 1st 1970 — from a Date instance, you can follow the spec, and call the getTime()
method:
const aDateInstance = new Date();
const millisecondsSince1970 = aDateInstance.getTime();
If you just want to know what that number is for the current instant in time (i.e. right now), you can even use the static Date.now
method:
const milliseconds = Date.now();
Now, here comes the magic. If that feels too wordy for some reason, you can actually do this:
const milliseconds = +new Date();
Want to know another trick? You can figure out the difference between two Date instances by subtracting them!
const myBirthday = new Date('1987-07-22');
const rightNow = new Date();
const numberOfMsIHaveLived = rightNow - myBirthday;
// 974911520369 or thereabouts
And just to screw with your head — let’s add two dates and see what happens:
const myBirthday = new Date('1987-07-22');
const myPartnersBirthday = new Date('1988-06-08');
const together = myBirthday + myPartnersBirthday;
// "Wed Jul 22 1987 02:00:00 GMT+0200 (CEST)Wed Jun 08 1988 02:00:00 GMT+0200 (CEST)"
I can’t even..
So, as I mentioned to begin with, my colleague asked me what in the world I was doing when I was writing +new Date()
. It was a good question! Why can you do math with Dates? You can’t do that with any other kinds of JavaScript objects! It was just one of those hacks I had picked up through the years of trying to ship features without thinking about maintainability.
So being the senior dev I was, I dug down deep to figure out what in the world was happening. Google didn’t help. Stack Overflow gave tons of different answers. Finally (thanks to some great people at Stack Overflow — props where props are due), I kind of understood what happens. But to understand what happens, let’s look at what actually happens when you do weird stuff with object instances that you probably shouldn’t.
First, let’s start with plain ol’ objects:
const object = {};
object + object
// "[object Object][object Object]"
object - object
// NaN
object * object
// NaN
object / object
// NaN
Now, this kind of makes sense — at least in part. If you subtract, divide or multiply two objects together (or even non-empty strings), you’ll get that especially annoyingNaN
— which is short for Not a Number. If you add them together, however, you get [object Object][object Object]
, which might seem a bit weird. But let’s return to that later.
However, that all looks totally sane compared to what you get when you do the same with Date instances:
const date = new Date();
date + date
// "Tue Jun 12 2018 21:38:21 GMT+0200 (CEST)Tue Jun 12 2018 21:38:21 GMT+0200 (CEST)"
date - date
// 0
date * date
// 2.3373282061373058e+24
date / date
// 1
+date
// 1528832363668
Now, let’s dig into why this happens!
Let’ start with understanding the general concept of what’s happening. When you try to use arithmetic operators with stuff that aren’t of the Number type, JavaScript tries to coerce that value into something it can work with. It calls this special method @@toPrimitive
, which — depending on what kind of operand it is (and a few other things) — either tries to turn it into a String or a Number.
If it can’t make sense of the operation, it outputs that special value NaN
. You might have come across it in your JavaScript adventures. 🦇
Now, this seems pretty consistent until you meet the addition operator. Turns out, the +
operator is overloaded in JavaScript, which means it can both concatenate strings and add numbers. This leads to quite a few different quirks — some of which we’re about to touch on now — but it does makes sense of what happens to both the plain object and the Date instance in our previous instance.
As previously mentioned, JavaScript tries to convert each operand to a primitive value (namely a string or a number) when you apply an operand to it. In the case of the addition operator, this operation calls the toString
prototypal method. For objects, that will return the string [object Object]
, and for dates, that will return a formatted, human readable date string (in our case, Tue Jun 12 2018 21:32:21 GMT +0200 (CEST)
).
This doesn’t explain why we suddenly get a bunch of milliseconds when we just write + new Date()
, though.
Turns out, due to a special case in the language specification, the Date object is treated in a different way than other objects when it comes to using the addition operator:
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.
http://www.ecma-international.org/ecma-262/5.1/#sec-11.6.1
This means that when you add two Date items together, you call the toString
method on both, while when you subtract the two, the toValue
method is called. When you use the + operator as a unary operator, the toValue
function is called as well.
When you call the valueOf
method on a Date-object, you get the amount of milliseconds. And that’s why you can subtract two Date objects.
This article is why you shouldn’t do math with Date objects or instances. Do you really want to write code that makes you dive into the ECMAScript specification to understand why it works? Stop it! Just write Date.prototype.getTime()
like a sane person!
Anyhow, if you ended up learning something about this amazing, quirky and weird language, I’m glad. And please dig into why something is weird. It’s a fantastic experience. Thank you for your attention!
All rights reserved © 2024