Topics

Jump to a topic.

Search questions

Find a question by title, tag, or summary.

Why is Date hard to use correctly?

Questions in this chapter (4)
  • Draft
  • easy
  • Askedoften
  • Verified October 2026

Answer

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() and new Date(y, m, d) use the host's time zone; getUTCHours(), toISOString() and Date.UTC do 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.sssZ is 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 years 0–99 map to 1900–1999.
  • Mutable. Setters change the object in place, so const end = start; end.setMonth(…) also changes start. Date arithmetic means copying with new Date(date) and then mutating.
  • Silent invalidity. Bad input gives an "Invalid Date" with time value NaN. Only toISOString() throws a RangeError; comparisons and getters return NaN or false. Check Number.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).

Example

process.env.TZ = 'America/New_York';

// 1. Date-only strings are UTC, date-time strings are local
const dateOnly = new Date('2026-03-08');
const dateTime = new Date('2026-03-08T00:00');
console.log(dateOnly.toISOString(), dateTime.toISOString());
// → 2026-03-08T00:00:00.000Z 2026-03-08T05:00:00.000Z
console.log(dateOnly.getDate(), dateTime.getDate());
// → 7 8

// 2. Months are 0-based; out-of-range fields roll over
console.log(new Date(2026, 1, 31).toDateString());
// → Tue Mar 03 2026

// 3. Setters mutate, and assignment copies the reference
const start = new Date(Date.UTC(2026, 0, 31));
const end = start;
end.setUTCMonth(1);
console.log(start.toISOString());
// → 2026-03-03T00:00:00.000Z

// 4. Invalid dates do not throw until you serialize them
const invalid = new Date('2026-13-01');
console.log(invalid.getTime(), String(invalid));
// → NaN Invalid Date
try { invalid.toISOString(); } catch (e) { console.log(e.name); }
// → RangeError

Pitfalls

  • Treating new Date('2026-03-08') as local midnight. It is UTC midnight, so in any zone behind UTC getDate() returns the previous day. Add T00:00 to get local midnight, or keep the whole pipeline in UTC.
  • Relying on new Date(string) for non-ISO input. Browsers and Node can disagree, and a string that works in V8 may be NaN elsewhere. Parse with a format you control, or pass numeric fields.
  • Adding months or days with setters on a shared object: d.setMonth(d.getMonth() + 1) on Jan 31 gives Mar 3 (2026), and it mutates the caller's date.
  • Using getTimezoneOffset() as a zone. It returns minutes with the opposite sign of the usual +05:30 (UTC−5 gives 300) and describes only that one instant's offset, not the zone or its rules.

Follow-ups

What does Date() without new return?
A string for the current time (spec: ToDateString(now)), not a Date. The argument list is ignored.
How does JSON.stringify treat a Date?
It calls toJSON(), which returns toISOString() (or null for an invalid date). JSON.parse does not reverse it: you get a string back and must rebuild the Date.
Why can the same local time map to two instants, or none?
DST transitions repeat or skip local clock times. For a repeated time the spec's local-to-UTC conversion picks the earlier instant. For a skipped time it shifts forward by the gap. See time-zones.

Exercises

Which day is it?

process.env.TZ = 'America/Los_Angeles';
const d = new Date('2026-07-04');
console.log(d.getDate(), d.getUTCDate());