Subtracting two dates in JavaScript gives milliseconds, and dividing by 86,400,000 gives days. That works until a range crosses a daylight saving change, when one "day" is 23 or 25 hours and your answer comes out as 29.958 instead of 30. In the UK the clocks change on the last Sundays of March and October, so this bug shows up twice a year. Here is the reliable way.
The short answer
function daysBetween(a, b) {
const utcA = Date.UTC(a.getFullYear(), a.getMonth(), a.getDate());
const utcB = Date.UTC(b.getFullYear(), b.getMonth(), b.getDate());
return Math.round((utcB - utcA) / 86400000);
}
daysBetween(new Date(2026, 2, 1), new Date(2026, 3, 1)); // 31
Converting both dates to midnight UTC removes the time of day and daylight saving from the sum. Math.round is a final guard against tiny floating point drift.
Why the naive version breaks
const a = new Date(2026, 2, 28); // 28 March 2026, UK time
const b = new Date(2026, 2, 30); // 30 March 2026
(b - a) / 86400000; // 1.9583333 on a UK computer
On 29 March 2026 UK clocks go forward an hour, so that day only has 23 hours. Math.floor would then give 1, which is wrong.
Parsing "YYYY-MM-DD" strings safely
A date-only ISO string such as new Date("2026-03-30") is parsed as UTC midnight, while new Date(2026, 2, 30) is local midnight. Mixing the two shifts results by a day for users west of UTC, such as in the US. Split the string yourself:
function parseYMD(s) {
const [y, m, d] = s.split("-").map(Number);
return new Date(y, m - 1, d);
}
Months are zero-based in the Date constructor, hence m - 1. To display dates the UK way, see format a date as DD/MM/YYYY in JavaScript.
Inclusive vs exclusive counts
From Monday to Friday is 4 days by subtraction, but a booking or leave request "Monday to Friday" is usually 5 days. Add 1 when both ends count:
const inclusive = daysBetween(start, end) + 1;
Working days only
function workingDaysBetween(start, end) {
let count = 0;
const d = new Date(start);
while (d <= end) {
const day = d.getDay();
if (day !== 0 && day !== 6) count++;
d.setDate(d.getDate() + 1);
}
return count;
}
Using setDate moves by calendar days, so it is not affected by DST. To skip bank holidays too, see working days with UK bank holidays in JavaScript.
Weeks, months and years
Weeks are simple: Math.floor(days / 7). Months are not a fixed length, so count calendar months instead:
function monthsBetween(a, b) {
let m = (b.getFullYear() - a.getFullYear()) * 12 + (b.getMonth() - a.getMonth());
if (b.getDate() < a.getDate()) m--; // not a full month yet
return m;
}
The modern way: Temporal
The new Temporal API has a date type with no time or time zone, which removes this whole class of bugs:
const a = Temporal.PlainDate.from("2026-03-28");
const b = Temporal.PlainDate.from("2026-03-30");
a.until(b).days; // 2
a.until(b, { largestUnit: "months" }).toString(); // "P2D"
Temporal has shipped in recent Firefox and Chromium releases. Check MDN for current support and use a polyfill for older browsers. I cover it in more depth in the JavaScript Temporal API tutorial.
FAQs
Why not just use Math.ceil?
Ceil hides the 23-hour day but breaks the 25-hour day in October (30.04 becomes 31). Normalising to UTC fixes both.
Do I need a library like date-fns?
Not for a day count. date-fns differenceInCalendarDays does the same UTC trick; it is worth it if you already do lots of date maths.
How do I count days until a date from today?
daysBetween(new Date(), target). The function ignores the current time, so the answer will not change during the day.
For a no-code answer, the free date calculator on this site counts days, weeks and working days between two dates.