JavaScript Date Table
| Piece | Syntax | Field note |
|---|---|---|
new Date() | Now, local zone | Internally UTC epoch MILLISECONDS wearing a calendar costume |
month is 0-indexed | new Date(2026, 9, 4) | = October 4 - the perpetual JavaScript trap |
string parsing | new Date('2026-10-04') | ISO date-only parses as UTC midnight; 2026/10/04 parses LOCAL - same day, different instant |
getters | getFullYear/getMonth/getDate | LOCAL zone readings - getMonth() + 1 for humans |
Date.now() | Milliseconds epoch | Fastest timestamp; a day is 86400000 ms - do arithmetic in ms |
toISOString() | 2026-10-04T...Z | Always UTC - THE storage and API exchange format |
Intl.DateTimeFormat | new Intl.DateTimeFormat('de-DE') | Locale names and order for free - one instant, many renderings |
mutation | setDate/setMonth mutate | In place - copy first with new Date(old) or your history rewrites itself |
Date is a millisecond counter wearing a calendar costume: internally every Date is UTC epoch milliseconds, and everything else - years, months, hours - is rendering on top. Hold that model and the API's oddities stop being random: getters read your LOCAL zone, toISOString always renders UTC.
Bottom line: the month is 0-indexed (9 means October), and string parsing splits on format - date-only ISO ('2026-10-04') parses as UTC midnight while slash format ('2026/10/04') parses as local time, so the same calendar date can land on different INSTANTS and come back a day off across time zones.
The honest part: Date objects MUTATE in place. setDate() on a stored deadline silently rewrites your deadline. The working discipline: store and transmit toISOString(), format at display time with Intl.DateTimeFormat, and copy (new Date(old)) before any set* call - or reach for a date library that makes values immutable.
How to use
- Exchange in ISO only: d.toISOString() for storage and APIs; parse only full ISO strings with explicit time and Z - never the slash formats in code that crosses zones.
- Render with Intl: new Intl.DateTimeFormat('de-DE', {dateStyle: 'long'}).format(d) - correct names, order and calendar per locale, no hand-built month arrays.
- Do calendar math in ms: Date.now() + 86400000 for tomorrow; for month arithmetic use new Date(y, m + 1, d) and let the constructor normalize overflow (month 13 = January next year).
Frequently asked questions
Why do JavaScript months start at zero?
Historical implementation, kept for backwards compatibility forever: the original API indexed month names into an array (0 = January), while day-of-month and year use natural numbers - an inconsistency inside the same constructor call. new Date(2026, 9, 4) is October 4, not September 4, and it bites even experienced developers every autumn. Practical defenses: prefer ISO strings or {year, month: 10} object arguments where available, name your constants (const OCT = 9), and treat any literal month number in code review as a bug until proven otherwise.
Why does new Date('2026-10-04') differ from new Date('2026/10/04')?
The two strings take different specification paths. Date-only ISO format is standardized to parse as UTC midnight; the slash format is implementation-defined and engines parse it as LOCAL time. Both create 'October 4' on the machine that parsed them, but they are different INSTANTS - 24 hours apart in half the world - so rendering either with toISOString() or in another time zone can show October 3. The portable contract: send and store full ISO with time and zone designator (2026-10-04T12:00:00Z), and parse nothing else in cross-zone code.
When do I need UTC getters instead of local ones?
When the moment, not the local reading, is the data. getHours() reads the user's time zone - right for showing 'your local time'; getUTCHours() reads the instant - right for comparing, bucketing by day, or rendering a schedule fixed to one zone. The cross-zone bug shape: storing local readings as if universal, then re-reading them on a server in UTC, shifts everything by the zone difference. Rule: persist instants (ISO-UTC), convert to local only at the display edge, and never round-trip local getters through storage.
How dangerous is Date mutation, really?
Dangerous precisely because it looks harmless. date.setDate(date.getDate() + 1) works - and if date is a stored deadline, a cached prop, or shared state, you just rewrote it everywhere it is referenced. The failures surface days later as deadlines a day early or recurring events that drift. The discipline: treat Dates as mutable handles to immutable values - copy before every set* call (new Date(old)), or never mutate and always derive new instances. Libraries like date-fns encode exactly this value semantics, which is a large part of why teams adopt them even for simple calendars.