All posts

Unix timestamp vs ISO 8601: which to use

Unix timestamp or ISO 8601 for your API? Compare readability, sorting, offsets and precision, see one instant in both formats, and learn where RFC 3339 fits.

By Last reviewed 5 min readISO 8601Unix timeAPI design

Every API that carries a time has to choose a representation. The two common choices are a Unix timestamp, a count of seconds or milliseconds since 1970-01-01T00:00:00Z, and an ISO 8601 string such as 2026-10-25T09:30:00Z. Both can describe an instant exactly. They differ in what else they carry, how they sort, and how easy they are to misread.

If you have a value in one format and need the other, convert between Unix time and ISO 8601 in your browser. The converter works in both directions and lists ISO 8601, Unix seconds and Unix milliseconds for UTC, your local zone and one extra zone you choose.

One instant, several spellings

Here is 25 October 2026 at 09:30 UTC in the forms you are likely to meet:

1792920600                   Unix seconds
1792920600000                Unix milliseconds
2026-10-25T09:30:00Z         ISO 8601 and RFC 3339, UTC
2026-10-25T09:30:00.000Z     JavaScript toISOString()
2026-10-25T09:30:00+00:00    Python isoformat() on a UTC datetime
2026-10-25T15:00:00+05:30    the same instant in Asia/Kolkata
2026-10-25T05:30:00-04:00    the same instant in America/New_York
2026-10-25T10:30:00+01:00    the same instant in Europe/Berlin

All eight identify one moment. Look at the Berlin line: Europe left summer time at 01:00 UTC that morning, so a few hours earlier the same city was at +02:00. An offset is a fact about one instant, not about a place.

What each format carries

A Unix timestamp is just a number. It says nothing about its unit or about where the event happened, and it ignores leap seconds: every Unix day is exactly 86,400 seconds. That minimalism is the point. The value is identical on every machine, and subtracting two timestamps gives a duration with no parsing at all.

An ISO 8601 string carries a calendar date, a time of day, an optional fraction and, if the producer includes one, an offset from UTC. With an offset, a reader recovers both the instant and the wall-clock time the producer saw (15:00 in Kolkata). It still doesn't carry the time zone: +01:00 could be Berlin in winter, Lagos all year, or several other places.

Side by side

Unix timestampISO 8601 string
Readable in a logNo, needs convertingYes
Self-describingNo, seconds or milliseconds is a guessYes
Keeps local timeNoYes, when an offset is included
Size in JSON10 or 13 digits20 to 29 characters, plus quotes
SortingNumeric sort is always rightString sort is right only with one fixed format and offset
ArithmeticSubtract directlyParse first
PrecisionFixed by the unitAny number of fractional digits

The sorting trap

ISO 8601 is often called "sortable as a string". That holds only when every value uses the same offset and the same precision. Mix them and string order can be exactly backwards:

["2026-10-25T09:30:00.5Z",
 "2026-10-25T09:30:00Z",
 "2026-10-25T10:00:00+01:00"].sort()
// returns the same order, which is wrong

// the same values as Unix milliseconds:
// 1792920600500, 1792920600000, 1792918800000
// chronological order is the reverse

The fraction breaks the first pair, because . sorts before Z. The offset breaks the last one, because 10:00+01:00 is 09:00 UTC. If you sort or index ISO strings, normalise them first: always UTC, always Z, always the same number of fractional digits. Better still, sort on a native timestamp column in the database.

Where RFC 3339 fits

ISO 8601 is a large standard. It allows week dates (2026-W43-7), ordinal dates (2026-298), the compact basic format (20261025T093000Z), reduced precision (2026-10), local times with no offset, and durations. Few parsers accept all of it.

RFC 3339 is a profile of ISO 8601 for internet protocols. It requires a full date, a full time and an offset (Z or ±hh:mm), and leaves the rest out. It also adds a few details of its own: lowercase t and z are allowed, applications may use a space instead of T for readability, -00:00 means "the UTC time is known but the local offset isn't", and a seconds value of 60 is allowed for leap seconds. When JSON Schema or OpenAPI says date-time, it means RFC 3339.

In practice, emit strings like 2026-10-25T09:30:00Z, which are valid under both standards, and be a little more lenient when you parse values typed by people.

Which one to choose

  • Public JSON APIs and logs: RFC 3339 in UTC. People can read it in a log line, it's unambiguous, and every language can parse it.
  • When the local time matters (a receipt, a check-in time): RFC 3339 with the producer's offset, plus the IANA zone in a separate field if you will compute future times from it.
  • Specs that require numbers: the JWT exp, iat and nbf claims are seconds since the epoch by definition (RFC 7519). Follow the spec; the guide to JWT exp and iat claims covers the details.
  • High-volume internal data such as metrics and events: integer epoch values are smaller and cheaper to compare. Put the unit in the field name (ts_ms).
  • Dates without a time (birthdays, due dates): a plain YYYY-MM-DD. Converting them to a timestamp invents a time and a zone, and they shift by a day when displayed elsewhere.
  • Database columns: neither. Use the native type, such as timestamptz in PostgreSQL, and convert at the API boundary.

Converting in code

// JavaScript
new Date(1792920600 * 1000).toISOString()               // "2026-10-25T09:30:00.000Z"
Math.floor(Date.parse("2026-10-25T09:30:00Z") / 1000)   // 1792920600

# Python 3.11+
from datetime import datetime, timezone
datetime.fromtimestamp(1792920600, tz=timezone.utc).isoformat()
# '2026-10-25T09:30:00+00:00'
int(datetime.fromisoformat("2026-10-25T09:30:00Z").timestamp())
# 1792920600

# Shell
date -u -d @1792920600 +%Y-%m-%dT%H:%M:%SZ   # GNU date (Linux)
date -u -r 1792920600 +%Y-%m-%dT%H:%M:%SZ    # BSD date (macOS)

Three details trip people up in Python. It writes +00:00 rather than Z; both are valid, but a string comparison against Z values fails. fromisoformat accepts the Z suffix only from Python 3.11; older versions raise ValueError. And without tz=timezone.utc, fromtimestamp returns the machine's local time, which is the start of many of the bugs in debugging timezone errors in APIs.

Next steps

Paste either format into the epoch converter to check a value before it goes into a fixture or a bug report. If you settle on Unix time, read seconds vs milliseconds so the unit never becomes a guess.

Paste anything. devprix picks the tool.

Press ⌘K on any page, paste JSON, a JWT, a timestamp or a URL, and the right tool opens with your data filled in.