Why timezone bugs keep sneaking into your app (and how I finally stopped them)

A pragmatic, India‑aware guide to understanding and preventing timezone bugs — tactics I used to stop off‑by‑one‑day errors, broken cron jobs, and confusing timestamps.

Written by: Rohan Deshpande

Developer at a laptop with a wall clock visible, representing time and deadlines
Image credit: Unsplash

I used to think timezone issues were “edge cases” — until a client in Mumbai received a payment reminder a day late and blamed our app. The data in our DB looked right, but the reminder cron had been scheduled in UTC and converted poorly to IST at midnight. That one outage cost us hours of debugging, a tight apology email, and an uncomfortable team retro.

If you ship software that touches dates — calendars, billing, reports, scheduled jobs, or even simple activity timestamps — you will meet timezone bugs. They’re not exotic; they’re a predictable cost. Here’s a practical approach I adopted that reduced these bugs from weekly to rare, with the real tradeoffs you should expect.

What’s actually going wrong

Main principle: store an unambiguous truth, present a local truth

Concrete fixes that helped us

  1. Use IANA timezones end-to-end Don’t rely on numeric offsets like +5:30. Save and use zone names (Asia/Kolkata, America/Los_Angeles). This matters especially around DST and when clients or employees are outside India.

  2. Make your scheduler timezone-aware When you let users schedule tasks for local times, convert that local time to UTC for storage and scheduling. In Kubernetes, run cronjobs with UTC and use a conversion layer (server-side) that reads the user’s timezone and computes the next UTC run.

  3. Fix tests: freeze time and test across zones Add tests that run in at least two timezones (UTC and Asia/Kolkata) and include edge cases around midnight and DST transitions in other regions. Freeze time in unit tests so date-dependent logic is deterministic. We used libraries that let us set timezone in test runs, and it caught two logic bugs immediately.

  4. Rely on robust libraries, not string parsing magic Moment.js is deprecated; pick modern tools: date-fns + date-fns-tz in Node, Luxon where you need richer APIs, zoneinfo in Python 3.9+, and java.time in Java. These handle conversions and IANA updates better than hand-rolled parsing.

  5. Avoid comparing local DATE() values in the DB Queries like WHERE DATE(created_at) = ‘2025-12-22’ assume created_at is in local time. Instead convert the local day range to UTC bounds and query with created_at BETWEEN start_utc AND end_utc. If you need fast lookups by local date, add a computed column for the user’s local date (and index it). Tradeoff: extra storage and maintenance, but far fewer surprises.

India-specific notes

Realistic tradeoffs and constraints

What I stopped doing (and you should, too)

A quick checklist to adopt today

If your team treats time like another field to be cast around, you’ll keep chasing timezone bugs. Treat time as a cross-cutting concern: make it explicit, test it across zones, and accept the tradeoffs (storage, conversions, ops). After we adopted these rules, the number of incidents due to time-related confusion dropped dramatically — and the trust emails from product owners became notably less urgent.

Wrap-up Timezone bugs are a predictable pain, not a mystical one. A little upfront discipline — UTC storage, explicit tz fields, timezone-aware scheduling, and targeted tests — buys a lot of reliability. You’ll still hit edge cases (IANA updates, daylight-saving weirdness in other countries), but those are manageable operational problems, not silent correctness failures.

If you’re shipping a feature that touches dates this quarter, add one timezone test, store one timezone field, and schedule one cron in UTC. Small moves, big reduction in middle-of-the-night debugging.