Dates & Times

The Date object represents a single moment in time. You can create dates, read their parts, do arithmetic and format them for display. Date has some awkward corners — months start at zero, and time zones cause confusion — so this lesson also points out the traps and the newer Temporal API.

Creating dates

JS create.js
const now = new Date();                          // current date and time
const fromString = new Date("2026-03-14");       // ISO date string
const fromParts = new Date(2026, 2, 14, 9, 30);  // year, month (0-based!), day, hour, minute
const fromTimestamp = new Date(0);               // milliseconds since 1 Jan 1970 UTC

Internally, a date is just a number: the milliseconds elapsed since midnight UTC on 1 January 1970 (the "Unix epoch"). Date.now() returns that number for the current moment.

JS timestamp.js
const epoch = new Date(0);
console.log(epoch.toISOString());
console.log(new Date(86_400_000).toISOString());
console.log(typeof Date.now());
Output
1970-01-01T00:00:00.000Z
1970-01-02T00:00:00.000Z
number

Months start at zero

In the new Date(year, month, …) form, January is 0 and December is 11. Days of the month start at 1 as normal. This trips up everyone at least once:

JS months.js
const d = new Date(2026, 0, 31);
console.log(d.getMonth(), d.getDate());

const overflow = new Date(2026, 1, 30);
console.log(overflow.getMonth(), overflow.getDate());
Output
0 31
2 2

February 30th doesn't exist, so Date silently rolls over to 2 March. That overflow is occasionally useful (adding days, below), but it also means invalid input isn't rejected.

Reading the parts

JS getters.js
const d = new Date(2026, 9, 6, 14, 5, 9);

console.log(d.getFullYear());
console.log(d.getMonth() + 1);
console.log(d.getDate());
console.log(d.getDay());
console.log(d.getHours(), d.getMinutes(), d.getSeconds());
Output
2026
10
6
2
14 5 9

getDay() is the day of the week, from 0 (Sunday) to 6 (Saturday). Each getter has a matching setter (setFullYear, setMonth…), and each has a UTC version (getUTCHours…).

Date arithmetic

JS arithmetic.js
const start = new Date(2026, 0, 28);

const later = new Date(start);
later.setDate(later.getDate() + 7);
console.log(later.getMonth() + 1, later.getDate());

const a = new Date(2026, 0, 1);
const b = new Date(2026, 2, 1);
const days = (b - a) / (1000 * 60 * 60 * 24);
console.log(Math.round(days), "days");
Output
2 4
59 days

To add days, use setDate with a value past the end of the month — Date rolls over correctly into February. Subtracting two dates gives the difference in milliseconds. Copy a date with new Date(original) before changing it, since setters modify the object in place.

Formatting dates

JS format.js
const d = new Date(Date.UTC(2026, 9, 6, 14, 30));

console.log(d.toISOString());
console.log(d.toLocaleDateString("en-US", { timeZone: "UTC" }));
console.log(d.toLocaleDateString("en-GB", { timeZone: "UTC" }));
console.log(d.toLocaleDateString("en-US", { weekday: "long", year: "numeric", month: "long", day: "numeric", timeZone: "UTC" }));
console.log(d.toLocaleTimeString("en-US", { hour: "2-digit", minute: "2-digit", timeZone: "UTC" }));
Output
2026-10-06T14:30:00.000Z
10/6/2026
06/10/2026
Tuesday, October 6, 2026
02:30 PM

toISOString() gives a standard, sortable format — ideal for storing and sending dates. For people, use toLocaleDateString / toLocaleTimeString (or Intl.DateTimeFormat) so each locale sees its own format. The timeZone option is used here so the output is the same wherever this runs.

Relative times

JS relative.js
const rtf = new Intl.RelativeTimeFormat("en", { numeric: "auto" });

console.log(rtf.format(-1, "day"));
console.log(rtf.format(3, "week"));
console.log(rtf.format(-5, "minute"));
Output
yesterday
in 3 weeks
5 minutes ago

Parsing strings safely

Only the ISO format ("2026-10-06" or "2026-10-06T14:30:00Z") is parsed consistently by every browser. Formats like "10/06/2026" are ambiguous (October 6th or 10th June?) and handled differently by different engines. Also note: a date-only ISO string is treated as UTC, but a date-time string without Z is treated as local time.

JS invalid.js
const bad = new Date("not a date");
console.log(bad.toString());
console.log(Number.isNaN(bad.getTime()));
Output
Invalid Date
true

Time zones and Temporal

A Date only knows UTC and the computer's local time zone, which makes scheduling across zones hard. The new Temporal API fixes this with separate types for plain dates, times, zoned date-times and durations, and it's immutable. It's available in recent versions of Firefox and Chrome; for older browsers, libraries such as date-fns, Day.js and Luxon fill the gap.

JS temporal.js
const today = Temporal.Now.plainDateISO();
const due = today.add({ days: 30 });
console.log(due.toString());              // e.g. 2026-11-05
const meeting = Temporal.ZonedDateTime.from("2026-10-06T09:00[America/New_York]");
console.log(meeting.withTimeZone("Asia/Tokyo").toString());

Try it yourself

Try it yourself
Console output