A date that shows up as the day before is one of the most common small bugs in front-end code. Here is a one-minute check you can run in any console.
The check
const a = new Date("2026-10-25"); // date-only string
const b = new Date("2026-10-25T00:00"); // date-time string, no offset
Per MDN's Date reference, when there is no time zone offset, date-only strings are read as UTC, and date-time strings are read as local time. MDN notes this comes from a historical spec error that could not be changed for web compatibility.
So the two lines above are different moments unless your machine is on UTC.
What I saw
I ran this in Node with the time zone set to America/Los_Angeles:
new Date("2026-10-25").toString()
// Sat Oct 24 2026 17:00:00 GMT-0700 (Pacific Daylight Time)
new Date("2026-10-25").getDate()
// 24
new Date("2026-10-25T00:00").toString()
// Sun Oct 25 2026 00:00:00 GMT-0700 (Pacific Daylight Time)
Same calendar date typed in, but the first one is Saturday the 24th when you ask for the local day. On a UTC machine both lines print the 25th, which is why this often passes on a dev laptop or CI and fails for users in other zones.
Quick fixes
- If the string is a calendar date with no time (a birthday, a due date), keep it as a string, or format it with
timeZone: "UTC"so the same zone is used for parsing and display. - If you mean local midnight, build it from parts:
new Date(2026, 9, 25)(months start at 0). - If you mean an exact moment, include an offset or
Zin the string.
One test worth adding
Run the date logic once with the process in a zone far from UTC, for example TZ=America/Los_Angeles and TZ=Pacific/Auckland. If the displayed day changes between runs, there is a parse or display mismatch.
Which time zone do you use to test date code?
Top comments (3)
Your new Date(2026, 9, 25) fix also needs a calendar-validity check if those parts come from a form. new Date(2026, 1, 29) silently becomes March 1, not an invalid date. I'd add that and April 31 to your Los Angeles/Auckland tests, with 2028-02-29 as the valid leap-year control. Reject changed year/month/day components before formatting. That keeps a timezone fix from quietly accepting a date the user never entered.
Good catch, thanks. I ran it in Node just now: new Date(2026, 1, 29) gives March 1, and new Date(2026, 3, 31) gives May 1, while new Date(2028, 1, 29) stays Feb 29. So the parts constructor rolls over instead of rejecting. Reading the year, month and day back and comparing them to the inputs is a cheap check, and those three cases belong in the test list.
The mismatch is at the boundary, not in the render. Storing the offset with the date is safer than parsing it back, and the check should assert the timezone where the value enters.