JSON Date Format: ISO 8601, Timezones, and the Right Way to Store Dates

JSON has six value types — string, number, boolean, null, object, and array — and none of them is a date. This is by design. JSON's authors deliberately kept the format minimal, and because every programming language stores dates differently, no single representation could be universal.

Try it directly in your browser:

Convert timestamps →

So dates in JSON are always one of two things: a string in a chosen format or a number representing a timestamp. Picking the wrong one — or applying it inconsistently — is one of the most common sources of subtle bugs in API integrations.

The short answer: use ISO 8601

If you remember nothing else from this article: store dates as ISO 8601 strings in UTC.

{
  "createdAt": "2026-05-16T14:30:00.000Z",
  "birthday": "1995-06-22",
  "expiresAt": "2026-12-31T23:59:59Z"
}

ISO 8601 is the international standard for representing dates and times. It is unambiguous, sortable as a plain string, parseable by every modern language, and human-readable. It is also what JavaScript's JSON.stringify() produces by default when you pass it a Date.

The ISO 8601 format in detail

The full datetime form looks like this:

2026-05-16T14:30:00.123Z
└─date──┘ └─time────┘ └timezone
PartMeaning
2026-05-16Date in YYYY-MM-DD
TSeparator between date and time
14:30:00Time in 24-hour HH:mm:ss
.123Optional milliseconds
ZUTC (Zulu time). Equivalent to +00:00

If you need a different timezone, replace Z with an offset:

2026-05-16T14:30:00+05:30   // India Standard Time
2026-05-16T14:30:00-08:00   // Pacific Standard Time
2026-05-16T14:30:00         // Naive — no timezone info (avoid)

Always include the timezone. A datetime without one is a date that means different moments in time depending on who reads it.

How JavaScript handles dates in JSON

JavaScript Dates serialize cleanly to ISO 8601 strings. Date.prototype.toJSON() calls toISOString() internally, and JSON.stringify() automatically uses toJSON when it encounters a Date:

const event = {
  name: "Launch day",
  scheduled: new Date("2026-05-16T14:30:00Z")
};

JSON.stringify(event);
// '{"name":"Launch day","scheduled":"2026-05-16T14:30:00.000Z"}'

Going the other way is not symmetrical. JSON.parse() returns the date as a string — it does NOT rehydrate it into a Date object:

const data = JSON.parse('{"scheduled":"2026-05-16T14:30:00.000Z"}');
console.log(typeof data.scheduled);  // "string"
console.log(data.scheduled instanceof Date);  // false

If you want a real Date, wrap it explicitly or use a reviver function:

// Option 1 — wrap on read
const event = JSON.parse(jsonString);
event.scheduled = new Date(event.scheduled);

// Option 2 — JSON.parse reviver
const isoPattern = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?(Z|[+-]\d{2}:\d{2})$/;
const event = JSON.parse(jsonString, (key, value) => {
  if (typeof value === 'string' && isoPattern.test(value)) {
    return new Date(value);
  }
  return value;
});

The Unix timestamp alternative — and its biggest pitfall

If your API is performance-sensitive or works with time-series data, you may prefer Unix timestamps (seconds or milliseconds since 1970-01-01 UTC):

{
  "createdAt": 1747405800
}

The pitfall: JavaScript uses milliseconds, but most other languages and APIs use seconds. This is the most common subtle date bug in cross-language systems.

// JavaScript
Date.now();  // 1747405800000  (13 digits, milliseconds)

// Python
import time; time.time();  // 1747405800.123  (10 digits, seconds)

// PHP
time();  // 1747405800  (10 digits, seconds)

If a JavaScript backend stores Date.now() as a "timestamp" and a Python consumer reads it as seconds, the result is a date in the year 57,313. If the reverse happens — Python writes seconds and JavaScript reads them with new Date() — the result is sometime in 1970.

The rule: document your timestamp unit explicitly, or sidestep the question entirely by using ISO 8601.

Common mistakes

Locale-dependent formats

// Disaster waiting to happen
{
  "date": "05/16/2026"
}

Is that May 16th (US) or 16th of May (US again, by coincidence)? Try "03/04/2026" — is it March 4th or April 3rd? The format is ambiguous, depends on the reader's locale, and produces silent bugs for 1–12 of the month and crashes on the other 19 days. Use ISO 8601 ("2026-05-16") always.

Forgetting the timezone

// Ambiguous — what timezone is "14:30:00"?
{
  "scheduled": "2026-05-16T14:30:00"
}

JavaScript parses this string as local time. A server in Tokyo and a browser in New York will produce two different moments in time. Always append Z or an explicit offset.

Storing dates as nested objects

// Avoid — verbose and harder to compare
{
  "date": { "year": 2026, "month": 5, "day": 16 }
}

// Prefer
{
  "date": "2026-05-16"
}

Mixing formats within one document

Consistency is more important than which format you pick. An API that returns "2026-05-16T14:30Z" for createdAt and 1747405800 for updatedAt forces every consumer to write conditional parsing. Pick one format per field type and stick with it across the whole API surface.

Choosing between ISO strings and Unix timestamps

Use caseBetter choice
Public API for varied consumersISO 8601 string
Logs and audit trails (human-readable)ISO 8601 string
JWT exp, iat, nbf claimsUnix seconds (the spec requires it)
High-throughput time-series dataUnix milliseconds (explicitly documented)
Calendar dates (no time component)ISO 8601 date YYYY-MM-DD
Birthdays, anniversariesISO 8601 date YYYY-MM-DD
Anything sensitive to timezone (meetings, deadlines)ISO 8601 with explicit timezone

JSON Schema validation for dates

If you use JSON Schema, the format keyword can enforce date strings:

{
  "type": "object",
  "properties": {
    "createdAt": { "type": "string", "format": "date-time" },
    "birthday":  { "type": "string", "format": "date" }
  }
}

Supported formats include date-time (full ISO 8601), date (just YYYY-MM-DD), and time (just HH:mm:ss). Most validators enforce these strictly when configured to do so.

A complete, well-formed example

{
  "user": {
    "id": "user_8s2m1p",
    "name": "Alex Rivera",
    "birthday": "1992-03-14",
    "createdAt": "2024-11-02T09:15:00.000Z",
    "lastLoginAt": "2026-05-16T14:30:00.000Z",
    "subscription": {
      "startedAt": "2026-01-01T00:00:00Z",
      "renewsAt": "2027-01-01T00:00:00Z",
      "cancelledAt": null
    }
  }
}

Every field uses ISO 8601. The birthday omits time because it doesn't have one. Every datetime ends in Z, making it unambiguous UTC. A null value clearly represents "not yet" rather than a missing field.

Need to work with timestamps?

Convert between Unix timestamps and ISO 8601 instantly, or paste JSON containing dates into the formatter to inspect structure.

Timestamp Converter JSON Formatter

Frequently Asked Questions

Why does JSON have no date type?

JSON was designed as a minimal data interchange format with only six value types: string, number, boolean, null, object, and array. Dates were intentionally excluded because there is no single date representation every language and runtime agrees on. The convention that emerged is to store dates as ISO 8601 strings, which every modern language can parse.

Why is my JavaScript Date saved as a different timezone in JSON?

Date.toJSON() always converts to UTC and appends a Z suffix. A Date created in your local timezone will be serialized as the equivalent UTC moment. The information is preserved — when parsed back with new Date(), the local time is reconstructed from the UTC string — but if you compare the raw string, it will look different from the local time you started with.

Should I store dates as Unix timestamps or ISO strings in JSON?

ISO 8601 strings are usually the better choice. They are human-readable, self-describing (you can see the date in a log without converting), and unambiguous. Unix timestamps are smaller and faster to compare but require care: JavaScript uses milliseconds while most other languages and APIs use seconds. A timestamp of 1716000000 is May 18, 2024 if treated as seconds and January 20, 1970 if treated as milliseconds — a 54-year bug waiting to happen.

How do I store a date without a time in JSON?

Use the ISO 8601 date-only format: "YYYY-MM-DD" (for example "2026-05-16"). This is the unambiguous standard for calendar dates without a time component. Avoid locale formats like "05/16/2026" (US) or "16/05/2026" (most of the rest of the world) — they look identical for 1–12 of the month and produce silent bugs the rest of the time.

What is the difference between Z and +00:00 in an ISO 8601 string?

They mean the same thing — UTC, zero offset. Z stands for "Zulu time" (military / aviation shorthand for UTC) and is shorter, so most JSON producers and consumers prefer it. Both are valid per the ISO 8601 spec and every standard parser accepts either.

Ready to convert timestamps?

Open Timestamp Converter →
About the author

Pasindu Ishan is a software developer based in Sri Lanka. He builds developer tools at JSON Dev Tools.