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

Problem

You need to get the current date or 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.

Table 4-1. Date methods for getting pieces of date information
Method Gets Possible values

getFullYear()

The year

A four-digit number like 2021

getMonth()

The month number

0 to 11, where 0 represents January

getDate()

The day of the month

1 to 31

getDay()

The day of the week

0 to 6, where 0 represents Sunday

getHours()

The hour of the day

0 to 23

getMinutes()

The minute

0 to 59

getSeconds()

The seconds

0 to 59

getMilliseconds()

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

Problem

You have date information in a string, but you want to convert it to a Date object so you can manipulate it in your code or perform date calculations.

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

Problem

You want to find a date that’s a specific number of days before or after another 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

Problem

You need to see if two Date objects represent the same calendar date, or determine if one date is before another.

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());

See Also

For more math with dates, see Recipes and .

Calculating the Time Elapsed Between Two Dates

Problem

You need to calculate how many days, hours, or minutes separate 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, use setHours() 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

Problem

You want to create a formatted string based on a Date object.

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.