Date.prototype.toISOString()

The one Date format with a single unambiguous meaning. It is also what toJSON returns, which is why a Date passed to JSON.stringify becomes an ISO string.

Date methodES5 (2009)Live demo
Common call
d.toISOString()
Returns
'2026-03-15T12:00:00.000Z'
Replaces
manual zero-padded formatting
Watch out
THROWS on an invalid Date — the only to* method that does
date.toISOString()
→ string

Demo

Live evaluation
Try:
Inputs
isostringa date string to parse
Output
new Date('2026-03-15T12:00:00Z').toISOString()
'2026-03-15T12:00:00.000Z'

Every valid result has the same shape and ends in Z, which is what makes this format safe to store and compare as text. The second and third cases are the trap worth memorising: a DATE-ONLY string is parsed as UTC midnight, but a date-TIME string without a zone is parsed as LOCAL — so '2026-03-15' and '2026-03-15T00:00' are different instants, and the second one can land on the previous day once converted back to UTC. The last case is the other surprise: an invalid Date makes this method THROW, where toString would have returned 'Invalid Date'.

Common patterns

Store and transmit
The default choice for any persisted date.
await save({createdAt: new Date().toISOString()});
JSON does it for you
toJSON delegates to toISOString.
JSON.stringify({at: new Date()});
Just the date part
The format is fixed, so slicing is safe.
const day = d.toISOString().slice(0, 10);

Examples

1. Full timestamp
new Date('2026-03-15T12:00:00Z').toISOString()
Returns
'2026-03-15T12:00:00.000Z'
2. Date-only is UTC
new Date('2026-03-15').toISOString()
Returns
'2026-03-15T00:00:00.000Z'
3. No zone is local
new Date('2026-03-15T00:00').toISOString()
Returns
the previous day, east of UTC
4. JSON uses it
JSON.stringify({at: new Date('2026-03-15T12:00:00Z')})
Returns
'{"at":"2026-03-15T12:00:00.000Z"}'
5. Invalid throws
new Date('nonsense').toISOString()
Returns
RangeError: Invalid time value
6. But toString does not
new Date('nonsense').toString()
Returns
'Invalid Date'

Pitfalls

1. It throws on an invalid Date
Every other formatting method returns "Invalid Date" as a string. This one throws RangeError, so serialising a date that came from user input or a failed parse can crash a request handler rather than producing bad output.
Throws
new Date(userInput).toISOString()
RangeError: Invalid time value
Validate first
const d = new Date(userInput);
if (Number.isNaN(d.getTime())) reject();
d.toISOString();
safe
2. Date-only and date-time strings parse differently
The specification treats a bare date as UTC and a date with a time but no offset as LOCAL. So "2026-03-15" and "2026-03-15T00:00" are different instants, and the off-by-one-day bug that follows is the most-reported Date issue there is.
Local, may shift a day
new Date('2026-03-15T00:00').toISOString().slice(0, 10)
'2026-03-14' east of UTC
Be explicit
new Date('2026-03-15T00:00Z').toISOString().slice(0, 10)
'2026-03-15'
3. It always converts to UTC
That is the point, and it means the date part may not be the local date. Slicing the first ten characters to get "today" gives the wrong day for anyone whose local date differs from the UTC date at that moment.
UTC day, not local
new Date().toISOString().slice(0, 10)
may be yesterday or tomorrow locally
Local day
new Date().toLocaleDateString('sv-SE')
local YYYY-MM-DD
4. Years outside four digits use the extended form
Before year 0 or after 9999 the output switches to a six-digit signed year with a leading plus or minus. Code that slices fixed offsets, or a database column sized for 24 characters, breaks on those.
Different length
new Date(Date.UTC(10000, 0, 1)).toISOString()
'+010000-01-01T00:00:00.000Z'
Parse, do not slice
new Date(s).getUTCFullYear()
10000

When to use

Use it
  • Storing a date in a database or a file
  • Sending a date over an API
  • Logging, where a sortable unambiguous format matters
  • Comparing dates as strings — ISO sorts correctly as text
Reach for something else
  • Showing a date to a person → toLocaleDateString
  • The Date may be invalid → check getTime first, or it throws
  • You need the local calendar day → a locale format, not a slice
  • You need a zone-aware wall-clock time → store the zone separately

Notes

Complexity
O(1)
Return
A new string, always 24 characters for four-digit years
CPython impl
V8: Builtins-date-toisostring
Memory
Allocates the result string
Thread-safe
Single-threaded

FAQ

Almost always because a date-time string without an offset was parsed as local time and then converted back to UTC. Append a Z if the input was meant to be UTC, or use a date-only string, which is parsed as UTC already.

new Date('2026-03-15T00:00');    // local
new Date('2026-03-15T00:00Z');   // UTC
new Date('2026-03-15');          // UTC

History

ES5
toISOString and toJSON added, along with ISO 8601 parsing in Date.parse.
ES2016
Parsing rules clarified: date-only strings are UTC, date-time strings without an offset are local.