Working With Unix Timestamps Without Getting Burned

Updated 2026-03-27 · By SPUNK13 · Back to spunk.bet

Timestamp bugs are quiet. Nothing crashes; the numbers are just wrong, usually by an hour, and usually only for some users. Almost all of them come from a handful of avoidable mistakes.

Seconds or milliseconds: check before you convert

Unix time counts seconds since 1970-01-01T00:00:00Z. JavaScript's Date.now() returns milliseconds. Mixing them produces dates in 1970 or in the year 55,000, which at least is obvious. The quick test: a current timestamp in seconds is ten digits (around 1.7 billion), in milliseconds it is thirteen. Java, JavaScript and many JSON APIs use milliseconds; Unix tooling, Postgres extract(epoch …) and most Python and Go code use seconds. Convert explicitly at the boundary and name the variable so the unit is visible: expires_at_ms, not expires.

Store UTC, convert at the edge

The rule that prevents most problems: store instants in UTC, do the conversion to local time only when displaying to a user, and keep the user's timezone as an IANA identifier (Europe/Rome, America/Chicago) rather than a UTC offset. Offsets change twice a year; identifiers do not. In Postgres, use timestamptz rather than timestamp — the former stores an absolute instant, the latter stores wall-clock digits with no meaning attached, and mixing them is a classic source of one-hour errors.

The daylight-saving edge cases that actually occur

When clocks spring forward, a local time like 02:30 does not exist on that date, and a naive parse either throws or silently shifts. When clocks fall back, 01:30 happens twice, so a local timestamp is ambiguous and cannot be converted to an instant without extra information. Recurring events make it worse: "every day at 09:00 local" is not a fixed interval, because one day a year it is 23 hours after the previous one and another day it is 25. Store the rule (time plus timezone), compute the next occurrence at run time, and never precompute a year of UTC instants from a local schedule.

2038, and why it still matters

A signed 32-bit Unix timestamp overflows on 19 January 2038. Modern 64-bit systems and languages are fine, but 32-bit embedded devices, old database columns declared as INT, and file formats with a fixed 32-bit field are not. This is already live for anything computing dates more than a decade out — mortgage terms, long-dated contracts, certificate expiry. Check integer column widths in any schema that stores epoch seconds as an integer, and prefer a native timestamp type.

Formats worth standardising on

ISO 8601 / RFC 3339 for anything a human or another system will read: 2026-08-09T14:32:00Z. It sorts lexicographically, it is unambiguous, and every language parses it. Avoid locale-dependent formats in storage and logs entirely — 03/04/2026 is two different days depending on the reader's country. For durations, ISO 8601 has a form (PT15M) but an integer number of seconds in a clearly named field is usually easier for everyone.

Practical conversions

date -u -d @1786000000 +%Y-%m-%dT%H:%M:%SZ   # GNU date, epoch to ISO
date -u +%s                                   # now, in seconds
python3 -c "import datetime;print(datetime.datetime.fromtimestamp(1786000000,datetime.timezone.utc))"
psql> SELECT to_timestamp(1786000000) AT TIME ZONE 'UTC';

Rules that prevent the rest

Want More?

Explore our complete collection of free tools and resources.

Visit spunk.bet
400+ Free Tools Free Casino Meme Generator Birth Charts Scam Database BPM Calculator