Time zones, dates and calendars in system design
Handling time correctly in distributed systems and products: UTC timestamps versus local times, time zone identifiers and daylight saving, scheduling recurring events, business days and local dates, storing durations, clock skew, leap seconds, and testing time-dependent code.
Reading is half of it. See this used in a real interview: walk through Design Airbnb →
Time bugs are among the most common and embarrassing in production: reminders sent at 3 a.m., bookings shifted by an hour after a daylight saving change, daily reports missing a day's data, payments due on a date that does not exist. Designs involving schedules, bookings, reports or notifications should state how they handle time. A few rules prevent almost all of these bugs.
Two different kinds of time
- Instants: a specific moment, the same everywhere ("the payment was captured at 2026-10-05T12:00:00Z"). Store as UTC timestamps.
- Local (civil) times: what a person sees on a wall clock in a place ("check-in at 15:00 in Lisbon", "every Monday at 9:00 my time"). Store as a local date or time plus a time zone identifier.
Mixing them up is the root of most bugs. A future local event cannot safely be converted to UTC at booking time, because time zone rules can change before it happens.
Time zone identifiers, not offsets
Store IANA zone names like Europe/Lisbon or America/New_York, not offsets like +01:00. An offset is a snapshot; a zone carries the rules, including daylight saving transitions and historical changes. Governments change rules with little notice, so keep the time zone database updated in every service.
Daylight saving
Twice a year in many zones:
- Spring forward: some local times do not exist (02:30 may be skipped).
- Fall back: some local times happen twice (01:30 occurs in two different instants).
Decide what your product does in each case (run at the next valid time; run once, not twice) and use a time library that handles it. Recurring schedules must be computed in local time and converted to UTC for each occurrence, not as "every 24 hours from the first run". See delayed jobs and distributed cron.
Common product cases
| Case | Store | Notes |
|---|---|---|
| Event log, payments, audit records | UTC instant | display in the viewer's zone |
| Hotel night, rental check-in | local date and time plus the property's zone | a "night" is a local date, not a UTC range. See bookings and reservations |
| Recurring reminder | local time, recurrence rule, user's zone | recompute when the user changes zone |
| Birthday, due date | local date with no time | "the 5th" should not become the 4th in another zone |
| Business hours | weekly local schedule plus zone | include holidays per region |
| Daily report | the business's reporting zone | "yesterday" depends on the zone; be explicit |
| Duration (a 15-minute hold) | an elapsed duration | not affected by zones or DST |
Daily aggregates
"Daily active users" and "revenue per day" depend on where a day starts. Choose a reporting time zone (often UTC for global metrics, local for per-market reports), document it, and partition data accordingly. Aggregating in UTC and showing it as local days produces numbers that do not match anyone's expectations. See data quality and data contracts.
Scheduling for users
- Send notifications at a sensible local time in each user's zone, with quiet hours. See push notifications.
- Spread "9 a.m. everywhere" sends: they become hourly waves as each zone reaches 9 a.m., which naturally smooths load, but large zones still create spikes; add jitter. See Design a Notification System.
- Recompute schedules when users travel or change their zone.
Money and dates
Billing periods, interest and due dates follow local or contractual calendars: month-end varies in length, the 31st does not exist in every month, and business days exclude weekends and holidays that differ by country. Use explicit rules and a holiday calendar per market. See Design a Payment System.
Machine clocks
Server clocks drift and are corrected by NTP; they can jump backwards. So:
- Use monotonic clocks for measuring durations and timeouts within a process.
- Never rely on wall-clock order between machines for correctness; use sequence numbers, logical clocks or a single authority. See unique ids, ordering and time.
- Leap seconds are usually smeared by cloud providers (spread over hours), but be aware they exist.
APIs and formats
- Exchange instants as ISO 8601 strings with
Zor an explicit offset, or as Unix timestamps with documented units (seconds versus milliseconds is a classic bug). - Exchange local dates as
YYYY-MM-DDwithout time, and local times with the zone id alongside. - Let clients send their zone; do not guess from IP when it matters.
See API design.
Testing
Inject a clock instead of calling the system time directly, so tests can simulate DST transitions, month ends, leap years and different zones. Test with zones that have half-hour offsets and with dates near transitions. See testing distributed systems.
In the interview
For Design Airbnb: "Nights are local dates in the listing's time zone; check-in times are local times with the zone id; the payment and audit timestamps are UTC instants." For Design a Job Scheduler: "Cron expressions are evaluated in the job's zone with explicit DST rules; each occurrence is computed to a UTC instant for the timer store."
Checklist
- UTC instants for events; local time plus zone id for human schedules.
- IANA zone names, updated time zone data everywhere.
- Explicit DST behaviour for skipped and repeated times.
- Local dates for nights, birthdays and due dates.
- A documented reporting zone for daily aggregates.
- Monotonic clocks for durations; no cross-machine ordering by wall clock.
- Injected clocks and tests around transitions.