jsondatesjavascriptapi

Dates in JSON: ISO 8601, Timestamps & Timezone Pitfalls

·9 min read·JSON Concepts

JSON has no date type

This trips up almost everyone: JSON's six types are string, number, boolean, null, object, and array. There is no date type. Every date in JSON is really a string or a number, and how you choose to represent it determines whether your API is a pleasure or a source of subtle, timezone-shaped bugs.

json
{
  "created_at": "2026-06-11T14:30:00Z",
  "expires_unix": 1781100600
}

Option 1: ISO 8601 strings (recommended)

The best default is an ISO 8601 string in UTC:

json
{ "timestamp": "2026-06-11T14:30:00Z" }

The trailing Z means UTC ("Zulu" time). ISO 8601 is human-readable, sorts correctly as a string, is unambiguous, and is natively parsed by virtually every language:

javascript
const d = new Date("2026-06-11T14:30:00Z"); // works everywhere

Always store and transmit in UTC, and convert to the user's local time only at display time.

Option 2: Unix timestamps (numbers)

A Unix timestamp is the number of seconds (or milliseconds) since 1970-01-01 UTC:

json
{ "timestamp": 1781100600 }

Timestamps are compact and trivially comparable, which is why they are common in tokens (like JWT exp) and logs. The downsides: they are not human-readable, and there is constant seconds-vs-milliseconds confusion — JavaScript uses milliseconds, Unix tooling uses seconds.

javascript
new Date(1781100600 * 1000); // multiply seconds by 1000 for JS

ISO 8601 vs Unix timestamp

AspectISO 8601 stringUnix timestamp
Human-readableYesNo
Sorts as-isYes (lexicographic)Yes (numeric)
AmbiguityNone (with Z)Seconds vs milliseconds
Best forAPIs, config, documentsTokens, logs, math

For most APIs, prefer ISO 8601 strings. Reach for timestamps when compactness or arithmetic matters.

How JSON.stringify handles dates

In JavaScript, JSON.stringify converts a Date to an ISO 8601 string automatically — but JSON.parse does not turn it back into a Date:

javascript
const obj = { at: new Date("2026-06-11T14:30:00Z") };
const json = JSON.stringify(obj);
// '{"at":"2026-06-11T14:30:00.000Z"}'

const back = JSON.parse(json);
typeof back.at; // "string" — NOT a Date!

You must re-hydrate dates yourself after parsing, for example with a reviver function or by converting known fields with new Date(...).

The classic timezone bug

The most common mistake is sending a date without timezone information:

json
{ "date": "2026-06-11 14:30:00" }

Is that 14:30 in London, New York, or Tokyo? Different parsers assume different things — some local, some UTC — so the same string becomes a different instant on different machines. Always include the offset or Z. For date-only values (birthdays, due dates) where time genuinely does not matter, use a plain YYYY-MM-DD string and treat it as a calendar date, not an instant.

Best practices

  • Store and send UTC, formatted as ISO 8601 with Z.
  • Never send a naive datetime with no timezone.
  • Convert to local time only for display.
  • Re-hydrate dates after `JSON.parse` — they come back as strings.
  • For date-only data, use YYYY-MM-DD and document that it is a calendar date.

Frequently asked questions

ISO 8601 in UTC, like "2026-06-11T14:30:00Z". It is unambiguous, human-readable, sorts correctly, and parses natively in every major language.

You almost certainly sent a datetime without timezone info, and a parser assumed local time instead of UTC (or vice versa). Always include Z or an explicit offset.

No. JSON.stringify serializes a Date to a string, but JSON.parse returns it as a string. Convert known date fields with new Date(...) or a reviver after parsing.

Try Unix Timestamp Converter

Convert between Unix timestamps and human-readable dates.