Timezone bugs share a specific, frustrating pattern: they work fine in development, pass QA, and then break for real customers in distant timezones or right when daylight saving time shifts. By then they're in production, often silently — a notification that goes out a day late, a monthly report that splits revenue across the wrong two months, a scheduled feature launch that fires at the wrong local time for half your users.
The core rule: store in UTC, convert at the edges
The foundational discipline that prevents most of these bugs is simple to state and easy to violate in practice: store every event time in UTC, and only convert to a local timezone at the point of display or user input. A useful shorthand: store universally, display personally.
In PostgreSQL specifically, this means using TIMESTAMPTZ rather than a plain TIMESTAMP. TIMESTAMPTZ automatically normalizes incoming timestamps to UTC for storage, which gives you one unambiguous source of truth you can convert to any timezone for display without ever losing information about what instant in time actually occurred.
Use IANA timezone names, not raw offsets
A common mistake is storing a UTC offset (like -05:00) instead of a named timezone (like America/New_York). Offsets don't account for daylight saving time transitions — an offset that's correct in January can be wrong in July for the same location. IANA timezone names encode the actual rules for when DST applies in a given region, so storing the named zone (and converting with a library that understands those rules) is what actually survives a DST transition correctly.
Don't build timezone logic on the native Date object
Most languages' built-in date/time objects (JavaScript's Date being the classic example) have known deficiencies handling timezone and DST math correctly. The practical fix is using a dedicated library — Luxon and the newer Temporal API in the JavaScript ecosystem, or equivalent libraries in other languages — rather than hand-rolling timezone arithmetic. This isn't a minor style preference; native date handling is a frequent source of the exact off-by-one-hour bugs that only show up during DST transitions.
Three things to test separately
A timezone-aware application actually has three distinct concerns that need separate test coverage, and conflating them is where a lot of bugs hide:
- Storage as an absolute instant — does the system correctly store the same moment in time regardless of what timezone the input came from?
- Display as a localized view — does the system correctly convert that stored instant back into each user's local time for display?
- Scheduling as a timezone-dependent rule — for recurring events tied to a local time (like "send this reminder at 9am in the user's timezone every day"), does the system correctly recompute the UTC trigger time across a DST transition, since "9am local" corresponds to a different UTC instant before and after the clocks change?
That third category is where most subtle bugs live, since it requires re-deriving the UTC time from a rule rather than storing a fixed UTC timestamp once and reusing it.
Where this bites SaaS products specifically
Scheduled notifications, recurring billing dates, usage reports that aggregate "this month," and any feature involving a user-set reminder time are the highest-risk areas. Each of these either needs to recompute against a rule (reminder times) or needs very deliberate boundary handling (billing periods, monthly reports) so a report doesn't silently split data across an unintended boundary because of a timezone conversion error.
Timezone rules themselves aren't static
A subtlety that catches even teams following every rule above: the IANA timezone database itself changes periodically, because countries actually change their DST policy with some regularity — Mexico eliminated most DST observance in 2022, Egypt reinstated DST in 2023, and British Columbia has had a pending DST-related policy change under discussion for 2026. Each of these requires an update to the underlying timezone data your libraries depend on (the tzdata package on most systems), not just your application code. If your deployment pipeline doesn't include a process for keeping the timezone database current, a correctly-written application can still compute the wrong local time for a region after its government changes the rules — the bug isn't in your code at all, it's in stale reference data your code trusted. Treating tzdata updates as a routine dependency-update task, the same way you'd patch a security vulnerability, closes this gap.
Leap seconds are a related, rarer failure mode
A more obscure but real edge case worth knowing about, particularly for anything doing precise scheduling: leap seconds have historically caused production outages when systems assume time can never move backwards. Past leap second insertions have caused measurable incidents — including services experiencing hiccups and, in some documented cases, servers spiking to 100% CPU because of how their internal clock-handling code reacted to the discontinuity. This is a narrower risk than DST handling (leap seconds are rare, and the standards bodies are moving to phase them out over the coming decade), but any system doing sub-second precision scheduling or relying on strictly monotonic timestamps for correctness should be aware that "time went backwards by one second" is a real historical failure mode, not a theoretical one — and that assumption is worth testing explicitly if your system depends on it.
The practical takeaway
Store everything in UTC using TIMESTAMPTZ or equivalent, use IANA timezone names rather than raw offsets, never build timezone math on your language's native date object, and write tests that explicitly cross a DST boundary — not just tests that happen to run in whatever timezone your CI server is in. Most timezone bugs are entirely preventable with this discipline; they show up in production specifically because it's easy to skip during initial development and only surface once real users in real timezones start using the feature.
Sources: tinybird.co, github.com/jacksonkasi1, qaskills.sh
Keep reading
Get new posts as they publish
No spam — just the next post, straight to your inbox.