The form asked for a start date.
The user picked
the fifteenth of October.
The API stored it
and sent it back as a string.
2026-10-15.
The page turned it into a Date
and showed it.
In London
it said the fifteenth.
In New York
it said the fourteenth.
Same string.
Same code.
One day apart.
Nothing was wrong with the data.
The bug was one line,
new Date with a string in it,
and a rule
most of us never read.
JavaScript parses a date on its own,
year, month, day,
as midnight in UTC.
Add a time to the same string,
with no offset,
and it parses as local time instead.
That is in the specification.
It is not a browser quirk.
So the fifteenth
became the first instant of the fifteenth
in UTC.
Then the page formatted it
in the user's own time zone,
and in New York
midnight UTC
is eight in the evening
the day before.
Everyone west of Greenwich
saw yesterday.
The team,
sitting on Greenwich,
saw nothing wrong.
The tests ran on a server set to UTC,
so they passed too.
A start date is not a moment.
It is a label on a calendar,
and it means the same day
wherever the reader is standing.
The moment the code
turned it into an instant,
it had to pick a time zone,
and it picked one
nobody chose.
So keep calendar days as calendar days.
Leave the string alone
if all you do is show it.
If you need arithmetic,
use a plain date type,
Temporal.PlainDate where you have it,
or a library that has one.
If you must use Date,
build it from the parts,
year, month minus one, day,
which gives local midnight,
or format it with the time zone set to UTC
so it reads back what it was given.
Then make the tests travel.
Run the suite with TZ set to New York.
Run it again with Auckland.
Any test that changes its answer
was depending on where the server lives.
A birthday has no time zone.
Do not give it one
by accident.
– Serguey Asael Shinder
Top comments (0)