Date & time
Create, compare, parse, calculate, and format dates with JavaScript's Date object while avoiding time zone and locale traps.
- 01Think in timestampsExplain that a Date holds one number: the milliseconds since the start of 1970 in UTC.
- 02Read date text safelyUse ISO strings, and know which ones mean UTC and which mean your local time.
- 03Do date math and show dates on purposeUse UTC methods for math that gives the same result on every computer, and Intl to show dates to people.
One number, many clocks
People think about dates in many ways: birthdays, deadlines, calendars, and time zones. A JavaScript Date is much simpler. Inside, it keeps just one number.
That number is called a timestamp. It counts milliseconds (thousandths of a second) from one fixed moment: midnight at the start of January 1, 1970, in UTC. You will see that moment written as 1970-01-01T00:00:00.000Z.
UTC stands for Coordinated Universal Time. It is the world's reference clock. It never moves for summer time. Every other time zone is described as a number of hours ahead of UTC or behind it.
Everything else a Date can do is a way to read that one number, change it, or write it as text.
This lesson uses ideas from earlier lessons:
- A timestamp is a plain number.
JSON.stringifyturns a Date into text, andJSON.parsecan turn that text back into a Date. You saw both in the JSON lesson.+dateturns a Date into its timestamp. That uses object to primitive conversion.<and>work on dates because dates can turn into numbers, as in the Comparisons lesson.
Picture one stopwatch for the whole world. It started at midnight UTC on January 1, 1970. It is the same stopwatch in Kolkata, Mumbai, New York, and Tokyo. It only counts milliseconds. A Date object holds one reading from that stopwatch.
- In real life: The stopwatch started at midnight UTC on January 1, 1970
- In JavaScript: Timestamp 0
- In real life: Every tick is one millisecond
- In JavaScript: The number a Date stores
- In real life: People in different cities say different clock times for the same moment
- In JavaScript: UTC methods and local methods show different hours
A JavaScript Date is an object that holds one moment in time as a timestamp: a number of milliseconds. Its methods (functions that belong to the object) do four kinds of jobs:
- read the moment in UTC,
- read the moment in your own time zone,
- change the moment,
- write the moment as text, for a computer or for a person.
Timestamps and Date.now()
INTERACTIVEDate.now() gives you the timestamp for right now. It is a plain number, not a Date object.
"Right now" is different for every reader. So the playground below works it out in your browser, after the page has loaded. It shows the same moment in several ways. The results marked in your time zone use your computer's clock settings.
const now = Date.now();const instant = new Date(now);instant.toISOString();instant.toString();instant.toLocaleString();instant.toLocaleString("en-US", { timeZone: "Asia/Tokyo" });loading...loading...loading...loading...loading...loading...loading...loading...Waiting for the page to load. The server and your browser could show different times, so only your browser makes these values.
Here is what the playground code does:
new Date(now)puts the timestamp inside a Date object.toISOString()writes the moment as text in UTC.toString()andtoLocaleString()write the moment using your computer's time zone and language settings.- The last line passes a language,
"en-US", and atimeZone. So it shows Tokyo time in US English on every computer.
When you need the same output on every computer, choose the language and the time zone yourself. Both toLocaleString and Intl.DateTimeFormat accept them as options.
JavaScript also has a second clock. The code below uses both clocks.
const savedAt = Date.now();const expiresAt = savedAt + 5000;console.log(expiresAt - savedAt); const started = performance.now();const finished = started + 500;console.log(finished > started);- Lines 1 to 3 use
Date.now(). It tells you when something happened, like the time on a wall clock. Use it for times you save, such as "this link expires at …". Line 2 adds 5000 milliseconds (5 seconds), so line 3 prints5000. - Lines 5 to 7 use
performance.now(). It counts milliseconds since this page opened. Use it to measure how long a task took. Line 6 adds 500, so line 7 printstrue.
The example adds fixed numbers so its output is always the same. In real code, you read the clock once before the work and once after it, then subtract.
Now step through a small program. It makes a timestamp, puts it in a Date, and then reads it back in three ways.
Step through a Date that holds one timestamp. Before line 3 runs, guess which month number it gives.
script
const moment = new Date(stamp);const month = moment.getUTCMonth();const iso = moment.toISOString();const number = +moment;Put on different glasses and the same thing looks different. The thing itself does not change. In the same way, the timestamp does not change when you ask for UTC or for local time. Only the words and numbers you see change. Think of two friends in different cities on one video call. One says it is 9 in the morning. The other says it is 7:30 in the evening. It is still the same call.
- In real life: The thing you look at
- In JavaScript: The timestamp
- In real life: UTC glasses
- In JavaScript: getUTCFullYear(), toISOString()
- In real life: Local glasses
- In JavaScript: getFullYear(), toString()
- In real life: Language glasses
- In JavaScript: toLocaleString() and Intl
Creating and parsing dates
INTERACTIVEThere are four common ways to create a Date:
new Date()for the moment right now.new Date(timestamp)when you already have the milliseconds.new Date(Date.UTC(...))when you have a year, a month, a day, and so on, and want them read as UTC. The result is the same on every computer.new Date("...")with an ISO string, for dates you saved as text.
An ISO string is date text in one standard shape, like "2024-03-10T09:00Z": the year, month, and day, then a T, then the time. The next section explains each part.
Each row of the table shows one way to create a Date. Read across: the code, what you get, and what to watch for.
| When you write | What you get | Watch out for |
|---|---|---|
new Date() | The moment right now. | It changes every millisecond, so a page should make it in the browser, not on the server. |
new Date(1710000000000) | A Date that holds this timestamp. | The number counts from UTC, even when local methods show a different clock time. |
new Date(2024, 0, 31) | January 31, 2024, at midnight in your time zone. | Months start at 0, so 0 is January. |
new Date(Date.UTC(2024, 0, 31)) | January 31, 2024, at midnight UTC. | Date.UTC gives back a number, not a Date. new Date puts it in a Date. |
new Date("2024-03-10T09:00Z") | 9:00 UTC on March 10, 2024. | Add Z or an offset like +05:30 (hours ahead of UTC) when the exact moment matters. |
Parsing means reading text, like "2024-03-10", and turning it into a Date. Parsing is where Date causes the most trouble. The rules for ISO strings are written down in the JavaScript standard. Other shapes, like "March 10, 2024" or "10/03/2024", have no fixed rules. Each browser may read them its own way.
Try the lab. Click each string. See whether it becomes a UTC time, a local time, a time with an offset, or an invalid date.
new Date("2024-03-10"); // date-only ISO: UTCnew Date("2024-03-10T09:00"); // no offset: local timenew Date("2024-03-10T09:00Z"); // Z: UTCnew Date("2024-03-10T09:00+05:30");new Date("March 10, 2024"); // non-ISO: avoid for datanew Date("not a date").getTime(); // NaNThe lab waits for the page to load, because some results depend on your time zone.
The next example puts a date-only string next to a string with a date and a time.
const dateOnly = new Date("2024-03-10");const meeting = new Date("2024-03-10T09:00");const localComponents = new Date(2024, 2, 10, 9);console.log(dateOnly.toISOString());console.log(meeting.getTime() === localComponents.getTime());- Line 1 has a date only. A date-only ISO string means midnight UTC.
- Line 2 has a date and a time, but no
Zand no offset. So it means 9:00 in your time zone. - Line 3 builds 9:00 on March 10 from numbers. Numbers passed to
new Dateare also read in your time zone. Month2is March. - Line 4 prints
2024-03-10T00:00:00.000Zon every computer. - Line 5 prints
trueon every computer. Lines 2 and 3 mean the same local moment, so their timestamps match.
The code doesn't print meeting.toISOString(). That UTC text would be different in each time zone.
"2024-03-10"has a date only. It means midnight UTC."2024-03-10T09:00"has a time but no offset. It means 9:00 in your time zone."2024-03-10T09:00Z"and"2024-03-10T09:00+05:30"each name one exact moment.- Text that isn't a date still gives a
Dateobject. ItsgetTime()isNaN("not a number").
ISO strings, Date.UTC and time zones
SORTISO 8601 is the name of the standard for date text like 2024-03-10T09:00:00.000Z. Read it from left to right:
2024-03-10is the year, the month, and the day.Tseparates the date from the time.09:00:00.000is hours, minutes, seconds, and milliseconds.Zmeans the time is in UTC.
Instead of Z, a string can end with an offset, like +05:30. It means "this clock time, in a place 5 hours and 30 minutes ahead of UTC". India uses that offset. A Z or an offset makes the string name one exact moment.
date.toISOString()Date.UTC(2024, 0, 31)new Date("2024-03-10")new Date("2024-03-10T09:00")date.getHours()date.setMonth(1)date.toLocaleString()Intl.DateTimeFormat("en-GB", { timeZone: "UTC" })
Sort each method or string by what its result depends on.
Date.UTC(year, monthIndex, day, hour) builds a timestamp from parts and reads them as UTC. The result is the same on every computer. Remember two things:
- The month is an index that starts at 0. January is
0and December is11. - It gives back a number. Wrap it in
new Date(...)when you want Date methods.
Getters, setters and overflow
STEP THROUGHHere, a getter is a method that reads one part of a date, like the year or the hour. Dates have two sets of getters:
- Local getters read the date in your time zone:
getFullYear(),getMonth(),getDate()(the day of the month),getDay()(the day of the week, with Sunday as 0), andgetHours(). - UTC getters read the date in UTC:
getUTCFullYear(),getUTCMonth(), and so on.
Use the UTC getters in tests. Their results don't depend on the computer's time zone.
const jan31 = new Date(Date.UTC(2024, 0, 31, 9));console.log(jan31.getUTCMonth()); // 0, Januaryconsole.log(jan31.getUTCDate()); // 31Line 1 makes January 31, 2024, at 9:00 UTC. Line 2 prints 0, the month index for January. Line 3 prints 31, the day of the month.
A setter is a method that changes one part, like setMonth() or setUTCDate(). A setter changes the Date object itself. It doesn't make a new one.
Setters also overflow: if the day you ask for doesn't exist, the extra days carry over into the next month. Ask for February 31 and you get a day in March. Sometimes that helps. Sometimes it is a bug.
Step through the code below and watch one Date change.
Watch setters change the same Date object. A day that doesn't exist carries over into the next month. UTC methods make the result the same on every computer.
script
due.setUTCMonth(due.getUTCMonth() + 1);due.setUTCDate(due.getUTCDate() + 7);console.log(due.toISOString());If two variables point to the same Date object, setMonth or setDate changes what both of them see. When you need to keep the original, make a copy first with new Date(oldDate.getTime()).
const original = new Date(Date.UTC(2024, 0, 31));const copy = new Date(original.getTime());copy.setUTCDate(copy.getUTCDate() + 1);console.log(original.toISOString());console.log(copy.toISOString());- Line 1 makes January 31, 2024, at midnight UTC.
- Line 2 makes a new Date from the original's timestamp. It is a separate object, not a second name for the same one.
- Line 3 adds one day to the copy only.
- Line 4 prints January 31 for the original.
- Line 5 prints February 1 for the copy.
Doing math with dates
To find the time between two exact moments, subtract one Date from the other. The result is in milliseconds. Divide it by 86_400_000, the number of milliseconds in 24 hours, to get days.
That works well for UTC times, like the moment a login expires. It doesn't always match calendar days in your own time zone. Many places move their clocks for summer time (this is called daylight saving time). On those days, a local day is 23 or 25 hours long.
const a = new Date("2024-06-01T00:00:00Z");const b = new Date("2024-06-01T00:00:00Z");console.log(a === b);console.log(a.getTime() === b.getTime());console.log(a < new Date("2024-06-02T00:00:00Z"));- Line 1 makes a Date for June 1, 2024, at midnight UTC.
- Line 2 makes a second Date for the same moment.
- Line 3 prints
false.===checks whetheraandbare the very same object. They are two separate objects. - Line 4 prints
true.getTime()gives each timestamp, and the two numbers are equal. - Line 5 prints
true.<turns both dates into timestamps, and June 1 comes before June 2.
a and b are two separate objects for the same moment. === asks "Is this the same object?" and the answer is no. Comparing timestamps asks "Is this the same moment?" and the answer is yes.So when you mean "the same moment", compare timestamps with getTime() or +date. To measure a time span, you subtract those same timestamps.
You can also move a date forward with UTC setters. This is the same code you stepped through earlier.
const due = new Date(Date.UTC(2024, 0, 31, 9));due.setUTCMonth(due.getUTCMonth() + 1);due.setUTCDate(due.getUTCDate() + 7);console.log(due.toISOString());Line 2 adds one month, and line 3 adds seven days. Line 4 prints 2024-03-09T09:00:00.000Z on every computer, because every step uses UTC.
Many of these problems are why JavaScript is getting Temporal, a newer set of date tools. The next lesson covers it. Temporal has a separate type for each of these:
- an exact moment,
- a calendar date with no time,
- a time zone,
- a length of time, such as 3 days.
Date tries to do all of these with one object.
Turning dates into text
Write dates one way for computers and another way for people:
toISOString()is for saving and sending data. It always uses UTC and always ends withZ.toJSON()callstoISOString(). That is whyJSON.stringifyturns a Date into an ISO string.toLocaleString(),toLocaleDateString(), andIntl.DateTimeFormatare for people. They write the date in a person's language and style.
const instant = new Date("2024-12-24T18:30:00Z");console.log(instant.toISOString());console.log(instant.toJSON());console.log(new Intl.DateTimeFormat("en-GB", { weekday: "long", day: "numeric", month: "long", year: "numeric", timeZone: "UTC"}).format(instant));console.log(instant.toLocaleDateString("en-GB", { timeZone: "UTC" }));- Line 1 makes a Date for December 24, 2024, at 18:30 UTC.
- Lines 2 and 3 print the same ISO text:
2024-12-24T18:30:00.000Z. - Lines 4 to 10 make a formatter with
Intl.DateTimeFormat."en-GB"means British English. The options ask for the weekday and the month as words, and for UTC. It printsTuesday, 24 December 2024. - Line 11 uses
toLocaleDateStringwith British English and UTC. It prints24/12/2024on every computer.
For pages people read, choose the language and the options on purpose. en-GB puts the day first, and en-US puts the month first. The Internationalization with Intl lesson covers formatting dates, numbers, lists, and plurals in depth.
Where you'll use this
Dates show up in forms, dashboards, calendars, logs, and data from APIs. They also decide when saved data is too old to use. A good habit has four steps:
- Save the moment as an ISO string.
- Turn it back into a Date when you need Date methods.
- Do math with timestamps or UTC methods.
- Turn it into text for people only at the end, when you show it.
For calendar dates, named time zones, and lengths of time, move on to Temporal instead of writing more helper code around Date.
Each row of the table is a common job. Read across to see a good first choice and why.
| The job | A good first choice | Why |
|---|---|---|
| Save a date in JSON | An ISO string from toISOString(). | It is UTC text that reads the same on every computer. |
| Show a date to a person | Intl.DateTimeFormat(locale, options). | People expect their own language and date order. |
| Check whether something has expired | getTime() or <. | You are comparing two exact moments. |
| Add calendar days in tests | UTC getters and setters. | The computer's time zone can't change the result. |
| Build new date features | Temporal. | It has separate types for dates, times, time zones, and lengths of time. |
Common mistakes with dates
"Months are numbered from 1."
In Date, months are numbered from 0. new Date(2024, 0, 31) is January 31.
"Every ISO string is UTC."
A date-only ISO string is UTC. A string with a date and a time but no offset is local time.
"A bad date string throws an error."
Usually it doesn't. You get an Invalid Date object, and its getTime() returns NaN.
"Adding a month stops at the last day of the month."
Setters overflow. January 31 plus one month asks for February 31, which carries over into March. The exact day depends on the year. With local setters, it can also depend on the time zone.
"toLocaleString is fine for tests."
Only when you pass a language and a timeZone. Without them, the result depends on the computer.
"Two Dates for the same moment are ===."
They are two separate objects, so === gives false. Compare their timestamps.
Each row of the table is one problem. Read across to see what surprises people and a safer habit.
| Problem | What surprises people | A safer habit |
|---|---|---|
| Month numbers | January is 0. | Name the variable monthIndex, or use ISO strings. |
| Parsing text | Text that isn't ISO is read differently in different browsers. | Accept only ISO strings for data. |
| Math in local time | Summer time changes make some days 23 or 25 hours long. | Use UTC for exact 24-hour days. Use Temporal for calendar math. |
| Formatting | The default output is different for each reader. | Pass a language and a timeZone. |
Practice: dates without surprises
5 EXERCISESPredict what line 2 prints. Remember how Date numbers its months.
const birthday = new Date(2024, 0, 31);
console.log(birthday.getMonth());It prints 0. Month indexes start at 0, so January is 0, February is 1, and December is 11.
Work out the number of days before you run the code.
const start = new Date("2024-06-01T00:00:00Z");
const end = new Date("2024-06-11T00:00:00Z");
console.log((end - start) / 86400000);It prints 10. The two moments are exactly ten 24-hour days apart. The result is the same on every computer because both strings end with Z.
Run the code and compare the two lines. Then change dateStyle to "medium" and run it again.
const instant = new Date("2024-12-24T18:30:00Z");
console.log(new Intl.DateTimeFormat("en-GB", { dateStyle: "full", timeZone: "UTC" }).format(instant));
console.log(new Intl.DateTimeFormat("en-US", { dateStyle: "full", timeZone: "UTC" }).format(instant));const instant = new Date("2024-12-24T18:30:00Z");
console.log(new Intl.DateTimeFormat("en-GB", { dateStyle: "full", timeZone: "UTC" }).format(instant));
console.log(new Intl.DateTimeFormat("en-US", { dateStyle: "full", timeZone: "UTC" }).format(instant));Both lines show the same moment. British English (en-GB) puts the day before the month. US English (en-US) puts the month before the day. The Intl lesson covers these options in depth.
This code should make December 5, 2024, but it makes a date in the next year. Which month number should December use?
// Bug: intended December 5, 2024
const launch = new Date(2024, 12, 5);
console.log(launch.toISOString());const launch = new Date(2024, 11, 5);Use month index 11 for December. Month index 12 overflows into January of the next year.
Why does this print true even though it makes two separate Date objects?
const a = new Date("2024-06-01T00:00:00Z");
const b = new Date("2024-06-01T00:00:00Z");
console.log(a.getTime() === b.getTime());const a = new Date("2024-06-01T00:00:00Z");
const b = new Date("2024-06-01T00:00:00Z");
console.log(a.getTime() === b.getTime());a and b are two different objects, but they hold the same timestamp. getTime() turns each one into its number, so the comparison is about the moment, not the object.
Quiz: check your understanding
7 QUESTIONSQuestion 1 of 7What does a JavaScript Date object keep inside?
Choose an answer to see the explanation.
Question 2 of 7What month number does this print?
Read the code, then predictconsole.log(new Date(Date.UTC(2024, 0, 31)).getUTCMonth());Choose an answer to see the explanation.
Question 3 of 7Which string means midnight UTC?
Choose an answer to see the explanation.
Question 4 of 7What does this check for an invalid date print?
Read the code, then predictconst d = new Date("not a date"); console.log(Number.isNaN(d.getTime()));Choose an answer to see the explanation.
Question 5 of 7What type does Date.UTC give back here?
Read the code, then predictconst stamp = Date.UTC(2024, 0, 1); console.log(typeof stamp);Choose an answer to see the explanation.
Question 6 of 7How should you check whether two Date objects are the same moment?
Choose an answer to see the explanation.
Question 7 of 7Which method always uses UTC and ends with a Z?
Choose an answer to see the explanation.
Key takeaways
- A
Dateholds one timestamp in milliseconds. Its methods choose how to read it. Date.now()andDate.UTC()give back numbers.new Date(...)gives back an object.- Months start at 0 when you pass numbers to
new Date,Date.UTC, and setters. - A date-only ISO string is UTC. A string with a date and a time but no offset is local time.
- For results that are the same on every computer, use UTC methods and pass a
timeZone. toISOString()is for computers.Intl.DateTimeFormatand thetoLocalemethods are for people.
Remember the one-liner.
A Date is one timestamp. You can read it in UTC, in your local time, or written for a person's language.
Up next: Temporal, newer date tools that fix many of Date's problems.