In short
JavaScript date methods let you read, change, compare and format dates. Learn date formats and parsing pitfalls, get and set methods, date differences, and Intl.DateTimeFormat. JavaScript date formats and parsing pitfalls You met the Date object in the Dates and Timers lesson.
JavaScript date methods let you read, change, compare and format dates. Learn date formats and parsing pitfalls, get and set methods, date differences, and Intl.DateTimeFormat.
JavaScript date formats and parsing pitfalls
You met the Date object in the Dates and Timers lesson. This lesson goes deeper. First, remember the most important fact: a Date stores one moment in time as a number, the milliseconds since 1 January 1970 at midnight UTC. UTC (Coordinated Universal Time) is the world's reference clock, the time at 0° longitude without summer time. Everything else, like "October 1" or "6:30 PM", is just a way of showing that number in a time zone.
There are several ways to create a date. These are the reliable ones:
| You write | Meaning | Time zone used |
|---|---|---|
new Date() | Right now | |
new Date(2026, 9, 1, 18, 30) | Year, month (0 to 11), day, hours, minutes | Local |
new Date("2026-10-01T18:30") | ISO date and time | Local |
new Date("2026-10-01T18:30:00Z") | ISO with Z at the end | UTC |
new Date("2026-10-01T18:30:00+02:00") | ISO with an offset | That offset |
new Date("2026-10-01") | ISO date only | UTC (surprise!) |
new Date(1790856000000) | A timestamp in milliseconds |
That last-but-one row is the classic pitfall. A date-only string like "2026-03-20" is read as midnight UTC, while the same string with a time is read as local time. For someone in New York (4 hours behind UTC in March), midnight UTC is still the evening before:
// Run in New York:
console.log(new Date("2026-03-20").toDateString()); // Output: Thu Mar 19 2026 (!)
console.log(new Date("2026-03-20T00:00").toDateString()); // Output: Fri Mar 20 2026
// Safe everywhere: use numbers for a local calendar date
console.log(new Date(2026, 2, 20).toDateString()); // Output: Fri Mar 20 2026
Other formats, like "03/04/2026" or "1 October 2026", are not part of the standard. Browsers try to guess, and they may guess differently (is 03/04 March 4 or 3 April?). Stick to ISO strings or numbers.
When a string cannot be read at all, you get an Invalid Date. It is still a Date object, so check it with getTime():
const fromForm = new Date("next friday");
console.log(fromForm); // Output: Invalid Date
console.log(Number.isNaN(fromForm.getTime())); // Output: true
function isValidDate(date) {
return !Number.isNaN(date.getTime());
}
console.log(isValidDate(new Date("2026-10-01T09:00"))); // Output: true
Note: An
<input type="date">gives you its value as a date-only string like"2026-10-01". To treat it as a local date, add a time:new Date(input.value + "T00:00").
Date get methods: reading parts of a date
Get methods read one part of a date. Each local version has a UTC twin (getHours and getUTCHours) that reads the same moment in UTC instead of the visitor's time zone.
| Method | Returns | Example for 1 Oct 2026, 18:30 |
|---|---|---|
getFullYear() | Four-digit year | 2026 |
getMonth() | Month, 0 (Jan) to 11 (Dec) | 9 |
getDate() | Day of the month, 1 to 31 | 1 |
getDay() | Weekday, 0 (Sun) to 6 (Sat) | 4 (Thursday) |
getHours() | Hours, 0 to 23 | 18 |
getMinutes() / getSeconds() | 0 to 59 | 30 / 0 |
getMilliseconds() | 0 to 999 | 0 |
getTime() | Milliseconds since 1970 (UTC) | a big number |
getTimezoneOffset() | Minutes between UTC and local time | -60 in London in summer |
getUTCHours() and other getUTC... | The same parts in UTC | depends on your zone |
toISOString() | A UTC string for saving or sending | "2026-10-01T17:30:00.000Z" in London |
const event = new Date(2026, 9, 1, 18, 30);
const days = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
console.log(event.getFullYear()); // Output: 2026
console.log(event.getMonth() + 1); // Output: 10
console.log(days[event.getDay()]); // Output: Thursday
const isWeekend = event.getDay() === 0 || event.getDay() === 6;
console.log(isWeekend); // Output: false
Note that getTimezoneOffset() is positive when you are behind UTC (like New York) and negative when you are ahead.
Date set methods: changing a date and adding days
Set methods change one part of an existing date. They change the original object, so copy a date first if you still need the old value.
| Method | Changes |
|---|---|
setFullYear(y, m?, d?) | Year (and optionally month and day) |
setMonth(m, d?) | Month |
setDate(d) | Day of the month |
setHours(h, m?, s?, ms?) | Time (and optionally minutes, seconds, ms) |
setMinutes(), setSeconds(), setMilliseconds() | Smaller parts |
setTime(ms) | The whole date, from a timestamp |
The nice part: if a value is too big or too small, JavaScript rolls over into the next or previous month or year. That makes date maths easy:
function addDays(date, days) {
const copy = new Date(date); // copy, so the original is untouched
copy.setDate(copy.getDate() + days);
return copy;
}
const ordered = new Date(2026, 9, 1);
console.log(addDays(ordered, 45).toDateString()); // Output: Sun Nov 15 2026
console.log(addDays(ordered, -1).toDateString()); // Output: Wed Sep 30 2026
console.log(ordered.toDateString()); // Output: Thu Oct 01 2026 (unchanged)
const meeting = new Date(2026, 9, 1);
meeting.setHours(9, 15, 0, 0); // 9:15:00.000
console.log(meeting.toLocaleTimeString("en-US")); // Output: 9:15:00 AM
// Day 0 means "the last day of the previous month"
const daysInFeb2026 = new Date(2026, 2, 0).getDate();
console.log(daysInFeb2026); // Output: 28
console.log(new Date(2028, 2, 0).getDate()); // Output: 29 (a leap year)
new Date(year, month + 1, 0) is a famous trick: it asks for day 0 of the next month, which rolls back to the last day of the month you want.
Common mistake: Adding months to the 31st.
setMonthkeeps the day number, so 31 January plus one month becomes "31 February", which rolls over to 3 March. Check for this when you build monthly billing or reminders.
const renewal = new Date(2026, 0, 31);
renewal.setMonth(renewal.getMonth() + 1);
console.log(renewal.toDateString()); // Output: Tue Mar 03 2026
Comparing dates and the difference between two dates
The < and > operators work on dates, because JavaScript turns them into their millisecond numbers. But === compares objects, not moments, so two separate Date objects are never equal. Compare getTime() instead:
const a = new Date(2026, 9, 1);
const b = new Date(2026, 9, 1);
console.log(a === b); // Output: false (two different objects)
console.log(a.getTime() === b.getTime()); // Output: true (same moment)
console.log(a < new Date(2026, 9, 2)); // Output: true
const deadline = new Date(2026, 11, 31, 23, 59);
console.log(new Date() > deadline ? "Closed" : "Still open");
Subtracting two dates gives the difference in milliseconds. Divide to get days, and round, because a day with a summer time change has 23 or 25 hours:
const MS_PER_DAY = 24 * 60 * 60 * 1000;
function daysBetween(start, end) {
return Math.round((end - start) / MS_PER_DAY);
}
console.log(daysBetween(new Date(2026, 9, 1), new Date(2026, 11, 25))); // Output: 85
function getAge(birthDate, today = new Date()) {
let age = today.getFullYear() - birthDate.getFullYear();
const birthdayPassed =
today.getMonth() > birthDate.getMonth() ||
(today.getMonth() === birthDate.getMonth() && today.getDate() >= birthDate.getDate());
if (!birthdayPassed) age--; // not had a birthday yet this year
return age;
}
console.log(getAge(new Date(2000, 10, 15), new Date(2026, 9, 1))); // Output: 25
For ages and months, count calendar parts like getAge does. Dividing milliseconds by "365 days" gives wrong answers around leap years and birthdays.
Formatting dates with Intl.DateTimeFormat and relative time with Intl.RelativeTimeFormat
Intl.DateTimeFormat formats dates for any language. Besides the dateStyle and timeStyle shortcuts you saw before, you can choose each part yourself, and even pick a timeZone:
const launch = new Date(Date.UTC(2026, 9, 1, 12, 0)); // 12:00 UTC
const full = new Intl.DateTimeFormat("en-US", { dateStyle: "full" });
console.log(full.format(launch)); // Output: Thursday, October 1, 2026
const custom = new Intl.DateTimeFormat("en-US", {
weekday: "short", month: "short", day: "numeric", hour: "numeric", minute: "2-digit", timeZone: "UTC",
});
console.log(custom.format(launch)); // Output: Thu, Oct 1, 12:00 PM
for (const zone of ["America/New_York", "Europe/London", "Asia/Tokyo"]) {
const time = new Intl.DateTimeFormat("en-US", { timeStyle: "short", timeZone: zone }).format(launch);
console.log(`${zone}: ${time}`);
}
// Output:
// America/New_York: 8:00 AM
// Europe/London: 1:00 PM
// Asia/Tokyo: 9:00 PM
const range = new Intl.DateTimeFormat("en-US", { dateStyle: "medium" });
console.log(range.formatRange(new Date(2026, 9, 1), new Date(2026, 9, 5))); // Output: Oct 1 – 5, 2026
One moment, three local times: that is exactly how a webinar page can show each visitor the start time in their own zone. Time zones use names like "Europe/Paris", which handle summer time for you.
For "3 hours ago" or "in 2 days", use Intl.RelativeTimeFormat. You give it a number and a unit; negative numbers mean the past:
const rtf = new Intl.RelativeTimeFormat("en", { numeric: "auto" });
console.log(rtf.format(-1, "day")); // Output: yesterday
console.log(rtf.format(0, "day")); // Output: today
console.log(rtf.format(2, "week")); // Output: in 2 weeks
console.log(rtf.format(-3, "hour")); // Output: 3 hours ago
function timeAgo(date, now = new Date()) {
const seconds = Math.round((date - now) / 1000);
const units = [["year", 31536000], ["month", 2592000], ["week", 604800], ["day", 86400], ["hour", 3600], ["minute", 60]];
for (const [unit, size] of units) {
if (Math.abs(seconds) >= size) return rtf.format(Math.round(seconds / size), unit);
}
return rtf.format(seconds, "second");
}
const posted = new Date(Date.now() - 5 * 60 * 1000); // 5 minutes ago
console.log(timeAgo(posted)); // Output: 5 minutes ago
numeric: "auto" allows words like "yesterday" and "tomorrow"; without it you get "1 day ago". timeAgo picks the biggest unit that fits, which is perfect for comment timestamps.
Tip: Save and send dates as UTC with
toISOString()(like"2026-10-01T12:00:00.000Z"), and only turn them into local time when you show them to a person. This avoids most time zone bugs.
The Date object has many old quirks: months from 0, setters that change the original, and confusing parsing. The new Temporal API fixes them, with separate types for plain dates, times and zoned date-times. It is now available in several major browsers, but check current support on MDN's Temporal page before you rely on it. Everything in this lesson works in every browser today.
Practice exercise
Build a "subscription summary" for a page. Start with const start = new Date(2026, 0, 31);. Calculate the date 14 days later (end of the free trial) and show it with Intl.DateTimeFormat using dateStyle: "long". Then show how many days are left from today until the trial ends, and use Intl.RelativeTimeFormat to print something like "in 3 days" or "2 weeks ago".
Hint: reuse addDays and daysBetween from this lesson. Pass the day count to rtf.format(days, "day").
Key takeaways
- A
Dateis a moment stored as milliseconds since 1970 UTC; date-only ISO strings are read as UTC, so prefer numbers or full ISO strings. - Get methods read parts of a date; set methods change the original and roll over into the next month or year.
- Compare dates with
<,>orgetTime(), never===; subtract them for a difference in milliseconds. Intl.DateTimeFormatformats dates in any language and time zone, andIntl.RelativeTimeFormatwrites "3 hours ago".- Store dates as UTC ISO strings, and keep an eye on the new Temporal API.