Blog

A Timezone Bug That Moved Dates Back a Day on Every Save

  • javascript
  • typescript
  • postgres
  • debugging

I’m rebuilding an order system for a client in the United States: quotes, approvals, sales orders, invoices and payments. While we were working on it, the stakeholders reported a bug in the old system. They would open a quote, change nothing, press Save, and the quote’s date would move back one day. Save it again and it moved back another day.

We investigated, and the save moving the date was ridiculous. It turned out to be only a timezone issue.

A quote date is a calendar day, like 14 November. It has no time and no timezone. But the old code turned it into a JavaScript Date, and a Date is always a moment in time. That made 14 November midnight UTC on the 14th. A browser in New York shows midnight UTC as 7 PM on the 13th. The form displayed the 13th, and pressing Save wrote the 13th back to the database. Every save took one more day off.

I never saw it myself, because I work from the Philippines, which is eight hours ahead of UTC, so midnight UTC is still the same day for me. The bug only appeared for people in time zones behind UTC, which happened to be everyone actually using the system.

In the new system, the fix lives in the project’s coding rules:

  • A calendar day, like a quote date or a due date, stays a plain YYYY-MM-DD string from the database to the screen and back. It never passes through a JavaScript Date.
  • A moment, like “approved at 3:15 PM”, is a real timestamp, stored in UTC and shown in the business’s time zone.

In PostgreSQL the calendar days are date columns, read out as text with to_char(col, 'YYYY-MM-DD') so the database driver never turns them into a Date. On the frontend they’re kept as strings and only formatted for display.

Then there’s a test that does exactly what the stakeholders did. It loads a record, saves it without changing anything, and checks that the date is still the same day. It runs with the time zone set to New York, so it fails the way a real user would see it fail, even on my machine in the Philippines.

If your users are in a different time zone from you, test in theirs.

Loading the terminal…