Time Utilities

ISO 8601 Parser

Parse or format ISO 8601 date-time strings.

What is ISO 8601?

ISO 8601 is the international standard for date and time representation. This tool converts between ISO 8601 format and other date formats.

ISO 8601 Formats

  • Date: 2024-01-15
  • Date+Time: 2024-01-15T14:30:00
  • With Timezone: 2024-01-15T14:30:00+08:00
  • UTC: 2024-01-15T06:30:00Z

Use Cases

  • API date formatting
  • Database timestamps
  • International date exchange

This tool runs a date-time string through the browser Date parser and prints four views of the same instant: canonical ISO 8601 in UTC, a locale-formatted local reading, milliseconds since the epoch, and whole seconds. Use it to check what an API timestamp actually denotes, or to normalise a hand-typed date before pasting it into code.

The parser is ECMAScript, not an ISO 8601 reader

Both modes call new Date(string), so what is accepted is the ECMAScript Date Time String Format plus whatever the engine adds. Two consequences matter. A date-only value such as 2025-01-30 is read as UTC midnight, while an offset-less date-time such as 2025-01-30T12:00:00 is read as local noon, so on a UTC+8 machine the same digits describe instants 8 hours apart. V8 also takes forms the standard omits: a space instead of T, an offset written +0800 without a colon, and 24:00:00 rolling into the next day. It normalises impossible dates instead of rejecting them, so 2025-02-30 quietly becomes 2025-03-02. What does fail are the ISO basics browsers never implemented: 20250130, week dates such as 2025-W05-4, and comma decimal fractions.

Why the ISO row stops matching your input

The ISO row is toISOString(): always UTC, always three fractional digits, offset folded away. Feed it 2025-01-30T20:00:00+08:00 and the +08:00 is subtracted, leaving 2025-01-30T12:00:00.000Z. Fraction digits past the third are cut, not rounded, because a Date holds integer milliseconds. Years outside 0000 to 9999 switch to the six-digit expanded form with an explicit sign, as in +012345-01-01T00:00:00.000Z. The local row is rendered with dateStyle: full, timeStyle: medium and hour12: false in zh-CN or en-US, so field order and weekday wording follow the language, and en-US prints midnight as 24:00:00 where zh-CN prints 00:00:00. The seconds row is Math.floor(ms / 1000), so pre-epoch instants round down: -500 ms reads as -1.

Format mode is a display filter, not a validator

Switching to format mode tightens nothing: the same new Date(s) call runs and three of the four rows are simply hidden. That is why an RFC 2822 mail header (Thu, 30 Jan 2025 12:00:00 GMT), a slash form such as 2025/01/30, or even a bare 2025 each produce a clean ISO string, and the output says nothing about whether the input was ISO 8601. The field label is not literal either: format mode is captioned as taking a datetime or timestamp, yet a bare epoch such as 1700000000 errors in both modes, since no engine parses a lone integer as a date. To validate a format rather than convert it, compare the input against the ISO grammar yourself, and send numeric input to a timestamp converter.

Worked example

Normalising an offset-carrying log timestamp

Input
2025-01-30T20:00:00+08:00
Output
2025-01-30T12:00:00.000Z

The offset is subtracted rather than echoed back, so the ISO row is the UTC instant and always ends in Z. The millisecond row shows 1738238400000 and the second row 1738238400. The local row returns 20:00 only when the browser sits at UTC+8; on another machine it prints that zone’s clock face, which is why the ISO row is the one worth copying.

Limits and pitfalls

  • There is no timezone selector: the local row follows the browser zone, so one input reads differently on a UTC+8 and a UTC-5 machine, while only the ISO and timestamp rows stay fixed.
  • A date-only value is treated as UTC but the same date with a time and no offset is treated as local, so 2025-01-30 and 2025-01-30T00:00:00 are different instants.
  • Impossible calendar dates are normalised rather than rejected, 2025-02-30 resolving to 2025-03-02, and one generic message covers every malformed input.
  • Precision stops at milliseconds: digits beyond the third in the fraction are truncated, and toISOString() never emits more than three.
  • A bare epoch such as 1700000000 fails in both modes, despite the format-mode caption mentioning timestamps.

Related tools

When one instant is the whole question, and when the job belongs somewhere else

What you are holdingThis pageWhere to go instead
The value is digits — a Unix second or millisecond countBoth modes pass the text to the Date parser, and a lone integer is not a date string any engine accepts, so 1700000000 ends on the error line rather than in the four rows.Send numbers to the timestamp converter, which has a seconds/milliseconds switch and reads them directly, or expand one in a shell with date -u -d @1700000000 +%FT%TZ. Come back here when the input is text such as 2025-01-30T20:00:00+08:00 and the question is what instant it names.
The answer has to be shown in a named zone, not in the zone my laptop happens to reportThe local row is built from the browser zone at render time: no dropdown, no parameter, no URL switch. A colleague elsewhere opens the same input and sees their own clock face, while the ISO row stays byte-identical for both of you.For "what is this in Tokyo, and in New York" use the timezone converter, or format in code with Intl.DateTimeFormat("en-GB", { timeZone: "Asia/Tokyo", timeStyle: "long" }). Switch as soon as the zone must be stated rather than inherited from the machine doing the reading.
The goal is to reject bad input, not to convert itNothing on the page ever claims a string is conformant: it answers whether an instant came out, and a successful row only means the engine found a date inside the text. A value that renders has proved nothing about the notation you typed, so this is a converter you read, not a check you pass.Gate the field with a real validator: JSON Schema format: "date-time", a regex anchored on Z|[+-]HH:MM, or Python 3.11 datetime.fromisoformat(), which now takes a trailing Z. Choose that the moment bad input has to produce an error message instead of a value.
The question is arithmetic — "30 days later", "how many days between these two"All four rows describe a single moment. There is no add, no subtract and no difference field, so a calendar question has to be worked out by hand from the timestamp row.Hand it to the date calculator, which counts calendar days rather than 24-hour blocks, or do it in code with Temporal.PlainDate or differenceInCalendarDays. Use this page first only to pin the two endpoint instants you will subtract.
The parse runs in shipped code and you need a fixture to test againstPaste one real production string, read the offset folding off the panel, and copy the ISO and millisecond rows: those two numbers are the expected values of a unit test, no manual arithmetic needed.Parse in production with something that reads the offset deliberately, Temporal.ZonedDateTime.from or date-fns parseISO, not the engine-dependent new Date(string). Pick the library as soon as the string is user-supplied or comes from a vendor feed; keep this page as the oracle you check it against.
A hundred timestamps inside a log file, not one in a textareaOne textarea, one result panel, one button that fills the box with the current time. The hundredth line is the hundredth paste, and there is no list mode to fall back on.In a shell, jq maps todateiso8601 across an array of epoch values and GNU date -u -d @1700000000 +%FT%TZ loops in a single line. Stay here for the one value you are about to reason about, not for a batch.

What the offset written in the string does to the ISO row

Offset in the stringZones keeping that standard offsetISO row for 2025-01-30T20:00:00 with it
Z or +00:00UTC, Reykjavik, Accra2025-01-30T20:00:00.000Z
+08:00China, Singapore, Perth2025-01-30T12:00:00.000Z
+09:00Japan, South Korea2025-01-30T11:00:00.000Z
+05:30India, Sri Lanka2025-01-30T14:30:00.000Z
+05:45Nepal2025-01-30T14:15:00.000Z
+08:45Eucla, Western Australia2025-01-30T11:15:00.000Z
+13:00Tonga2025-01-30T07:00:00.000Z
-03:00Argentina, Uruguay, Sao Paulo2025-01-30T23:00:00.000Z
-05:00New York in winter, Bogota, Lima2025-01-31T01:00:00.000Z
-12:00Baker Island, Etc/GMT+122025-01-31T08:00:00.000Z

Each row subtracts the offset from the wall clock and keeps the date rollover: the last two rows land on 31 January, which is where an off-by-one log date usually starts. The middle column names the standard offset, not a year-round constant — daylight saving swaps the literal a zone writes (New York is -05:00 in January and -04:00 in July). Fractional seconds survive the fold, so 2025-01-30T20:00:00.250+08:00 ends in .250Z. The table applies only while an offset is present in the string.

Frequently Asked Questions

Why is the ISO 8601 format shaped this way?
ISO 8601 puts large units first: 2026-08-30T14:30:00+08:00. Two payoffs: plain string sorting equals chronological sorting (logs and filenames order correctly), and the format is unambiguous — which made it the standard for APIs, logs, and databases.
What does the trailing Z mean?
Z is Zulu time, UTC+0: 2026-08-30T06:00:00Z and 2026-08-30T14:00:00+08:00 are the same instant. Store and transmit in UTC (Z), convert to local time only for display — the golden rule for avoiding timezone chaos.
Why does JavaScript’s toISOString() throw?
Usually the string is not ISO format (slash forms like "2026/08/30 14:00" parse inconsistently across engines), or the Date is Invalid before serialization. Validate the format with this tool first, then trace the code.

Content reviewed: