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
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.
const epoch = new Date(0); console.log(epoch.toISOString()); console.log(new Date(86_400_000).toISOString()); console.log(typeof Date.now());
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:
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());
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
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());
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
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");
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
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" }));
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
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"));
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.
const bad = new Date("not a date"); console.log(bad.toString()); console.log(Number.isNaN(bad.getTime()));
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.
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());