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.
{
"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:
{ "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:
const d = new Date("2026-06-11T14:30:00Z"); // works everywhereAlways 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:
{ "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.
new Date(1781100600 * 1000); // multiply seconds by 1000 for JSISO 8601 vs Unix timestamp
| Aspect | ISO 8601 string | Unix timestamp |
|---|---|---|
| Human-readable | Yes | No |
| Sorts as-is | Yes (lexicographic) | Yes (numeric) |
| Ambiguity | None (with Z) | Seconds vs milliseconds |
| Best for | APIs, config, documents | Tokens, 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:
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:
{ "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-DDand document that it is a calendar date.