Time, Timezones & Epoch Engineering in Distributed Systems
The battle-tested engineering handbook for handling time: Master Unix epoch seconds vs milliseconds, eliminate Daylight Saving Time (DST) bugs, configure PostgreSQL timestamptz correctly, and debug NTP clock skew.
1. The Four Golden Rules of Time in Software Engineering
/* RULE 1: Internal representation MUST always be UTC or Unix epoch milliseconds.
* RULE 2: Store timestamps in databases as UTC; convert to user timezones exclusively at the UI edge.
* RULE 3: Store recurring schedules as (Local Time + IANA Timezone Identifier), NOT as UTC!
* Why? When governments change DST laws, UTC shifts, but the user expects the alarm at 09:00 local!
* RULE 4: Always use IANA zone names ("Europe/Amsterdam", "America/New_York"), NEVER 3-letter codes ("EST", "BST").
* Why? "CST" represents Central Standard Time (US), China Standard Time, AND Cuba Standard Time!
*/Three-letter timezone abbreviations are ambiguous and cannot compute daylight saving transitions.
| Concept | Specification | Example | Best Use Case | Copy |
|---|---|---|---|---|
| Unix Epoch (Seconds) | POSIX time (10 digits) | 1792761600 | JWT exp claims, Linux kernel and file timestamps | |
| Unix Epoch (Milliseconds) | JavaScript Date.now() (13 digits) | 1792761600000 | Node.js and browser timestamps | |
| ISO 8601 / RFC 3339 | YYYY-MM-DDTHH:mm:ss.sssZ | 2026-10-23T13:20:00.000Z | REST APIs and JSON payloads | |
| IANA Timezone Identifier | Area/Location format | Europe/Amsterdam | User profiles and recurring schedules (DST-aware) |
2. ISO 8601 vs RFC 3339: Parsing and String Formats
// CORRECT: Produces unambiguous ISO 8601 / RFC 3339 UTC string
const now = new Date();
const apiString = now.toISOString(); // "2026-10-23T13:16:13.123Z"
// CAUTION with JavaScript Date Parsing:
// In older engines, "2026-10-23" was parsed as UTC, while "2026/10/23" was parsed as local time!
// Always validate against full ISO strings with timezone suffixes:
function parseSafeDate(isoString: string): Date {
if (!/Z|[+-]\d{2}:?\d{2}$/.test(isoString)) {
throw new Error(`Unsafe timestamp without timezone offset: ${isoString}`);
}
const d = new Date(isoString);
if (isNaN(d.getTime())) throw new Error(`Invalid date string: ${isoString}`);
return d;
}A date string without a trailing "Z" or "+/-HH:mm" offset defaults to unpredictable host-local interpretations.
3. Database Storage: PostgreSQL timestamptz vs MySQL DATETIME
-- POSTGRESQL:
-- Always use TIMESTAMPTZ (internally stored as pure 8-byte UTC microsecond integer!)
CREATE TABLE audit_logs (
id BIGSERIAL PRIMARY KEY,
event_name VARCHAR(100) NOT NULL,
occurred_at TIMESTAMPTZ NOT NULL DEFAULT clock_timestamp()
);
-- Note: clock_timestamp() gives the actual current time, unlike NOW() which freezes at transaction start!
-- MYSQL:
-- TIMESTAMP stores UTC (limited to Year 2038 problem!)
-- DATETIME stores raw literal numbers without timezone awareness (up to Year 9999).
-- In MySQL 8+, configure default time_zone = "+00:00" and use DATETIME.TIMESTAMPTZ in Postgres does NOT store the timezone name; it converts input to UTC and outputs in the client session timezone.
4. Daylight Saving Time (DST) & The Missing Hour Trap
// ANTI-PATTERN: Naive 24-hour addition breaks across DST switch:
const oneDayMs = 24 * 60 * 60 * 1000;
const tomorrow = new Date(today.getTime() + oneDayMs); // Might be 23 or 25 hours local!
// PRODUCTION PATTERN: Use Intl or modern Temporal API / date-fns / luxon
// In standard JavaScript, adjust calendar day explicitly:
const nextDay = new Date(today);
nextDay.setDate(today.getDate() + 1); // The browser runtime adjusts for DST automatically!
// Display in target user timezone accurately:
const formatted = new Intl.DateTimeFormat("nl-NL", {
timeZone: "Europe/Amsterdam",
dateStyle: "full",
timeStyle: "long"
}).format(today);Intl.DateTimeFormat leverages the operating system and browser IANA tzdb for exact historical and future DST offsets.
5. Clock Skew, NTP & Monotonic Timers
// ANTI-PATTERN: Wall-clock can jump backwards during NTP sync
const start = Date.now();
await doWork();
const duration = Date.now() - start; // Can occasionally be negative!
// PRODUCTION PATTERN: Monotonic clock (strictly increments, immune to clock drift)
const startMonotonic = performance.now(); // High-resolution milliseconds
await doWork();
const elapsedMs = performance.now() - startMonotonic;
// In Linux CLI: check NTP sync and server clock drift
timedatectl statusperformance.now() (Node.js and browsers) and CLOCK_MONOTONIC in C/Go/Rust guarantee strictly positive elapsed time.
Related Free Online Tools
Put these techniques into practice immediately with our privacy-first browser utilities:
Convert Unix epoch timestamps to UTC, ISO 8601, and local dates, or human dates to Unix seconds.
Convert time and dates across worldwide IANA time zones with daylight saving time adjustments.
Calculate exact durations in calendar days, business work days, and months between dates.