JSON has no date or time type. Every API has to choose how to encode timestamps, and inconsistent choices cause some of the most frustrating bugs: events that shift by a few hours, birthdays that move by a day, and timestamps that are off by a factor of 1,000. This guide recommends a format and explains the pitfalls.
The most widely supported convention is an ISO 8601 string, specifically the RFC 3339 profile:
json{ "createdAt": "2026-03-15T14:30:00Z", "updatedAt": "2026-03-15T09:30:00.250-05:00" }
T separates the date from the time.Z means UTC. An offset like -05:00 means that many hours behind UTC..250) are optional.ISO strings are readable in logs, sort correctly as plain strings (when they share the same offset), and are parsed natively by almost every language:
javascriptnew Date("2026-03-15T14:30:00Z").toISOString();
pythonfrom datetime import datetime datetime.fromisoformat("2026-03-15T14:30:00+00:00")
Python 3.11+ also accepts the Z suffix in fromisoformat. Older versions need +00:00.
For moments in time, such as when an order was placed or when a token expires, send UTC with Z. Convert to the user's local time zone only when displaying. This avoids ambiguity around daylight saving time and makes server logs comparable.
A birthday, a due date or a holiday isn't an instant. It's a calendar date. Send it as a plain date:
json{ "birthDate": "1990-07-04" }
Don't send "1990-07-04T00:00:00Z". Midnight UTC is still July 3rd in the Americas, so a client that converts to local time will show the wrong day. In JavaScript, new Date("1990-07-04") is interpreted as midnight UTC, which causes exactly this bug. Treat date-only values as strings, or use a date-only type such as Temporal.PlainDate where it's available.
For future events tied to a place, like a meeting at 9:00 in New York, an offset isn't enough. Offsets change with daylight saving time, and governments occasionally change the rules. Send the local time plus an IANA time zone name:
json{ "startsAt": "2026-11-02T09:00:00", "timeZone": "America/New_York" }
Some APIs send seconds or milliseconds since 1970-01-01 UTC:
json{ "created": 1773585000, "expiresAtMs": 1773585000000 }
They're compact and easy to compare, but:
Date.now() uses milliseconds, while Unix tools and JWT exp claims use seconds. Mixing them up gives dates in 1970 or in the year 58,000. Put the unit in the field name.If you use them, be consistent and document the unit.
"03/04/2026": is that March 4th or April 3rd? It depends on the reader's locale."2026-03-15 14:30:00" with no offset: which time zone?"/Date(1773585000000)/": an old .NET convention that other clients don't understand."Sun Mar 15 2026": not reliably parseable.In JSON Schema, use "format": "date-time" for timestamps and "format": "date" for calendar dates, and enable format validation in your validator; see JSON Schema basics.
| Value | Recommended JSON |
|---|---|
| Moment in time | "2026-03-15T14:30:00Z" |
| Calendar date | "2026-03-15" |
| Future local event | "2026-11-02T09:00:00" plus "America/New_York" |
| Duration | ISO 8601 duration "PT1H30M", or seconds as a number with the unit in the key name |
See also JSON data types and API JSON best practices.