The Date Object
Working with dates is one of those tasks that looks trivial until it isn’t. JavaScript’s built-in Date object handles a single moment in time, internally stored as the number of milliseconds since the Unix epoch (January 1, 1970, 00:00:00 UTC). Understanding how to create, read, and reason about Date instances — including a few notorious quirks — is the foundation for everything else you’ll do with time in JavaScript.
Creating a date
There are four common ways to construct a Date. The constructor is overloaded, so the arguments you pass determine which behavior you get.
// 1. The current moment
const now = new Date();
// 2. From a timestamp (milliseconds since the epoch)
const fromTimestamp = new Date(1_700_000_000_000);
// 3. From a string (ISO 8601 is the only format you should trust)
const fromString = new Date("2026-06-01T09:30:00Z");
// 4. From individual parts (year, month, day, ...)
const fromParts = new Date(2026, 5, 1, 9, 30, 0);
The parts-based constructor hides the single most infamous trap in the whole API: the month is zero-based. January is 0 and December is 11. The day-of-month, hours, minutes, and seconds are all one- or zero-based as you’d expect — only the month breaks the pattern.
const june = new Date(2026, 5, 1); // 5 = June, not May
console.log(june.toDateString());
Output:
Mon Jun 01 2026
Parsing non-ISO date strings (like
"06/01/2026"or"June 1, 2026") is implementation-dependent and a frequent source of cross-browser bugs. Always pass an ISO 8601 string or use the numeric parts constructor.
Reading and setting components
Every Date exposes paired getters and setters for each component. The plain methods (getFullYear, getMonth, …) operate in the runtime’s local time zone; their getUTC* counterparts operate in UTC.
const d = new Date("2026-06-01T09:30:45.123Z");
console.log(d.getFullYear()); // 2026
console.log(d.getMonth()); // 5 (June — zero-based again)
console.log(d.getDate()); // day of the month, e.g. 1
console.log(d.getDay()); // day of the week, 0 = Sunday
console.log(d.getHours()); // local hour
console.log(d.getUTCHours()); // 9 (the hour in UTC)
console.log(d.getMilliseconds()); // 123
Setters mutate the date in place and conveniently roll over out-of-range values, which makes simple arithmetic easy:
const d = new Date(2026, 0, 31); // Jan 31, 2026
d.setDate(d.getDate() + 1); // add one day
console.log(d.toDateString()); // rolls into February
Output:
Sun Feb 01 2026
Here’s a quick reference of the most-used accessors:
| Component | Local getter | UTC getter | Range |
|---|---|---|---|
| Year | getFullYear() | getUTCFullYear() | full 4-digit |
| Month | getMonth() | getUTCMonth() | 0–11 |
| Day of month | getDate() | getUTCDate() | 1–31 |
| Day of week | getDay() | getUTCDay() | 0–6 |
| Hours | getHours() | getUTCHours() | 0–23 |
| Minutes | getMinutes() | getUTCMinutes() | 0–59 |
| Seconds | getSeconds() | getUTCSeconds() | 0–59 |
| Milliseconds | getMilliseconds() | getUTCMilliseconds() | 0–999 |
Timestamps
Under the hood every date is just a number. You can extract that number with getTime(), and you can get the current timestamp without allocating a Date at all using the static Date.now().
const d = new Date("2026-06-01T00:00:00Z");
console.log(d.getTime()); // milliseconds since the epoch
console.log(+d); // same value via unary plus coercion
console.log(Date.now()); // current time, no object created
Because timestamps are plain numbers, they’re the safest unit for storing, transmitting, and comparing moments in time. Comparing two dates is just comparing two numbers:
const a = new Date("2026-06-01");
const b = new Date("2026-06-02");
console.log(a.getTime() < b.getTime()); // true
console.log(a < b); // true (relational operators coerce)
Beware
===between dates. TwoDateobjects are distinct references, sonew Date(0) === new Date(0)isfalse. Compare withgetTime()(or+date) instead.
UTC vs local time
A single Date represents one absolute instant, but how that instant is displayed depends on the time zone. The local getters apply the host machine’s zone offset; the UTC getters do not.
const d = new Date("2026-06-01T12:00:00Z");
console.log(d.toISOString()); // always UTC, ends in Z
console.log(d.getTimezoneOffset()); // minutes behind UTC (e.g. 300 for EST)
console.log(d.toLocaleString()); // formatted in the local zone
getTimezoneOffset() returns the difference, in minutes, between local time and UTC — and its sign is the opposite of what most people expect. For a zone that is UTC−5, it returns 300, not -300.
When you need to build a date from UTC parts rather than local parts, use the static Date.UTC(), which returns a timestamp:
const ts = Date.UTC(2026, 5, 1, 12, 0, 0); // June 1, 2026 12:00 UTC
const d = new Date(ts);
console.log(d.toISOString());
Output:
2026-06-01T12:00:00.000Z
Best practices
- Treat the month as zero-based everywhere you construct or read a date with the parts API.
- Store and exchange dates as ISO 8601 strings or numeric timestamps, never locale-formatted strings.
- Only ever parse ISO 8601 strings with the
Dateconstructor; other formats are not portable. - Compare dates with
getTime()or unary+, not with==/===. - Use
Date.now()for “the current time as a number” — it’s faster and clearer thannew Date().getTime(). - Be explicit about local vs UTC by choosing the matching getter/setter family for your use case.
- For complex time-zone math, reach for the modern
TemporalAPI or a maintained library rather than juggling offsets by hand.