JSON Toolkit

What format for timestamps in JSON

Bottom line: Epoch numbers and ISO strings each have trade-offs, but mixing them is what actually breaks things. Use ISO 8601 with an offset, store UTC, render in the user's time zone.

Beginner~3 min readPublished 2026-10-30Updated 2026-10-30
Choosing a timestamp format for JSON APIs

Bottom line: use ISO 8601 with an explicit offset on the wire, store UTC with a trailing Z, and convert only at display time; if you pick epoch numbers instead, fix seconds versus milliseconds globally and put the unit in the field name.

Why 2026-10-30 14:30:00 is not enough

That string carries no zone. 14:30 in Shanghai and 14:30 in London are eight hours apart, and there is no way to tell which is meant. new Date('2026-10-30 14:30:00') parses in the runtime's local zone, so the server and the browser derive two different instants from one payload — the kind of bug that never reproduces.

A bare 2026-10-30 is not an instant at all. Fine for birthdays and billing periods, wrong for a creation time.

Two workable formats

ISO 8601 strings

{
  "createdAt": "2026-10-30T14:30:00+08:00",
  "updatedAt": "2026-10-30T06:30:00Z"
}

Readable, sortable when the format is consistent, millisecond precision if needed, and self-describing.

Epoch numbers

{ "createdAt": 1793519400 }

Compact and unambiguous, but unreadable, and seconds versus milliseconds is a permanent hazard. If you choose epochs, standardise the unit and put it in the field name: createdAtMs is far safer than createdAt.

Conventions worth fixing early

Item Convention
Wire format ISO 8601 with Z or +08:00
Storage zone UTC, so DST never enters the picture
Precision Milliseconds is enough
Field naming createdAt, updatedAt, expiresAt
Pure dates A separate date field, e.g. birthday: "1990-01-01"
Durations Integer plus a unit, e.g. timeoutMs: 3000

Rendering

new Intl.DateTimeFormat('en-GB', {
  dateStyle: 'medium',
  timeStyle: 'short',
  timeZone: 'Europe/London',
}).format(new Date('2026-10-30T06:30:00Z'));

Specify timeZone explicitly when the display must not follow the device. Relying on the browser default is correct for user-facing times and wrong for reports.

Three recurring bugs

Bare dates parse as UTC midnight. Per the ECMAScript spec, date-only strings are UTC while date-time strings are local. That inconsistency is a reliable source of off-by-one-day bugs.

Database zone disagrees with the serializer. MySQL datetime has no zone; write local, read as UTC, and everything shifts by the offset.

Daylight saving. Storing future local times creates instants that do not exist or occur twice. UTC has no such gaps.

FAQ

How should I choose between ISO 8601 and epoch numbers?

Use ISO 8601 when people read the payload, when you want lexical sort order, or when the value must carry its own zone; the cost is roughly a dozen extra bytes and a parsing library. Use epoch numbers when compactness and trivial cross-language handling matter more, but fix seconds versus milliseconds globally and say which unit the field holds.

Should a bare date and a timestamp share the same field?

No. A bare 2026-10-30 is a calendar day with no time and no zone, while a timestamp field is expected to carry an exact instant. Keep birthdays and billing periods in a separate date field, and use an ISO 8601 At field for creation or update times.

Further reading