/commands/javascript/format-date-yyyy-mm-dd
javascript format date as YYYY-MM-DD
- tool
- JavaScript
- versions
- ES5+ (toISOString), Intl.DateTimeFormat: all current runtimes
- os
- any
For a YYYY-MM-DD string in UTC, use date.toISOString().slice(0, 10). For the local date (what the user's clock shows), build it from the parts: `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`, or let Intl.DateTimeFormat with the en-CA locale or formatToParts do it for a chosen time zone. The two can differ by a day around midnight.
UTC, local and a specific time zone
The example sets process.env.TZ at the top of the script (Node.js honours it at run time) so the output is the same on every machine; in real code the local time zone comes from the system.
cat > date.mjs <<'EOF'
process.env.TZ = "Europe/Rome"; // pin the local zone for this example
const d = new Date("2026-03-01T23:30:00Z"); // 23:30 UTC on 1 March
console.log("UTC (toISOString):", d.toISOString().slice(0, 10));
const pad = (n) => String(n).padStart(2, "0");
const local = `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`;
console.log("local parts: ", local, `(TZ=${process.env.TZ})`);
const tokyo = new Intl.DateTimeFormat("en-CA", { timeZone: "Asia/Tokyo" }).format(d);
console.log("en-CA in Tokyo: ", tokyo);
const parts = Object.fromEntries(
new Intl.DateTimeFormat("en-US", { timeZone: "America/New_York", year: "numeric", month: "2-digit", day: "2-digit" })
.formatToParts(d)
.map((p) => [p.type, p.value]),
);
console.log("parts in New York:", `${parts.year}-${parts.month}-${parts.day}`);
try {
new Date("31/12/2026").toISOString();
} catch (e) {
console.log(`${e.name}: ${e.message}`);
}
EOF
node date.mjsOutput
UTC (toISOString): 2026-03-01 local parts: 2026-03-02 (TZ=Europe/Rome) en-CA in Tokyo: 2026-03-02 parts in New York: 2026-03-01 RangeError: Invalid time value
At 23:30 UTC it is already 2 March in Rome and Tokyo, but still 1 March in UTC and New York. That is the classic "off by one day" bug: toISOString() is always UTC.
Options
| Code | Result | Time zone |
|---|---|---|
| d.toISOString().slice(0, 10) | 2026-03-01 | Always UTC |
| getFullYear / getMonth() + 1 / getDate with padStart | Local YYYY-MM-DD | The runtime time zone (TZ or system setting) |
| new Intl.DateTimeFormat("en-CA").format(d) | YYYY-MM-DD | Local, or the timeZone option |
| Intl.DateTimeFormat(...).formatToParts(d) | Separate year, month, day | Any IANA zone; does not depend on a locale pattern |
| d.toLocaleDateString("sv-SE") | YYYY-MM-DD | Local, or the timeZone option |
| getUTCFullYear / getUTCMonth / getUTCDate | UTC parts | Always UTC, like toISOString |
Pitfalls
- Parsing a date-only string.
new Date("2026-03-01")is read as midnight UTC, whilenew Date("2026-03-01T00:00")is midnight local time. Formatting the first with local getters west of UTC prints the previous day. - Locale patterns can change.
en-CAandsv-SEhappen to format asYYYY-MM-DD;formatToPartsdoes not depend on any locale's pattern and is the safer choice for machine-readable output. - Invalid dates.
toISOString()throwsRangeError: Invalid time valueon an invalidDate; the local getters returnNaN, which prints asNaN-NaN-NaN. - Missing values. Calling
.toISOString()onundefinedthrows Cannot read properties of undefined.
Confirm it worked
Test with a time close to midnight, as above, and in at least two time zones (TZ=America/New_York node app.js on Linux and macOS). If both print the date you expect, the formatting handles time zones correctly.
FAQ
Why is toISOString() one day off?
toISOString always returns the date in UTC. Shortly after midnight in a time zone east of UTC, or late in the evening west of it, the UTC date is a different day from the local one. Format the local parts with getFullYear, getMonth and getDate instead.
Why is getMonth() one less than the month?
getMonth() is zero-based: January is 0 and December is 11. Add 1 before padding it to two digits.
What throws RangeError: Invalid time value?
toISOString() on an invalid Date, for example new Date("31/12/2026"), which the parser does not understand. Check with Number.isNaN(date.getTime()) before formatting.
Sources
- developer.mozilla.orgdeveloper.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toISOString
- developer.mozilla.orgdeveloper.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat
- developer.mozilla.orgdeveloper.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/padStart