Chapter 4. Dates
JavaScript has surprisingly capable date features, which are wrapped in the somewhat old-fashioned Date
object. As youâll see, the Date
object has quirks and hidden trapsâlike the way it counts months starting at 0 and parses year information differently depending on the locale settings of the current computer. But once you learn to navigate these stumbling blocks, youâll be able to accomplish a number of common, useful operations, like counting the days between two dates, formatting dates for display, and timing events.
Getting the Current Date and Time
Solution
JavaScript includes a Date
object that provides good support for manipulating date information (and more modest support for performing date calculations). When you create a new Date
object, it is automatically populated with the current day and time, down to the nearest millisecond:
const
today
=
new
Date
();
Now itâs simply a matter of extracting the information you want from your Date
object. The Date
object has a long list of methods that can help you in this task. Table 4-1 lists the most important methods. Notice that the counting used by different methods isnât always consistent. Months and weekdays are numbered starting at 0, while days are numbered starting at 1.
Method | Gets | Possible values |
---|---|---|
|
The year |
A four-digit number like 2021 |
|
The month number |
0 to 11, where 0 represents January |
|
The day of the month |
1 to 31 |
|
The day of the week |
0 to 6, where 0 represents Sunday |
|
The hour of the day |
0 to 23 |
|
The minute |
0 to 59 |
|
The seconds |
0 to 59 |
|
The milliseconds (one thousandth seconds) |
0 to 999 |
Hereâs an example that displays some basic information about the current date:
const
today
=
new
Date
();
console
.
log
(
today
.
getFullYear
());
// example: 2021
console
.
log
(
today
.
getMonth
());
// example: 02 (March)
console
.
log
(
today
.
getDay
());
// example: 01 (Monday)
// Do a little extra string processing to make sure minutes are padded with
// a leading 0 if needed to make a two-digit value (like '05' in the time 4:05)
const
hours
=
today
.
getHours
();
const
minutes
=
today
.
getMinutes
().
toString
().
padStart
(
2
,
'0'
);
console
.
log
(
'Time '
+
hours
+
':'
+
minutes
);
// example: 15:32
Note
The Date
methods listed in Table 4-1 exist in two versions. The versions shown in the table use the local time settings. The second set of methods adds the prefix UTC (as in getUTCMonth()
and getUTCSeconds()
). They use Coordinated Universal Time, the global time standard. If you need to compare dates from different time zones (or ones that have different conventions for following daylight saving time), you must use the UTC methods. Internally, the Date
object always uses UTC.
Discussion
The Date()
object has several constructors. The empty constructor creates a Date
object for the current date and time, as youâve just seen. But you can also create a Date
object for a different date by specifying the year, month, and day, like this:
// February 10, 2021:
const
anotherDay
=
new
Date
(
2021
,
1
,
10
);
Once again, be wary of the inconsistent counting (months start at 0, while days start at 1). That means the anotherDay
variable above represents February 10, not January 10.
Optionally, you can tack on up to four more parameters to the Date
constructor for hours, minutes, seconds, and milliseconds:
// February 1, 2021, at 9:30 AM:
const
anotherDay
=
new
Date
(
2021
,
1
,
1
,
9
,
30
);
As youâll see in this chapter, JavaScriptâs built-in Date
object has some well-known limitations and a few quirks. If you need to perform extensive date operations in your code, such as calculating date ranges, parsing different types of date strings, or shifting dates between time zones, the best practice is to use a tested third-party date library, such as day.js or date-fns.
See Also
Once you have a date, you may want to use it in date calculations, as explained in âComparing Dates and Testing Dates for Equalityâ. You may also be interested in turning a date into a formatted string (âFormatting a Date Value as a Stringâ), or a date-containing string into a proper Date
object (âConverting a String to a Dateâ).
Converting a String to a Date
Solution
If youâre fortunate, youâll have your date string in the ISO 8601 standard timestamp format (like â2021-12-17T03:24:00Zâ), which you can pass directly to the Date
constructor:
const
eventDate
=
new
Date
(
'2021-12-17T03:24:00Z'
);
The T in this string separates the the date from the time, and the Z at the end of the string indicates itâs a universal time using the UTC time zone, which is the best way to ensure consistency on different computers.
There are other formats that the Date
constructor (and the Date.parse()
method) may recognize. However, they are now strongly discouraged, because their implementations are not consistent across different browsers. They may appear to work in a test example, but they run into trouble when different browsers apply different locale-specific settings, like daylight saving time.
If your date isnât in the ISO 8601 format, youâll need to take a manual approach. Extract the different date components from your string, then use those with the Date
constructor. You can make good use of String
methods like split()
, slice()
, and indexOf()
, which are explored in more detail in the recipes in Chapter 2.
For example, if you have a date string in the format mm/dd/yyyy, you can use code like this:
const
stringDate
=
'12/30/2021'
;
// Split on the slashes
const
dateArray
=
stringDate
.
split
(
'/'
);
// Find the individual date ingredients
const
year
=
dateArray
[
2
];
const
month
=
dateArray
[
0
];
const
day
=
dateArray
[
1
];
// Apply the correction for 0-based month numbering
const
eventDate
=
new
Date
(
year
,
month
-
1
,
day
);
Discussion
The Date
object constructor doesnât perform much validation. Check your input before you create a Date
object, because the Date
object may accept values that you would not. For example, it will allow day numbers to roll over (in other words, if you set 40 as your day number, JavaScript will just move your date into the next month). The Date
constructor will also accept strings that may be parsed inconsistently on different computers.
If you attempt to create a Date
object with a nonnumeric string, youâll receive an âInvalid Dateâ object. You can test for this condition using isNaN()
:
const
badDate
=
'12 bananas'
;
const
convertedDate
=
new
Date
(
badDate
);
if
(
Number
.
isNaN
(
convertedDate
))
{
// We end up here, because the date object was not created successfully
}
else
{
// For a valid Data instance, we end up here
}
This technique works because Date
objects are actually numbers behind the scenes, a fact explored in âComparing Dates and Testing Dates for Equalityâ.
See Also
âFormatting a Date Value as a Stringâ explains the reverse operationâtaking a Date
object and converting it to a string.
Adding Days to a Date
Solution
Find the current day number with Date.getDate()
, then change it with Date.setDate()
. The Date
object is smart enough to roll over to the next month or year as needed.
const
today
=
new
Date
();
const
currentDay
=
today
.
getDate
();
// Where will be three weeks in the future?
today
.
setDate
(
currentDay
+
21
);
console
.
log
(
`Three weeks from today is
${
today
}
`
);
Discussion
The setDate()
method isnât limited to positive integers. You can use a negative number to shift a date backward. You may want to use the other setXxx() methods to modify a date, like setMonths()
to move it forward or backward one month at a time, setHours()
to move it by hours, and so on. All these methods roll over just like setDate()
, so adding 48 hours will move a date exactly two days forward.
The Date
object is mutable, which makes its behavior look distinctly old-fashioned. In more modern JavaScript libraries, you would expect a method like setDate()
to return a new Date
object. But what it actually does is change the current Date
object. This happens even if you declare a date with const
. (The const
prevents you from setting your variable to point to a different Date
object, but it doesnât stop you from altering the currently referenced Date
object.) To safely avoid potential problems, you can clone your date before operating on it. Just use Date.getTime()
to get the underlying millisecond count that represents your date and use it to create a new object:
const
originalDate
=
new
Date
();
// Clone the date
const
futureDate
=
new
Date
(
originalDate
.
getTime
());
// Change the cloned date
futureDate
.
setDate
(
originalDate
.
getDate
()
+
21
);
console
.
log
(
`Three weeks from
${
originalDate
}
is
${
futureDate
}
`
);
See Also
âCalculating the Time Elapsed Between Two Datesâ shows how to calculate the time period between two dates.
Comparing Dates and Testing Dates for Equality
Solution
You can compare Date
objects just like you compare numbers, with the <
and >
operators:
const
oldDay
=
new
Date
(
1999
,
10
,
20
);
const
newerDay
=
new
Date
(
2021
,
1
,
1
);
if
(
newerDay
>
oldDay
)
{
// This is true, because newerDay falls after oldDay.
}
Internally, dates are stored as numbers. When you use the <
or >
operator, they are automatically converted to numbers and compared. When you run this code, you are comparing the millisecond value for oldDay
(943,074,000,000) to the millisecond value for newerDay
(1,612,155,600,000).
The equality operator (=
) works differently. It tests the object reference, not the object content. (In other words, two Date
objects are equal only if you are comparing two variables that point to the same instance.)
If you want to test if two Date
objects represent the same moment in time, you need to convert them to numbers yourself. The clearest way to do this is by calling Date.getTime()
, which returns the millisecond number for a date:
const
date1
=
new
Date
(
2021
,
1
,
1
);
const
date2
=
new
Date
(
2021
,
1
,
1
);
// This is false, because they are different objects
console
.
log
(
date1
===
date2
);
// This is true, because they have the same date
console
.
log
(
date1
.
getTime
()
===
date2
.
getTime
());
Note
Despite its name, getTime()
does not return just the time. It returns the millisecond number that is an exact representation of that Date
objectâs date and time.
Discussion
Internally, a Date
object is just an integer. Specifically, itâs the number of milliseconds that have elapsed since January 1, 1970. The millisecond number can be negative or positive, which means that the Date
object can represent dates from the distant past (roughly 271,821 BCE) to the distant future (year 275,760 CE). You can get the millisecond number by calling Date.getTime()
.
Two Date
objects are only the same if they match exactly, down to the millisecond. Two Date
objects that represent the same date but have a different time component wonât match. This can be a problem, because you may not realize that your Date
object contains time information. This is a common issue when creating a Date
object for the current day (âGetting the Current Date and Timeâ).
To avoid this issue, you can remove the time information using Date.setHours()
. Despite its name, the setHours()
method accepts up to four parameters, allowing you to set the hour, minute, second, and millisecond. To create a date-only Date
object, set all these components to 0:
const
today
=
new
Date
();
// Create another copy of the current date
// The day hasn't changed, but the time may have already ticked on
// to the next millisecond
const
todayDifferent
=
new
Date
();
// This could be true or false, depending on timing factors beyond your control
console
.
log
(
today
.
getTime
()
===
todayDifferent
.
getTime
());
// Remove all the time information
todayDifferent
.
setHours
(
0
,
0
,
0
,
0
);
today
.
setHours
(
0
,
0
,
0
,
0
);
// This is always true, because the time has been removed from both instances
console
.
log
(
today
.
getTime
()
===
todayDifferent
.
getTime
());
Calculating the Time Elapsed Between Two Dates
Solution
Because dates are numbers (in milliseconds, see âComparing Dates and Testing Dates for Equalityâ), calculations with them are relatively straightforward. If you subtract one date from another, you get the number of milliseconds in between:
const
oldDate
=
new
Date
(
2021
,
1
,
1
);
const
newerDate
=
new
Date
(
2021
,
10
,
1
);
const
differenceInMilliseconds
=
newerDate
-
oldDate
;
Unless youâre timing short operations for performance testing, the number of milliseconds isnât a particularly useful unit. Itâs up to you to divide this number to convert it into a more meaningful number of minutes, hours, or days:
const
millisecondsPerDay
=
1000
*
60
*
60
*
24
;
let
differenceInDays
=
differenceInMilliseconds
/
millisecondsPerDay
;
// Only count whole days
differenceInDays
=
Math
.
trunc
(
differenceInDays
);
console
.
log
(
differenceInDays
);
Even though this calculation should work out to an exact number of days (because neither date has any time information), you still need to use Math.round()
on the result to deal with the rounding errors inherent to floating-point math (see âPreserving Accuracy in Decimal Valuesâ).
Discussion
There are two pitfalls to be aware of when performing date calculations:
-
Dates may contain time information. (For example, a new
Date
object created for the current day is accurate up to the millisecond it was created.) Before you count days, usesetHours()
to remove the time component, as explained in âComparing Dates and Testing Dates for Equalityâ. -
Calculations with two dates only make sense if the dates are in the same time zone. Ideally, that means you are comparing two local dates or two dates in the UTC standard. It may seem straightforward enough to convert dates from one time zone to another, but often there are unexpected edge cases with daylight saving time.
There is a tentative replacement for the aging Date
object. The Temporal
object aims to improve calculations with local dates and different time zones. In the meantime, if your date needs go beyond the Date
object, you can experiment with a third-party library for manipulating the date. Both day.js and date-fns are popular choices.
And if you want to use tiny time calculations for profiling performance, the Date
object is not the best choice. Instead, use the Performance
object, which is available in a browser environment through the built-in window.performance
property. It lets you capture a high-resolution timestamp thatâs accurate to fractions of a millisecond, if supported by the system. Hereâs an example:
// Get a DOMHighResTimeStamp object that represents the start time
const
startTime
=
window
.
performance
.
now
();
// (Do a time consuming task here.)
// Get a DOMHighResTimeStamp object that represents the end time
const
endTime
=
window
.
performance
.
now
();
// Find the elapsed time in milliseconds
const
elapsedMilliseconds
=
endTime
-
startTime
;
The result (elapsedMilliseconds
) is not the nearest whole millisecond, but the most accurate fractional millisecond count thatâs supported on the current hardware.
Note
Although Node doesnât provide the Performance
object, it has its own mechanism for retrieving high-resolution time information. You use its global process
object, which provides the process.hrtime.bigint()
method. It returns a timing readout in nanoseconds, or billionths of a second. Simply subtract one process.hrtime.bigint()
readout from another to find the time difference in nanoseconds. (Each millisecond is 1,000,000 nanoseconds.)
Because the nanosecond count is obviously going to be a very large number, you need to use the BigInt
data type to hold it, as described in âManipulating Very Large Numbers with BigIntâ.
See Also
âAdding Days to a Dateâ shows how to move a date forward or backward by adding to it or subtracting from it.
Formatting a Date Value as a String
Solution
If you print a date with console.log()
, youâll get the dateâs nicely formatted string representation, like âWed Oct 21 2020 22:17:03 GMT-0400 (Eastern Daylight Time).â This representation is created by the DateTime.toString()
method. Itâs a standardized, nonlocale-specific date string thatâs defined in the JavaScript standard.
Note
Internally, the Date
object stores its time information as a UTC time, with no additional time zone information. When you convert a Date
to a string, that UTC time is converted into a locale-specific time for the current time zone, as set on the computer or device where your code is running.
If you want your date string formatted differently, you could call one of the other prebuilt Date
methods demonstrated here:
const
date
=
new
Date
(
2021
,
0
,
1
,
10
,
30
);
let
dateString
;
dateString
=
date
.
toString
();
// 'Fri Jan 01 2021 10:30:00 GMT-0500 (Eastern Standard Time)'
dateString
=
date
.
toTimeString
();
// '10:30:00 GMT-0500 (Eastern Standard Time)'
dateString
=
date
.
toUTCString
();
// 'Fri, 01 Jan 2021 15:30:00 GMT'
dateString
=
date
.
toDateString
();
// 'Fri Jan 01 2021'
dateString
=
date
.
toISOString
();
// '2021-01-01T15:30:00.000Z'
dateString
=
date
.
toLocaledateString
();
// '1/1/2021, 10:30:00 AM'
dateString
=
date
.
toLocaleTimeString
();
// '10:30:00 AM'
Keep in mind that if you use toLocaleString()
or toLocaleTime()
, your string representation is based on the browser implementation and the settings of the current computer. Do not assume consistency!
Discussion
There are many possible ways to turn date information into a string. For display purposes, the toXxxString() methods work well. But if you want something more specific or fine-tuned, you may need to take control of the Date
object yourself.
If you want to go beyond the standard formatting methods, there are two approaches you can take. You can use the getXxx() methods described in âGetting the Current Date and Timeâ to extract individual time components from a date, and then concatenate those into the exact string you need. Hereâs an example:
const
date
=
new
Date
(
2021
,
10
,
1
);
// Ensure date numbers less than 10 are padded with an initial 0.
const
day
=
date
.
getDate
().
toString
().
padStart
(
2
,
'0'
);
// Ensure months are 0-padded and add 1 to convert the month from its
// 0-based JavaScript representation
const
month
=
(
date
.
getMonth
()
+
1
).
toString
().
padStart
(
2
,
'0'
);
// The year is always 4-digit
const
year
=
date
.
getFullYear
();
const
customDateString
=
`
${
year
}
.
${
month
}
.
${
day
}
`
;
// now customDateString = '2021.11.01'
This approach is extremely flexible, but it forces you to write your own date boilerplate, which isnât ideal because it adds complexity and creates room for new bugs.
If you want to use a standard format for a specific locale, life is a bit easier. You can use the Intl.DateTimeFormat
object to perform the conversion. Here are three examples that use locale strings for the US, the UK, and Japan:
const
date
=
new
Date
(
2020
,
11
,
20
,
3
,
0
,
0
);
// Use the standard US date format
console
.
log
(
new
Intl
.
DateTimeFormat
(
'en-US'
).
format
(
date
));
// '12/20/2020'
// Use the standard UK date format
console
.
log
(
new
Intl
.
DateTimeFormat
(
'en-GB'
).
format
(
date
));
// '20/12/2020'
// Use the standard Japanese date format
console
.
log
(
new
Intl
.
DateTimeFormat
(
'ja-JP'
).
format
(
date
));
// '2020/12/20'
All of these are date-only strings, but there are many other options you can set when you create the Intl.DateTimeFormat()
object. Hereâs just one example that adds the day of the week and month to the string, in German:
const
date
=
new
Date
(
2020
,
11
,
20
);
const
formatter
=
new
Intl
.
DateTimeFormat
(
'de-DE'
,
{
weekday
:
'long'
,
year
:
'numeric'
,
month
:
'long'
,
day
:
'numeric'
});
const
dateString
=
formatter
.
format
(
date
);
// now dateString = 'Sonntag, 20. Dezember 2020'
These options also give you the ability to add time information to your string with the hour
, minute
, and second
properties, which can be set to:
const
date
=
new
Date
(
2022
,
11
,
20
,
9
,
30
);
const
formatter
=
new
Intl
.
DateTimeFormat
(
'en-US'
,
{
year
:
'numeric'
,
month
:
'numeric'
,
day
:
'numeric'
,
hour
:
'numeric'
,
minute
:
'numeric'
});
const
dateString
=
formatter
.
format
(
date
);
// now dateString = '12/20/2022, 9:30 AM'
See Also
âConverting a Numeric Value to a Formatted Stringâ introduced the Intl
object and the concept of locale strings, which identify different geographic and cultural regions. For a comprehensive explanation of the 21 options the Intl.DateTimeFormat
object supports, see the MDN reference. Itâs worth noting that a few of these details are implementation dependent and may not be present on all browsers. (Examples include the timeStyle
, dateStyle
, and timeZone
properties, which we havenât discussed here.) As always, for complex Date
manipulation, consider a third-party library.
Get JavaScript Cookbook, 3rd Edition now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.