A Date wraps a single time value: an integer number of milliseconds since 1970-01-01T00:00:00Z, within ±8.64 × 10^15 (about ±273,790 years), or NaN for an invalid date. It stores an instant, not a calendar date, and not a time zone. Almost every pitfall comes from the API mixing that instant with the host's local zone and with calendar fields.
- Hidden local time.
getHours(),toString()andnew Date(y, m, d)use the host's time zone;getUTCHours(),toISOString()andDate.UTCdo not. The same code gives different results on a laptop and on a UTC server. - Inconsistent parsing. The ES format
YYYY-MM-DDTHH:mm:ss.sssZis the only one the spec defines. With no offset, a date-only string ('2026-03-08') is read as UTC and a date-time string ('2026-03-08T00:00') as local time. Any other string ('March 8, 2026','2026/03/08','2026-03-08 10:00') is implementation-defined. - 0-based months, 1-based days.
new Date(2026, 1, 31)is not an error. Out-of-range fields roll over into the next month. Two-digit years0–99map to 1900–1999. - Mutable. Setters change the object in place, so
const end = start; end.setMonth(…)also changesstart. Date arithmetic means copying withnew Date(date)and then mutating. - Silent invalidity. Bad input gives an "Invalid Date" with time value
NaN. OnlytoISOString()throws aRangeError; comparisons and getters returnNaNorfalse. CheckNumber.isNaN(d.getTime()). - No zones, no calendars, no durations. There is no way to hold "9:00 in Tokyo" or "March 8, no time". Months and days are the only calendar fields, and "add one month" is hand-written arithmetic.
Use Date for instants and Date.now(). For wall-clock or calendar values, use Temporal (temporal-types).