I Never Had to Think About Timezones. Then I Did.
- 30 Jan, 2026
Ghana sits on GMT+0. No daylight saving, no offset, nothing. For most of my life, “the time” and “the time in Greenwich” were the same number. I never had a reason to think about timezones, because I had never once been in a situation where they mattered.
That turned into a blind spot I didn’t know I had until dates started landing on the wrong day while I was building Go By Bike Halifax. Someone would log something in the evening and it would save under tomorrow’s date instead of today’s. Not always, only in a narrow window late in the day. It took me a minute to see the shape of it, and honestly, a part of me felt a little silly once I did.
Here’s what was actually happening. Most servers, no matter where they physically sit, run their internal clock in UTC. That’s true whether the datacenter is in Virginia, Frankfurt, or Halifax. So “the server is in Canada” tells you nothing about what timezone the server’s code thinks it’s operating in, those are two completely unrelated facts. If your code asks the system for “today’s date” without explicitly saying which timezone you mean, you get UTC’s today, not the user’s today. And Halifax is three hours behind UTC. So for the last three hours of every Halifax day, UTC has already rolled into tomorrow while it’s still today for everyone actually using the app. Anything logged in that window gets silently mis-dated.
The reason this was invisible to me for so long isn’t that I was careless. It’s that growing up at GMT+0 never gave me a reason to notice it. If your local time and UTC are always the same number, code that quietly assumes “local time equals UTC” is never wrong. It’s not a bug you can trip over by accident when you live at GMT+0, because your own lived experience of time never contradicts it. The bug was always there, waiting for someone whose day doesn’t line up with Greenwich’s.
Once I actually looked, the bug wasn’t in one place. It was everywhere something touched a date: the code that saved a new entry, the code that displayed it back, a date picker that wouldn’t let you select today after a certain hour, a stats calculation that was quietly three hours off. Each one was a separate place someone had to independently notice that the server’s idea of “now” and the user’s idea of “now” were not the same thing.
The fix, in every case, was the same principle: never trust the ambient system clock’s notion of “today.” Explicitly anchor every date operation to the timezone that actually matters, the one the humans using the app are standing in, not the one the machine running the code happens to default to. It sounds obvious written down. It clearly wasn’t obvious to me, until I was the one debugging it.
In practice, that means never doing this:
// Wrong: "today" according to whatever timezone the server happens to be in
const today = new Date().toISOString().slice(0, 10);
// Also wrong: same mistake, different method
const label = someDate.toLocaleDateString();
And always naming the timezone explicitly instead of letting it default silently:
// Right: pick the timezone that actually matters, every time
function localDateString(date, timeZone) {
return new Intl.DateTimeFormat("en-CA", { timeZone }).format(date);
}
const today = localDateString(new Date(), "America/Halifax");
Intl.DateTimeFormat is built into JavaScript, handles daylight saving automatically, and takes any IANA timezone name, America/Halifax, Africa/Accra, whatever’s actually relevant to your users. The moment a date touches your code, it should pass through something like this, never the bare system default.
One thing worth being precise about: hardcoding "America/Halifax" was correct here specifically because Go By Bike Halifax is a single regional campaign, everyone’s “today” is Halifax’s today, no matter where someone happens to be logging in from. That’s not the general answer for every app. If your users are actually spread across timezones and each of them cares about their own local day, a single hardcoded string is the same mistake in a different shape. What you need there is the timezone detected per user, usually via Intl.DateTimeFormat().resolvedOptions().timeZone on the client, stored against their account, and passed in instead of a constant. Same mechanism, the value just has to come from the user, not from you.
There’s a third option too: let the server send the raw UTC instant and let the client format it, using whatever timezone the viewer’s own device is already set to. No detecting or storing anyone’s timezone at all. It works well when a date is really a preference of whoever’s looking, something like “posted 3 hours ago,” where you want it to match the viewer’s own clock. It works less well when the date is a fact about the subject itself, anchored to a place, since then it shouldn’t change depending on who’s looking or where they happen to be. Which of the three approaches is right comes down to that one question: is this date a fact about the thing it describes, or a preference of whoever’s reading it.
I don’t think this makes me careless so much as it makes me someone who’d simply never been handed a reason to know. But I’d rather know it now than keep being surprised by it later, one silently mis-dated evening at a time.
