Skip to tool
ecech.
🕑 Date, Time & Timezone

ISO 8601 Builder and Parser, Including the Part That Breaks Dates by One Day

Build a valid ISO 8601 string, or paste one and see exactly how it is interpreted — including why a date without a time lands in a different day.

As UTC

In your timezone

Epoch milliseconds

Which part is which


Or build one

Result


Advertisement

How the calculation works

The same day, typed two ways. Measured in UTC+8. "2026-08-10" no time, so the spec says UTC UTC   2026-08-10 00:00 local 2026-08-10 08:00 you asked for midnight, you got 8 a.m. "2026-08-10T00:00" has a time and no offset, so local UTC   2026-08-09 16:00 local 2026-08-10 00:00 midnight, but the UTC date is the 9th Eight hours apart, and on different calendar days in UTC. Both strings are valid ISO 8601. Adding "T00:00" is not a cosmetic change.

How to Use This Tool

Paste a string to see how it is really interpreted, or use the builder to produce one. Everything is computed in your browser, using your machine's timezone for the local readings.

The rule almost nobody knows

ISO 8601 and the JavaScript specification agree on something surprising:

  • A date-only string like 2026-08-10 is treated as UTC.
  • A date-time string with no offset like 2026-08-10T00:00 is treated as local time.

So adding a time to a string changes which timezone it means. In UTC+8 the two examples above are eight hours apart and sit on different calendar dates when expressed in UTC. This is the source of the classic off-by-one-day bug: a date picker hands you 2026-08-10, you store it, and a user in the Americas sees the 9th.

The fix is to be explicit. Always include an offsetZ if you mean UTC, or +08:00 if you mean a real place. A string with an offset means exactly one instant, and no reader has to guess.

Silent rollovers, which are worse than errors

An impossible date does not raise an error in JavaScript. It quietly moves:

  • 2026-02-30 becomes 2 March 2026
  • 2026-02-29 becomes 1 March 2026, because 2026 is not a leap year
  • 2026-13-01 is rejected, because the month is out of range rather than the day

This tool flags a rollover explicitly, because a value that changes itself and reports success is harder to find than one that throws.

What JavaScript does with dates that do not exist 2026-02-30 2026-03-02 no error, no warning 2026-02-29 2026-03-01 2026 is not a leap year 2026-13-01 Invalid Date month out of range, so it is caught The two that fail loudly are safer than the two that do not.
A bad day rolls forward. A bad month is rejected. The inconsistency is the hazard.

Forms that are valid ISO 8601 but not valid JavaScript

ISO 8601 is a large standard and browsers implement a subset. Verified in Node:

  • Week dates2026-W33-1 is legal ISO 8601 and returns Invalid Date.
  • Basic format20260810T123456Z, the separator-free form, is also rejected. Only the extended form with hyphens and colons works.
  • Leap seconds2016-12-31T23:59:60Z is a real second that existed, and JavaScript has no representation for it. Invalid Date.
  • A space instead of T2026-08-10 00:00 is not in the spec, yet every major browser accepts it. Convenient, and not portable to other parsers.

Fractional seconds are truncated, not rounded

JavaScript dates carry millisecond precision. Give one six digits and the extra three are discarded: 2026-08-10T12:34:56.789123Z becomes ...56.789Z. If you are reading microsecond timestamps out of a database, the sub-millisecond part is gone the moment it enters a Date, silently.

Which form to store

For an instant — when something happened — store UTC with an explicit Z. It is unambiguous, sorts correctly as text, and survives being read anywhere.

For a calendar date — a birthday, a due date, a public holiday — store the plain date and no time at all. Attaching midnight in some timezone is what causes it to drift by a day. A birthday is not an instant.

Advertisement

Frequently Asked Questions

Why does new Date('2026-08-10') give a different time from new Date('2026-08-10T00:00')?
Because the specification treats a date-only string as UTC and a date-time string without an offset as local time. In UTC+8 the first is 8 a.m. local and the second is midnight local, and expressed in UTC they fall on different calendar days. Adding a time to the string changes which timezone it means.
What is the correct ISO 8601 format?
For an instant, the extended form with an explicit offset: 2026-08-10T12:34:56Z for UTC, or 2026-08-10T12:34:56+08:00 for a specific offset. Include the T, use hyphens and colons, and never omit the offset — a string without one has to be guessed at by whoever reads it.
Why is my date one day off?
Almost always because a date-only value was parsed as UTC midnight and then displayed in a timezone behind UTC, which puts it on the previous day. Storing a plain calendar date without any time, and formatting it without converting timezones, avoids this entirely.
Does JavaScript reject impossible dates?
Not consistently. An out-of-range day rolls forward silently — 2026-02-30 becomes 2 March and 2026-02-29 becomes 1 March, both without any error. An out-of-range month is rejected as Invalid Date. This tool flags the rollover, because a value that changes itself and reports success is harder to catch than one that throws.
Are ISO week dates like 2026-W33-1 supported?
They are valid ISO 8601 but JavaScript returns Invalid Date for them, as it does for the separator-free basic format such as 20260810T123456Z. Browsers implement a subset of the standard, so a string being legal ISO does not mean a browser will parse it.
What happens to microsecond precision?
It is truncated. JavaScript dates hold milliseconds, so 2026-08-10T12:34:56.789123Z becomes .789 and the remaining digits are discarded with no warning. If sub-millisecond precision matters, keep the original string or an integer count and do not put it through a Date.
Should I store dates in UTC or local time?
Store an instant as UTC with a Z. Store a calendar date — a birthday, a deadline, a holiday — as a plain date with no time, because attaching midnight in some timezone is exactly what makes it drift. The two cases are different and one storage format cannot serve both well.

Related tools in Date, Time & Timezone

Browse all Date, Time & Timezone tools