Why Your Date Is Off by One Day
Your date moves a day because one part of the code uses UTC and another uses local time. Find which step does it, keep calendar days as YYYY-MM-DD strings, and name the time zone when you format.
Your date is off by one day because some part of your code reads or writes it in UTC and another part uses local time. A calendar day like "October 15" has no time and no zone, but a JavaScript Date is always an exact moment. Convert between the two in the wrong zone and the day moves. Find the step where it happens, then keep calendar days as plain YYYY-MM-DD strings.
Skip ahead: Just want the safe patterns? Skip to "The fixes".
First, which way is it off?
- One day early, and you're west of UTC (the Americas): you probably parsed
"2026-10-15"withnew Date()and displayed it with local methods. - One day early, and you're east of UTC (Philippines, most of Asia, Australia): you probably built a local date and sent it with
toISOString(). - Only on the server, or only for some users: the server and the browser are in different zones. Check which one formats the date.
- Only near midnight: a moment (with time) is being shown as a day in a different zone than where it happened.
Bug 1: parsing a date-only string
const d = new Date("2026-10-15");
console.log(d.getDate(), d.toDateString());
14 Wed Oct 14 2026
"2026-10-15" is read as midnight UTC. getDate() and toDateString() use local time, and in New York that's 8 PM on the 14th. In Manila the same code prints 15, which is why this bug survives testing in Asia and shows up when a customer in the US uses the site.
The confusing part: add a time and the rule flips. "2026-10-15T00:00" (no Z) is read as local time. Same-looking strings, different zones.
Bug 2: sending a local date with toISOString
const picked = new Date(2026, 9, 15);
console.log(picked.toDateString());
console.log(picked.toISOString());
console.log(picked.toISOString().slice(0, 10));
Thu Oct 15 2026
2026-10-14T16:00:00.000Z
2026-10-14
Midnight in Manila is 4 PM the day before in UTC, and toISOString() always converts to UTC. So the date you send to the server is the 14th. This is the common one for anyone east of UTC, and date pickers that return a local Date make it easy to hit.
Bug 3: the server formats in its own zone
Servers are often set to UTC. An order placed at 7:30 AM on November 1 in Manila is 11:30 PM on October 31 in UTC:
const placed = new Date("2026-11-01T07:30:00+08:00");
console.log(placed.getDate(), placed.getMonth() + 1);
console.log(placed.toLocaleDateString("en-PH", { timeZone: "Asia/Manila" }));
31 10
11/1/2026
getDate() on the server says October 31, so a monthly report puts the order in the wrong month. Passing timeZone: "Asia/Manila" gives the business's day.
The fixes
1. Keep calendar days as strings. Birthdays, due dates and delivery dates stay "2026-10-15" from the form to the database and back. Don't create a Date unless you need to calculate with it.
2. If you need a Date for a calendar day, build it locally from the parts:
function parseDay(text) {
const [y, m, d] = text.split("-").map(Number);
return new Date(y, m - 1, d);
}
const pad = n => String(n).padStart(2, "0");
function formatDay(date) {
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}`;
}
const due = parseDay("2026-10-15");
console.log(due.toDateString(), formatDay(due));
Thu Oct 15 2026 2026-10-15
Local in, local out. It gives the same day in every zone.
3. Store moments in UTC, show them in a named zone. Save "2026-11-01T07:30:00+08:00" or its UTC form, and format with Intl.DateTimeFormat plus an explicit timeZone for the business or the user.
4. Never use toISOString().slice(0, 10) for "today". In Manila it gives yesterday's date until 8 AM. Use formatDay(new Date()) from above.
⚠ Warning: Don't fix an off-by-one by adding or subtracting a day, or by adding hours, until it looks right. It will look right in your zone and be wrong in the next one, and twice a year in places with daylight saving time. Find the step that changes the zone and fix that.
💡 Tip: To test date code properly, run it in more than one zone. In Node: TZ=America/New_York node app.js, then TZ=Asia/Manila node app.js (on Windows PowerShell, $env:TZ="America/New_York"; node app.js). If the output changes, you've found a bug before your users did.
Checklist
- Is this value a calendar day or a moment?
- Calendar day: kept as
YYYY-MM-DD, or built from parts? - Any
new Date("YYYY-MM-DD")followed bygetDate()? - Any
toISOString()on a local date? - Every formatter on the server has
timeZone?
Run your date code with TZ=America/New_York and then TZ=Asia/Tokyo. Same output both times?
About
Spot which of three bugs moves your date, depending on where you are and where the code runs, then apply the four fixes and test in more than one time zone. After this, October 15 stays October 15 for every user.
- Responsible
- Mary Q
- Last Update
- 08/31/2026
- Reading Time
- 5 min
Reviews
Sign in to write a reviewComments (0)
Sign in to commentNo comments yet.