June 18, 2026
The Bug That Only Showed Up After Midnight. Welcome to Timezones
Some bugs you can reproduce any time. This one waited until 11 PM to ruin my evening. If you’ve ever shipped software with a date in it…
The Curly Brace
1 min read
Some bugs you can reproduce any time. This one waited until 11 PM to ruin my evening. If you've ever shipped software with a date in it, this one's coming for you too.
Whose 9 o'clock?
Five friends agree to meet "at 9." Easy — until one's in Mumbai, one's in London, one's in New York. Now "9" means three different moments. Someone shows up to an empty room.
That confusion, but in your database, is where timezone bugs are born.
What actually happened
A report counted "today's signups." It worked fine for me. Then a teammate in another timezone swore the numbers were wrong — and they were, for them. My "today" started at midnight my time; theirs started somewhere in the middle of my afternoon.
Then a nightly cleanup job scheduled for "midnight" ran at what was actually lunchtime for half our users. And when daylight saving flipped, a whole hour misbehaved. Every one of these traced back to the same root cause: we were storing local time, and local time is a moving target.
The fix is a discipline, not a library
There's one clock the whole planet agrees on: UTC. It doesn't have a timezone, it doesn't do daylight saving, it never argues. The rule:
- Store everything in UTC. Always.
DateTime.UtcNow, neverDateTime.Now. In Postgres, usetimestamptz. - Convert to the user's local time only at the very edge — the moment you display it. Not in the database, not in your business logic. Right before it hits their screen.
- Prefer
DateTimeOffsetoverDateTimewhen you can — it carries the offset with it, so the value isn't ambiguous.
Think of UTC as the language your system thinks in, and local time as a translation you do only when talking to a human.
var createdAtUtc = DateTime.UtcNow; // store this
// ...much later, in the UI layer only:
var shown = TimeZoneInfo.ConvertTimeFromUtc(createdAtUtc, userTz);var createdAtUtc = DateTime.UtcNow; // store this
// ...much later, in the UI layer only:
var shown = TimeZoneInfo.ConvertTimeFromUtc(createdAtUtc, userTz);The honest part
UTC everywhere covers 95% of cases. The tricky 5% is when the local time is the actual fact — like "this shop opens at 9 AM local, forever, even if the rules change." For those, store the local time and the timezone ID, not a fixed UTC instant. But that's the exception. Default to UTC and you'll dodge nearly every date bug there is.
One line to remember
Store in UTC. Convert at the edges. The moment you save a local time to your database, you've created a future bug — and stamped it with the exact date it'll detonate.
👏 Clap if a timezone has personally wronged you.
#dotnet #datetime #timezones #bugs #backend