Date and Time

Learn how to create, read, and format dates using the built-in Date object, understand timestamps and timezones, and use Intl.DateTimeFormat for locale-aware formatting.

Working with dates is one of the trickiest parts of everyday programming — not because the API is huge, but because time itself is full of edge cases (timezones, leap years, daylight saving). This lesson covers JavaScript's Date object and the common pitfalls to watch for.

Creating dates

new Date() with no arguments creates a Date object representing the current moment. You can also create a specific date with new Date(year, monthIndex, day, hours, minutes, seconds) — note that the month is zero-indexed (0 = January, 11 = December), a classic source of off-by-one bugs.

You can also create a Date from an ISO 8601 string, like new Date("2026-03-15"), or from a timestamp (milliseconds since a fixed reference point) with new Date(milliseconds).

Timestamps

A timestamp is the number of milliseconds that have elapsed since the Unix epoch — midnight, January 1, 1970, UTC. Date.now() returns the current timestamp as a plain number, which is useful for measuring elapsed time or generating unique-ish IDs.

Every Date object also has a .getTime() method that returns its own timestamp, and you can subtract two Date objects (or their timestamps) to find the number of milliseconds between them, which you then divide down into seconds, minutes, hours, or days as needed.

Getting and setting components

Once you have a Date object, getFullYear(), getMonth(), getDate(), getDay(), getHours(), getMinutes(), and getSeconds() read out its individual parts. Note the naming: getDate() returns the day of the month (1-31), while getDay() returns the day of the week (0 = Sunday through 6 = Saturday) — an easy pair to mix up.

The matching setters — setFullYear(), setMonth(), setDate(), and so on — mutate the Date object in place (unlike strings, Date objects are mutable) and are handy for things like advancing a date by a number of days.

• getFullYear() — four-digit year, e.g. 2026 • getMonth() — 0 (January) through 11 (December) • getDate() — day of the month, 1 through 31 • getDay() — day of the week, 0 (Sunday) through 6 (Saturday) • getHours() / getMinutes() / getSeconds() — time-of-day components

Formatting dates

Date objects have some built-in formatting methods like toDateString(), toISOString(), and toLocaleDateString(), but they are limited and inconsistent for anything beyond basic display. toISOString() is especially useful because it always returns a standardized, unambiguous format (YYYY-MM-DDTHH:mm:ss.sssZ) that's ideal for storing dates or sending them over an API.

For anything user-facing — displaying a date the way a real person expects to read it, in their own language and conventions — reach for Intl.DateTimeFormat rather than manually concatenating getMonth()/getDate()/getFullYear().

Timezone concepts

A Date object internally always stores a single UTC timestamp — it doesn't "remember" a timezone. What changes is how it is displayed: methods like getHours() and toLocaleString() show the time converted to the browser's local timezone, while getUTCHours() and toISOString() show it in UTC.

This distinction matters enormously for scheduling and international apps: two users in different timezones looking at the "same" Date object will see different local times, but it represents the exact same instant. When storing dates (in a database, in an API), UTC (via toISOString or a timestamp) is almost always the right choice; convert to local time only for display.

Common date pitfalls

Beyond the zero-indexed month, common mistakes include: parsing date-only strings like "2026-03-15" and assuming they're in local time (they're actually parsed as UTC midnight, which can shift the displayed date by a day depending on the viewer's timezone); mutating a Date object with a setter when you meant to create a new one; and forgetting that month/day arithmetic (like adding 1 to getDate() near the end of a month) needs the Date's own setters to correctly roll over into the next month.

Intl.DateTimeFormat overview

Intl.DateTimeFormat is part of the built-in Internationalization API and formats dates according to locale conventions — for example, the same date renders as "3/15/2026" in en-US but "15/03/2026" in en-GB. You create a formatter once with a locale and options, then reuse it to format as many dates as you like.

This is the modern, recommended way to display dates and times to users, because it correctly handles locale-specific ordering, month names, and calendar conventions without you having to hardcode any of it.

Creating and formatting dates

new Date(year, month, day, ...) uses a zero-indexed month — always double-check this when constructing dates manually. toISOString() always outputs UTC, marked with the trailing 'Z'. Intl.DateTimeFormat objects can be reused for many dates without recreating them each time, which is more efficient in loops.

const now = new Date();
const specific = new Date(2026, 2, 15); // March 15, 2026 (month is 0-indexed)
const fromString = new Date("2026-03-15T10:00:00Z");

Date.now();                 // current timestamp in ms
now.getFullYear();          // e.g. 2026
now.getMonth();             // 0-11
now.toISOString();          // "2026-03-15T10:00:00.000Z"

const formatter = new Intl.DateTimeFormat("en-US", {
  year: "numeric",
  month: "long",
  day: "numeric"
});
formatter.format(specific); // "March 15, 2026"

Code Examples

Reads the individual components of the current date. Note that getMonth() is zero-indexed while getDate() is one-indexed for the day of the month — a frequent source of confusion. Expected result: 2026 8 (September, if run today, since September is month index 8) 15 2 today.getMonth() returns 0 for January, so you typically add 1 when displaying a human-readable month number.

Calculates the number of whole days between two dates by subtracting their timestamps (in milliseconds) and dividing by the number of milliseconds in a day. Expected result: 73 Math.abs() ensures the result is positive regardless of which date comes first; Math.round() guards against tiny floating-point drift from daylight-saving transitions.

dateB.getTime() - dateA.getTime() gives the raw millisecond difference between two Date objects.

Shows the split between a friendly, locale-aware display format for humans (Intl.DateTimeFormat) and a strict, unambiguous machine format for storage or transmission (toISOString). Expected result: Monday, June 1, 2026 2026-06-01T00:00:00.000Z (exact offset depends on the local timezone the Date was created in) toISOString() and Intl.DateTimeFormat().format() serve different audiences: machines/storage versus human readers, respectively.

const today = new Date();
console.log(today.getFullYear());
console.log(today.getMonth());     // 0-11, not 1-12!
console.log(today.getDate());      // day of month
console.log(today.getDay());       // day of week, 0=Sunday
function daysBetween(dateA, dateB) {
  const msPerDay = 24 * 60 * 60 * 1000;
  const diffMs = Math.abs(dateB.getTime() - dateA.getTime());
  return Math.round(diffMs / msPerDay);
}

const start = new Date(2026, 0, 1);
const end = new Date(2026, 2, 15);
console.log(daysBetween(start, end));
function formatForDisplay(date) {
  return new Intl.DateTimeFormat("en-US", {
    weekday: "long",
    year: "numeric",
    month: "long",
    day: "numeric"
  }).format(date);
}

const launchDate = new Date(2026, 5, 1);
console.log(formatForDisplay(launchDate));
console.log(launchDate.toISOString());

How a Date object relates to UTC and local time

A Date never stores a timezone — it stores a single instant in time (UTC). Display methods choose whether to show that instant in UTC or convert it to the local timezone of whoever is running the code.

Date object
   (stores ONE UTC timestamp)
           │
     ┌─────┴──────┐
     │            │
 getUTCHours()  getHours()
  → UTC time   → converted to
               viewer's local
               timezone

Important Rules

new Date(year, month, day) uses a zero-indexed month (0 = January); getMonth() returns the same zero-indexed value.

getDate() returns the day of the month; getDay() returns the day of the week — they are not interchangeable.

A Date object always represents a single UTC instant internally; local-time methods only affect how it is displayed.

Store and transmit dates in UTC (toISOString() or a timestamp); convert to local time only when displaying to a user.

Date objects are mutable — setter methods like setDate() change the object in place, unlike string methods.

Where this shows up

Booking systems, calendars, and scheduling apps live and die by correct date/timezone handling. Blog posts show 'time ago' formatting computed from timestamp differences. E-commerce sites display delivery estimates and format order dates per the customer's locale using Intl.DateTimeFormat. APIs almost universally exchange dates as ISO strings or Unix timestamps to stay timezone-unambiguous between client and server.

const birthday = new Date(2026, 3, 15); // intending April 15

// Correct: month 3 means April (0-indexed), so this IS April 15 — verify with getMonth()

The most common Date bug is forgetting that the month argument is zero-indexed. new Date(2026, 3, 15) creates April 15, not March 15 — always double-check by logging getMonth() or using named constants when clarity matters.

if (date1 === date2) { /* comparing dates */ } if (date1.getTime() === date2.getTime()) { /* correct */ }

Two different Date objects representing the exact same moment are never === equal, because === compares object references, not values. Compare their .getTime() timestamps instead.

[Multiple Choice] What does new Date(2026, 0, 1) represent? A. December 1, 2026 B. January 1, 2026 C. January 0, 2026 (invalid) D. February 1, 2026

B. January 1, 2026 — The month argument is zero-indexed, so 0 means January. new Date(2026, 0, 1) represents January 1, 2026.

[True / False] A JavaScript Date object stores which timezone it was created in, and that timezone travels with it. A. True B. False

B. False — False. A Date object always internally stores a single UTC timestamp; it has no memory of any timezone. Display methods like getHours() simply convert that stored instant to the local timezone of whatever environment is running the code.

Write a function isWeekend(date) that returns true if the given Date falls on a Saturday or Sunday, and false otherwise.

function isWeekend(date) { const day = date.getDay(); return day === 0 || day === 6; } console.log(isWeekend(new Date(2026, 8, 19))); // Saturday -> true console.log(isWeekend(new Date(2026, 8, 15))); // Tuesday -> false getDay() returns 0 for Sunday and 6 for Saturday.