All guides

LEARN \u00b7 DEBUGGING GUIDE

Timezone off by one day in JavaScript: how to debug it

A user enters '2025-03-15' in a form. It saves as March 14. Or it displays as March 16. The date shifted by exactly one day. That is a timezone offset, not a random bug.

IntermediateJavaScript/Node runtime debugging

What this usually means

JavaScript's `Date` object is always a moment in UTC internally. When you parse a date string without a timezone, JavaScript guesses whether to treat it as UTC or local time — and the guess depends on the string format. ISO 8601 dates like `'2025-03-15'` are treated as UTC midnight. But if your server or database expects local time, the date shifts by your UTC offset. Similarly, displaying a UTC date in a local timezone can push it across midnight into the next or previous day.

( 01 )Fast diagnosis

The first ten minutes \u2014 establish facts before touching code.

  • 1Check the exact string being parsed. `'2025-03-15'` (ISO date-only) is parsed as UTC. `'2025-03-15T00:00:00'` is also UTC. `'03/15/2025'` is parsed as local time.
  • 2Check the database column type. `DATE` (no time) vs `TIMESTAMP` (with time) vs `TIMESTAMPTZ` (with timezone). Storing a UTC midnight timestamp in a DATE column can truncate to the wrong day.
  • 3Log the raw Date value at each step: after parsing, before saving, after reading, before displaying. Find which step introduces the shift.
  • 4Check the server's timezone (`process.env.TZ`, system timezone). If it is different from the user's timezone, date display can shift.
  • 5Verify the API serialises dates consistently. JSON has no native date type — dates are usually strings. The format matters.
( 02 )Where to look

The specific files, logs, configs, and dashboards that usually own this bug.

  • searchDate parsing code — how the input string becomes a Date object
  • searchDatabase schema — column types for date fields (DATE vs TIMESTAMP vs TIMESTAMPTZ)
  • searchAPI serialisation — JSON.stringify or custom date formatting
  • searchFrontend date display — `toLocaleDateString`, date-fns, dayjs, moment formatting
  • searchServer timezone configuration — `TZ` env var, system locale
  • searchORM date handling — how your ORM maps database date types to JavaScript Date objects
( 03 )Common root causes

Practical causes, not theory. These are the things you will actually find.

  • warningParsing `'2025-03-15'` as UTC midnight, then formatting in a negative-offset timezone shifts to the previous day
  • warningStoring a JavaScript Date as a `DATE` column — the database truncates the time, potentially shifting the day
  • warningThe server timezone differs from the user's timezone, and the display code uses local time
  • warningJSON serialisation drops the timezone offset, and the consumer parses it as local time
  • warningDaylight Saving Time transition — one day has 23 or 25 hours, shifting midnight calculations
  • warningUsing `new Date(dateString)` with inconsistent date string formats across the codebase
( 04 )Fix patterns

Concrete fix directions. Pick the one that matches your root cause.

  • buildParse dates with an explicit format: use a library like date-fns `parseISO` or dayjs with a format string
  • buildStore dates as UTC and convert to the user's timezone only for display
  • buildUse `TIMESTAMPTZ` in the database so timezone is preserved with the value
  • buildSerialise dates as ISO 8601 with timezone offset: `'2025-03-15T00:00:00.000Z'`
  • buildUse date-only strings (`'2025-03-15'`) without time for date-only fields and never convert them to Date objects that include time
( 05 )How to verify

A fix you cannot prove is a guess. Close the loop.

  • verifiedEnter a date in the UI, submit, reload, and confirm the displayed date matches the input.
  • verifiedTest with a user timezone that is UTC+14 (Kiritimati) and UTC-12 (Baker Island) — both edge cases.
  • verifiedTest around a DST transition date for the user's timezone.
  • verifiedWrite a unit test that parses a date string, serialises it, and asserts the round-trip preserves the date.
  • verifiedCheck the database directly: `SELECT date_column FROM table WHERE id = ...` and compare to the input.
( 06 )Mistakes to avoid

Things that make this bug worse or harder to find.

  • warningUsing `new Date()` without understanding its parsing rules
  • warningAssuming all users are in the same timezone as the server
  • warningStoring local time in the database instead of UTC
  • warningUsing `toISOString()` for display — it always outputs UTC, which may not match user expectation
  • warningNot testing date logic across timezone and DST boundaries