The date that was yesterday until 3am
This line looks harmless:
const today = new Date().toISOString().slice(0, 10);
toISOString() gives the UTC date. I live in UTC+3, so from midnight until 03:00 every night that line names yesterday, and on the first of a month it names the previous month.
An app I maintain did it in three places: the today marker on a calendar, and a gate on the admin page that decides whether the month you’re editing is already in the past, twice. Nobody had noticed, because nobody uses a rostering app at 2am. Except the schedulers, who work nights. It surfaced in the review of the change that moved the app’s scripts out of its templates, as an old bug in code that change had only relocated.
The obvious fix
One shared helper that builds the date from local parts, in outline:
function isoDate(d = new Date()) {
const p = (n) => String(n).padStart(2, "0");
return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())}`;
}
The three clock reads were switched to it, and the test runs the helper under a UTC+3 timezone, with a control proving the zone really applied, because a timezone test that silently runs in UTC passes for the wrong reason.
One site that looks the same was deliberately left alone. A balance panel builds its dates in UTC and reads them back in UTC. That is arithmetic, not a clock read, and “fixing” it would break it. The test sweeps for the clock pattern specifically so nobody tidies it up later.
Two of the three were wrong
A second pass over my own diff, before anything shipped, found the problem. The admin page’s past-month gate is not an independent opinion about what day it is. It is a mirror of a decision the server takes, and the server runs in a container on UTC.
The old UTC read had been right for that site. My fix made the client and the server disagree for three hours on the first of every month: the client would demand an acknowledgement checkbox that the server never rendered, and committing the change would be impossible.
A local-date fix applied to a server mirror is the bug, not the cure. When the browser is predicting what the server will decide, the browser’s clock is the wrong clock, whichever timezone you read it in. That gate now reads the server’s own timestamp out of the payload it already loads, which is what another page in the same app had learned to do weeks earlier.
So the final count: one genuine bug on the calendar marker, fixed with the local helper; two sites where UTC had been accidentally correct, now reading the server’s date on purpose; and one look-alike left as it was.
The check I ended up with
My first guard pinned three spellings of the bug. That is pinning instances, not the class. It is now inverted: the test fails on any UTC serialisation of a date in the page scripts unless the line is explicitly allowlisted with a reason.
If you grep your own front end for toISOString().slice(0, 10), ask two questions of each hit. Is this a clock read or arithmetic? And whose clock should it be: the person looking at the screen, or the server that will judge what they submit?
