Sechno
Programming

Handling Time Correctly: Practical Patterns, Pitfalls, and Code Examples

Concrete guidance for storing, comparing, measuring, and testing time in applications. Includes code examples in JavaScript and Python, database tips, tradeoffs, and testing strategies to avoid common datetime bugs.

SSechno Team 5 min read 75 views
Handling Time Correctly: Practical Patterns, Pitfalls, and Code Examples

Why time still breaks software

Time-related bugs are common, often subtle, and can cause data corruption, scheduling failures, analytics errors, and flaky tests. Problems usually come from three root causes: mixing timezones or naive/aware datetimes, using wall-clock time to measure intervals, and poor serialization/round-tripping across services. The guidance below focuses on practical, repeatable patterns you can apply immediately.

Core rules

  • Store timestamps in UTC. Persist a single canonical instant in UTC; convert to local time only for display.
  • Use timezone-aware datetimes. In languages that distinguish naive vs aware datetimes, prefer aware datetimes (or attach explicit offset info).
  • Use monotonic clocks for intervals. For timeouts, measuring elapsed time, and rate-limiting, use a monotonic clock that isn’t affected by system clock updates.
  • Serialize as ISO 8601 with offset (or Z for UTC). ISO 8601 strings are compact, human-readable, and widely supported by parsers and databases.
  • Keep timezone metadata when needed. If a scheduled event depends on a local timezone (calendar-based reminders), store the local timezone identifier alongside the UTC instant.

Practical examples

Below are small, copy-pasteable patterns for common tasks: canonical storage, elapsed-time measurement, and local-time display.

JavaScript — canonical UTC storage and measuring elapsed time

const now = new Date();
// Canonical storage format (UTC / ISO 8601)
const iso = now.toISOString(); // e.g. "2026-05-10T12:34:56.789Z"
 
// Parse back into a Date (always treated as UTC if ISO has Z or offset)
const parsed = new Date(iso);
 
// Measure elapsed time: use a monotonic API
// Browser: performance.now(); Node: process.hrtime.bigint()
function elapsedMillis(start) {
  return performance.now() - start;
}
 
const start = performance.now();
// ... work ...
console.log(`Elapsed ms: ${elapsedMillis(start)}`);

Notes: Date.toISOString() always emits UTC (Z). For scheduling or human-facing formatting, convert using Intl.DateTimeFormat or a timezone-aware library.

Python — timezone-aware datetimes and monotonic timing

from datetime import datetime
from zoneinfo import ZoneInfo
import time
 
# Create an aware UTC timestamp
now_utc = datetime.now(tz=ZoneInfo("UTC"))
iso = now_utc.isoformat()  # e.g. '2026-05-10T12:34:56.789000+00:00'
 
# Parse back (fromisoformat available for matching formats)
parsed = datetime.fromisoformat(iso)
 
# Use monotonic for elapsed intervals
start = time.monotonic()
# ... work ...
elapsed = time.monotonic() - start
print(f"Elapsed seconds: {elapsed:.6f}")

Database schema example (Postgres)

-- Store canonical instant with timezone information
CREATE TABLE events (
  id serial PRIMARY KEY,
  created_at timestamptz NOT NULL DEFAULT now(),
  event_local_tz text -- optional: e.g. 'America/Los_Angeles' if you need local scheduling
);

Notes: timestamptz stores instants with timezone awareness in Postgres and converts to UTC internally; store the user's timezone separately if you need to re-evaluate local calendar rules later.

Formatting for users

  • Keep a single UTC instant in your model and store the user's IANA timezone (e.g., "Europe/Paris") when you need to display local times or reschedule recurring events.
  • When showing datetimes, use the user's locale + timezone to format via Intl.DateTimeFormat (JS) or Babel/zoneinfo (Python).

Testing strategies

  • Freeze or fake time in unit tests to get deterministic behavior. Use freezegun for Python and @sinonjs/fake-timers (or similar) for JavaScript.
  • Tests that rely on elapsed durations should mock or stub monotonic sources when possible.
  • End-to-end tests for scheduled jobs should run in an isolated timezone environment or explicitly set the TZ to a known value to avoid DST flakiness.

Common pitfalls and tradeoffs

  • UTC everywhere vs. local-time semantics: Storing UTC simplifies ordering and math, but if your domain uses local calendar semantics (e.g., "send invoice on the 1st of every month at 09:00 in Europe/Berlin"), you must also store the timezone and apply rules on display / scheduling.
  • Using wall clock for intervals: Relying on system time (Date.now(), time.time()) for timeouts can break when the clock is adjusted. The tradeoff is limited—use monotonic APIs for intervals and wall-clock for human timestamps.
  • Leap seconds: Most platforms and databases do not expose leap seconds directly and may smear or ignore them. For almost all applications, you don’t need special leap-second handling; in very high-precision systems, consult your platform docs and consider NTP/PTP practices.
  • Storing timezone as offset only: Offsets (e.g., +02:00) do not encode DST rules. Prefer IANA zone names when you need to preserve scheduling semantics across DST transitions.

Checklist to apply right now

  1. Ensure all persisted timestamps are normalized to UTC (ISO string or native DB instant).
  2. Switch interval measurement to monotonic clocks where you need reliable timings.
  3. Store IANA timezone identifiers for user preferences or scheduled events.
  4. Add deterministic time fakes to your test suite for time-dependent behaviors.
  5. Audit external APIs for how they serialize datetimes (ISO 8601 with offset recommended).

Conclusion

Time-related bugs are predictable and avoidable with consistent rules: store instants in UTC, use timezone-aware types, measure intervals with monotonic clocks, and include timezone metadata for calendar-driven features. Apply the checklist above to reduce a large class of bugs and make time handling maintainable across your stack.

Further reading: the Stack Overflow Blog piece "Time is a construct but it can still break your software" provides a good conceptual overview of why these issues persist; use the practical patterns here to operationalize those ideas.

Was this helpful?

Share this post

Comments (0)

Want to join the conversation?

Log in or sign up to leave a comment and share your thoughts.

Log in to Comment