bugs · Sep 18, 2026
The Bug That Moved Every Date by a Day
For three releases, people west of Greenwich saw note dates one day early. Nothing crashed and nothing was logged. How we found it, the two lines that explain it, and what we do now.
It started with an email from Thandiwe, who lives in Vancouver. A note she had written on the eighth of March said, in its footer, that it was last edited on the seventh. She assumed she'd made a mistake. Then she checked six more notes. All of them were a day early.
We couldn't reproduce it. Everyone on the team lives within two hours of Greenwich, and in our time zones every date was correct. The bug had been shipping for three releases and only people to the west could see it.
Where we looked first
Linnea started with the obvious suspects. The sync layer, because dates travel through it. The footer component, because it displays them. The database, because it stores them. All three were fine. The stored value was the string "2026-03-08", exactly what Thandiwe had written. The display component printed whatever it was handed.
So the wrong date was being created between the two, in a small helper that turned the stored string into something the footer could format. That helper was four lines long and had been written with care.
The two lines
Here is the whole cause, reduced.
const stored = "2026-03-08";
const asUtc = new Date(stored);
const asLocal = new Date(`${stored}T00:00:00`);
console.log(asUtc.toLocaleDateString("en-CA"));
console.log(asLocal.toLocaleDateString("en-CA"));Run it with the time zone set to Los Angeles and it prints 2026-03-07 and then 2026-03-08. Set the zone to Oslo and it prints 2026-03-08 twice.
A date-only string, handed straight to the Date constructor, is read as midnight in UTC. Adding a time part with no offset makes the same string read as midnight local time. Midnight UTC on the eighth is still the evening of the seventh anywhere in the Americas, so when the footer formats it in the viewer's own zone, it correctly reports the seventh.
Every piece of code was doing its job. The bug lived in the assumption that a calendar date is a moment in time. It isn't. March the eighth is a day on a calendar, with no hour and no zone, and treating it as an instant means it moves with whoever is looking.
The fix, and the smaller fix
The first fix was to parse the string into year, month and day, and build the local date from those three numbers. That solved it for the footer.
The better fix was to stop passing a date through a time-shaped object at all. We now keep date-only values as strings until the last possible step, and the formatter reads them as three numbers. There is no Date object in the path, so there's no zone to get wrong.
We also found two other places with the same shape: the "created" date in the exported file and the grouping of notes under "Today" and "Yesterday". Each had been off by a day for the same people, and nobody had noticed because the errors cancelled out in the sorting.
What we do now
Our test suite runs the date helpers under three zones: one ahead of UTC, one behind it, and UTC itself. A bug that only shows up west of Greenwich now fails in CI.
Review asks one extra question about any date: is this a moment, or is this a day? Those are different types and they get different names in the code.
We added Thandiwe to the beta list, and she's been the first to test every release since. A report from someone in a different zone is worth more to us than a week of internal testing.
The bug shipped in 2.4. The fix is in 2.6.1. In between, the most useful thing our software did was let one person, patiently, check six more notes.
No comments yet
Comments are open. Have a thought or a question? Share it below.