Saying stuff about stuff.

Getting the last day of the month or year in JavaScript

It’s easy to get the first day of a month but what about the last day of a month? Looking on Stack Overflow this is quite a common ask but it’s not something I’d ever encountered and the solution is quite interesting so I thought I’d describe it here.

It turns out the way to get the last day of a month is to ask for the day before the first day of the following month! I particularly like that phrasing because it’s both a description for humans and an explanation of the code. Here’s an example:

const month = 5 // June.

const startOfMonth = new Date(2019, month, 1)
// Sat Jun 01 2019...

const endOfMonth = new Date(2019, month + 1, 0)
// Sun Jun 30 2019...

Obviously zero is the day before the first day of a month?! And zero isn’t a special case, you can continue to count back:

new Date(2019, 6, 1)
// Mon Jul 01 2019...

new Date(2019, 6, 0)
// Sun Jun 30 2019...

new Date(2019, 6, -1)
// Sat Jun 29 2019...

// Keep going...

new Date(2019, 6, -364)
// Sun Jul 01 2018...

It works with months too, and across years:

new Date(2019, 11)
// Sun Dec 01 2019...

new Date(2019, 12) // ¿December + 1?
// Wed Jan 01 2020...

The two can be combined to get the last day of the year by asking for the zeroeth day of the thirteenth month (remembering it’s zero-indexed, so month number 12):

new Date(2019, 12, 0)
// Tue Dec 31 2019...

Or the final second before the new year:

new Date(2019, 12, 0, 24, 0, -1)
// Tue Dec 31 2019 23:59:59 GMT+0000 (Greenwich Mean Time)

And it’s the same behaviour when you mutate a date object — it does the cleverness on write so it reads back normally.

const date = new Date(2019, 6)
// Mon Jul 01 2019...

date.setMonth(12)
// Wed Jan 01 2020...

date.getFullYear()
// 2020

date.getMonth()
// 0

It seems a bit weird at first but behaves entirely consistently — as described in the specs (even the first one from 1997) — and is unusually developer friendly (particularly compared to the zero-indexed month debacle), I can’t believe I didn’t know this already.