All playbooks

Playbook 05 / 17

Debugging Timezone Bugs

Time bugs usually live at day boundaries, DST changes, and unit conversions.

The pattern

Some layer assumes local time where another assumes UTC, or seconds where another uses milliseconds. The code is internally consistent on the developer's machine and wrong everywhere else. Time bugs concentrate at boundaries: day rollovers, DST transitions, and the moment a timestamp crosses a serialization edge.

( 01 )Symptoms

How this failure announces itself.

  • warningDaily reports missing the first or last records of the day for some users.
  • warningExpiry or scheduling fires an hour early or late (DST), or several hours off (timezone).
  • warningThe bug reproduces only for users in certain timezones, or only on certain calendar days.
  • warningTimestamps that are exactly 1000x too large or too small - seconds versus milliseconds.
( 02 )First moves

The first ten minutes — establish facts before touching code.

  • 1Take one concrete wrong record and write down every timestamp it touched, with the zone and unit each layer assumed.
  • 2Reproduce with a frozen clock set to the failing boundary - 23:30 local, the DST switch night, the month end.
  • 3Identify the first layer where the timestamp's meaning changed; that is the bug, even if a later layer surfaced it.
  • 4Check the runtime's TZ setting in the failing environment - servers are usually UTC while laptops are not.
( 03 )Where to look

The code and config that usually owns this bug.

  • searchDate parsing - strings without explicit offsets get interpreted in whatever zone the runtime happens to use.
  • searchBoundary queries - BETWEEN day-start AND day-end is wrong unless both ends are computed in the user's zone.
  • searchScheduler configuration - cron daemons, job runners, and databases each have their own default timezone.
  • searchUnit conversions - every multiplication or division by 1000 is a suspect.
( 04 )Common fixes

Fix the cause, then make the regression impossible.

  • buildStore and transmit UTC instants; convert to local time only at the presentation edge.
  • buildMake timezones explicit in every parse and format call instead of relying on runtime defaults.
  • buildCompute calendar boundaries (day, month) in the user's zone, then convert back to instants for querying.
  • buildAdd regression tests pinned to hostile dates: DST transitions, midnight boundaries, leap days.
( 05 )Prove the fix

A fix you can't demonstrate is a guess. Close the loop.

  • verifiedThe original record lands in the right bucket in both a UTC and a non-UTC environment.
  • verifiedBoundary tests pass with the clock frozen at the previously failing instants.
  • verifiedRun the suite with TZ set to a different zone and confirm results do not change.