Skip to content
JavaScript js dates 4 min read

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:

ComponentLocal getterUTC getterRange
YeargetFullYear()getUTCFullYear()full 4-digit
MonthgetMonth()getUTCMonth()011
Day of monthgetDate()getUTCDate()131
Day of weekgetDay()getUTCDay()06
HoursgetHours()getUTCHours()023
MinutesgetMinutes()getUTCMinutes()059
SecondsgetSeconds()getUTCSeconds()059
MillisecondsgetMilliseconds()getUTCMilliseconds()0999

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. Two Date objects are distinct references, so new Date(0) === new Date(0) is false. Compare with getTime() (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 Date constructor; 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 than new 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 Temporal API or a maintained library rather than juggling offsets by hand.
Last updated June 1, 2026
Was this helpful?