Working with Dates and Times

Overview

We have mentioned elsewhere that almost everything in JavaScript, with the exception of primitive data types, is an object. Not surprisingly then, JavaScript has a large number of built-in objects, one of which - the Date object - can be used to work with dates and times. Unlike most of the other built-in objects in JavaScript, the Date object does not have any unique properties of its own, but it does provide a number of methods that can be used to, among other things, create timestamps, represent date and time information in a specific format, or undertake time-related tasks.

The ability to store, retrieve, and output date and time information is an essential feature of many applications. It is also important that the underlying format used to store the data is platform-independent. This ensures that date and time information can be exchanged between applications, regardless of the hardware they are running on, or the system software running on that hardware.

The way in which date and time data is actually presented to the end-user can vary, depending on factors such as language, locale, and the requirements of the application, and we'll be exploring the various ways in which date and time data can be formatted. First, however, we'll take a brief look at the ISO 8061 standard, which defines a universally accepted machine- and human-readable format for the exchange of date- and time-related data.

JavaScript uses a simplified version of the ISO 8061 format to store datetime values independently of the high-level representation of that data. The term "datetime" reflects the fact that a datetime variable is a numerical value that represents a precise instant in time. That instant is defined as the number of milliseconds that have elapsed since 00:00:00 UTC (Coordinated Universal Time) on 1st January 1970.

This method of storing date and time information facilitates the creation of timestamps, and enables JavaScript to carry out arithmetic operations such as calculating the amount of time that has elapsed between two events, or comparing datetime values to determine the chronological order in which some number of related events occurred.

Retrieving individual date and time components (years, months, days, hours, minutes, seconds, and milliseconds) from a datetime value is handled by JavaScript behind the scenes. Developers do not usually need to concern themselves with interpreting low-level datetime values, and can instead concentrate on extracting, formatting, and manipulating the date and/or time components they require using the methods provided by the JavaScript Date object.

The same applies to the conversion of a string value containing date and time data entered by a user or generated programmatically into a datetime value in milliseconds. The Date object provides methods for parsing strings containing date and time information and converting that information into a datetime value. Note that dates and times in JavaScript are not separate entities. They simply occupy different positions within the JavaScript datetime string - a topic we'll discuss in due course.

The ISO 8601 standard

The international standard for the exchange and communication of date- and time-related data is the ISO 8601 standard, first published (as ISO 8601:1988) in 1988 by the International Organisation for Standardization (ISO), who continue to maintain the standard. The most recent version of the standard (ISO 8601-1:2019), and the fourth to date, was published in 2019, although an amendment was published in 2022 in an attempt to clarify some technical issues, remove some ambiguities from definitions, and reintroduce the "24:00:00" format referring to the instantaneous time at the end of a calendar day.

The ISO 8601 date format uses an all-numeric, most-to-least-significant notation in which temporal terms appear in descending order of duration. Which is a fancy way of saying that year comes before month, month comes before day, and so on. The representation of date and time information is restricted to Arabic numerals and a restricted set of characters that have a special meaning within the standard, such as "-" and ":".

Each type of temporal value (year, month, day etc.) appearing in the representation of a specific date and time must have the same fixed number of digits, padded with leading zeros if required. Two formats are accepted. The basic format, which has a minimal number of separators, and the extended format, which includes additional separators to make the date-time representation more human-readable.

In the ISI 8601 extended format, a hyphen ("-") is used to separate date values (i.e. year, month, week and day), while a colon (":") is used to separate time values (hours, minutes and seconds.). To illustrate the difference, here are the basic and extended representation for the date 31st May 2024:

20240531     (basic)
2024-05-31   (extended)

Any of the temporal values that make up the complete representation of a date and time may be omitted, providing the most-to-least-significant ordering is maintained, and providing the temporal values within the shortened representation are consecutive values in the full representation. In other words, "2024-05" is a valid (extended) representation that represents the month of May in the year 2024. The representation "2024-31" is invalid - It would be interpreted as an attempt to represent the 31st day of an unspecified month in the year 2024.

ISO 8601 specifies that a year must be represented by (at least) four digits YYYY - a requirement no doubt prompted by the infamous Y2K problem that caused such consternation among IT professionals in the period leading up to the start of the new millennium which occurred on 1st January 2000. Unless otherwise specified by an application, the four-digit format allows for the representation of any year between 0000 (1 BCE) and 9999 (9999 CE).

The full extended version of a calendar date takes the form YYYY-MM-DD, where YYYY represents the four-digit year as we have already seen, MM represents the two-digit month, and DD represents the two-digit day. Note that the basic representation of a calendar date in the form YYYYMM which omits the day is not allowed because it could be confused with the truncated version of a specific date YYMMDD, which is still widely used.

We stated earlier that a specific instant in time is stored as an integer value representing the number of milliseconds that have elapsed at that instant since midnight at the start of January 1, 1970 - a date commonly referred to as the epoch. If you have read the article "Working with Numbers" in this section, you will know that the range of integers that can safely be represented in JavaScript is between -9,007,199,254,740,992 and +9,007,199,254,740,992.

JavaScript uses a slightly smaller range of integer values in the range -8,640,000,000,000,000 to 8,640,000,000,000,000 to represent datetime values, which allows values in the range -100,000,000 to +100,000,000 days - in other words, a range that spans exactly 100 million days (just under 274 thousand years) either side of the epoch (one day is equal to 24 x 60 x 60 x 1000 = 86,400,000 milliseconds). The datetime values stored in a Date object can thus range from April 20th, 271821 BCE to September 13th, 275760 CE.

There are a couple of things to note here. The first is that, as may have occurred to you, the year could have more than four digits. The Date object can hold an expanded year, expressed as a "+" 0r "-" sign followed by six digits. The second thing, which may also have occurred to you, is that for dates prior to 00:00 on January 1st 1970, the datetime value must be stored as a negative offset (in milliseconds) from that date.

One final point is that the ISO 8601 standard is based on the proleptic Gregorian calendar. The Gregorian calendar, which is today used in most parts of the world, was introduced in October 1582 by Pope Gregory XIII as a replacement for the Julian calendar, principally to more accurately reflect the 365.422-day solar year (based on the time taken by the Earth to orbit the Sun) by adding an extra day to the year at regular four-year intervals. Years in which this occurs are called leap-years.

The proleptic Gregorian calendar is a backwards extension of the Gregorian calendar that accounts for dates prior to October 1582. There is inevitably some disparity between historical dates that were originally recorded prior to the adoption of the Gregorian calendar and their modern equivalent based on the proleptic Gregorian calendar. This is further complicated by the fact that many countries adopted the Gregorian calendar much later, and at different times. For example, Britain and America first adopted it in 1752, and Japan and China in 1872 and 1912 respectively.

Week numbers and ordinal dates

Many businesses use week numbers rather than calendar dates for planning activities because they provide a more convenient way to organise and track time. A year consists of either 52 or 53 full weeks, whereby week 01 is the week with the first Thursday of the year in it. Thus, if January 1st falls on a Monday, Tuesday, Wednesday or Thursday, it is in week 01. If it falls on a Friday, Saturday or Sunday, January 1st is in week 52 or 53 of the previous year.

ISO 8601 defines a standard week date representation for dates based on week numbers and weekdays. The year is represented by YYYY as for calendar dates. The week is represented by Www, where ww is the two-digit week number in the range 01 - 53, and the day is represented by D, a single digit in the range 1 - 7, with Monday being 1 and Sunday being 7. The following two examples represent the week number in which the date Friday 26th April 2024 falls, and the week number and day, respectively:

2024-W17
2024-W17-5

The ISO 8601 week-numbering year starts on the first Monday of week 01 and ends on the last Sunday before the next week-numbering year starts, which means that there are no gaps between one week-numbering year and the next. It also means that there can be up to three days in the first week of the week-numbering year that fall within the previous calendar year. There can also be up to three days in the last week-numbering week of the year that fall in the next calendar year.

If three days of the first week in the week-numbering year fall within the previous calendar year, they will be Monday, Tuesday and Wednesday. If three days of the last week in the week-numbering year fall within the next calendar year, they will be Friday, Saturday and Sunday. All Thursdays in a given week-numbering year will always be in the same calendar year.

ISO 8601 also defines a standard ordinal date, which represents a date using the year, as YYYY, and the number of days that have elapsed since the beginning of that year, as DDD, from 001 to 365 (or 366 in a leap year). The following examples represent basic and extended versions of the ordinal date on which Friday 26th April 2024 falls:

2024117
2024-117

JavaScript does not natively support a date and time representation based on week numbers or ordinal dates, so if an application needs to be able to represent dates in either of these formats, we will need to improvise a little. The Date object does not provide methods for extracting the week or day number for a given date. We will be exploring ways in which these values can be calculated in due course.

ISO 8601 time formats

ISO 8601 uses a 24-hour time system in which the basic format is Thhmmss and the extended format is Thh:mm:ss. The order is important - the standard requires the same most-to-least-significant notation used for dates, whereby hours come before minutes, and minutes come before seconds.

The "T" at the beginning denotes a time, which is followed by three two-digit numbers, zero padded if necessary, representing hours (00 to 23), minutes (00 to 59) and seconds (00 to 59). None of these time values can be specified on its own. As a minimum, both hours and minutes must be used (hhmm), and seconds must be used together with hours and minutes (hhmmss).

Note that a value of 24 may be used for the hour to represent midnight at the end of a calendar day (see below). A value for seconds of 60 is also allowed when it is necessary to represent a leap second. This is a one-second adjustment applied to UTC every other year or so to reconcile the precise time, as measured by atomic clocks, with the observed solar time, which can drift away from UTC slightly due to the gradual slowing of the Earth's rotation.

The "T" may be omitted in the extended version but this is only allowed in the basic version if there is no possibility of confusing times with dates. The beginning of a calendar day is expressed as T000000 (basic) or T00:00:00 (extended). The end of a calendar day is expressed as T240000 or T24:00:00. The end of one calendar day, and the beginning of the next calendar day, thus occur at the same instant in time.

The standard supports the addition of a decimal fraction to the smallest time value in the representation, should this be required by an application. Fractional values for hours, minutes and seconds can consist of one or more digits. If used with hours, the time representation cannot contain a value for minutes or seconds. If used with minutes, it cannot contain a value for seconds. The separator for fractional values can be either a comma or a period.

The time value 10.35 and 30.75 seconds could be represented in ISO 8601format as:

T103530.750     (basic format with period fractional separator)
T103530,750     (basic format with comma fractional separator)
T10:35:30.750   (extended format with period fractional separator)
T10:35:30,750   (extended format with comma fractional separator)

Note that we have used three decimal digits to represent the fractional part of the seconds value. This is not a requirement - it simply reflects the fact that a JavaScript datetime value representing a given instant in time is determined by the number of milliseconds (thousandths of a second) that have elapsed since 00:00:00 UTC (Coordinated Universal Time) on 1st January 1970 - a moment in time known as the epoch. This way of measuring time is sometimes known as Unix epoch time because it was first used on Unix-based computer systems.

The datetime string format

A single moment in time can be represented by concatenating a time value with a date value, with the letter "T" placed between the two values to act as a separator. Both formats (basic and extended) may be used, but both date and time must be expressed using the same format (either all basic or all extended).

The degree of precision for the time value may vary, but the date must be represented in full, regardless of whether it is represented as a calendar date, or using week numbers, or as an ordinal date. For the moment, we will limit our discussion of datetime values to those expressed using calendar dates. The representation of a datetime with millisecond precision could be expressed using either of the following formats:

YYYYMMDDThhmmss.sss
YYYY-MM-DDThh:mm:ss.sss

Less precise datetime values may also be expressed by omitting one or more values from the right-hand side of the datetime string. For example, the following basic and extended datetime formats are both valid (note however that, as we mentioned earlier, hours cannot be used without minutes):

YYYYMMDDThhmmss
YYYY-MM-DDThh:mm:ss

YYYYMMDDThhmm
YYYY-MM-DDThh:mm

A time zone designator (TZD) may also be appended to an ISO 8601 datetime string in order for localised date and time information can be calculated and displayed (we'll talk about time zones in more detail shortly). The TZD is expressed as either "Z" (zero offset from UTC), or as a value that indicates how far ahead of or behind UTC a time zone is, in the form +hh:mm or -hh:mm.

The complete JavaScript datetime string can be defined as follows:

YYYY-MM-DDTHH:mm:ss.sssZ

or

±YYYYYY-MM-DDTHH:mm:ss.sssZ

where:

JavaScript's datetime string format is a simplified version of the extended UTC datetime format defined in the ISO 8601 standard. The datetime value stored by the Date object always contains UTC date and time values. Whenever these values are used to display a date and time on a user's device, the host environment adds the appropriate (positive or negative) offset to the UTC datetime value, depending on the user's locale.

To demonstrate, let's suppose its 9:00 a.m. in New York on 6th May 2024. New York is on Eastern Daylight Time (EDT) at that time of year, which is UTC-4. We could do something like this:

const NYTime = new Date("2024-05-06T09:00:00-04:00");
console.log(NYTime);
// Date Mon May 06 2024 15:00:00 GMT+0200 (Central European Summer Time)

In this example, the Date object stores the date and time as a UTC datetime value using a positive offset of four hours to compensate for the fact that New York is four hours behind UTC (as per the time zone designator provided). When a user's device displays the datetime value, it will do so using the stored UTC datetime value plus the (positive or negative) offset appropriate for the user's locale.

As you can see, in the above example the locale falls within the Central European Summer Time zone, which is UTC+2, so a total of six hours is added to the time in New York to get the local time (generally speaking, we rarely need to specify time zone information in this way).

Creating a Date object

We have already seen a few examples of how a Date object is created using the Date() constructor together with the new keyword. We can create a Date object that contains the current date and time using the Date() constructor without any arguments, or we can pass date and time information as an argument to the Date() constructor, in the form of a JavaScript datetime string, in order to create a Date object that represents a specific moment in time. For example:

const now = new Date();
console.log(now);
// Date Tue Sep 10 2024 20:14:03 GMT+0100 (British Summer Time)

const zeroHour = new Date("1970-01-01T00:00:00Z");
console.log(zeroHour);
// Date Thu Jan 01 1970 01:00:00 GMT+0100 (Greenwich Mean Time)

The obvious question that arises from the second example above is why JavaScript returns Thu Jan 01 1970 01:00:00 GMT+0100 (Greenwich Mean Time) rather than Thu Jan 01 1970 00:00:00 GMT+0000 (Greenwich Mean Time). The answer is that between October 1968 and October 1971, Britain used British Standard Time (BST), which meant that the time remained on GMT+0100 all year round.

The following syntax, using comma-delimited integer values to represent year, month, day, hour, minute and second values, also works (note that months are indexed from zero):

const zeroHour = new Date(70, 0, 1, 0, 0, 0);
console.log(zeroHour);
// Date Thu Jan 01 1970 00:00:00 GMT+0100 (Greenwich Mean Time)

As does this more verbose syntax:

const zeroHour = new Date("January 1 1970 00:00:00");
console.log(zeroHour);
// Date Thu Jan 01 1970 00:00:00 GMT+0100 (Greenwich Mean Time)

When creating a Date object with specific arguments, time values may be omitted (the hour, minute and second values will all default to 00), but a valid date must be used or JavaScript will report an invalid date. For example, this is OK:

const myBirthday = new Date("1955-02-25");
console.log(myBirthday);
// Date Fri Feb 25 1955 00:00:00 GMT+0000 (Greenwich Mean Time)

But this will create an error:

const badDate = new Date("1955-13-01");
console.log(badDate);
// Invalid Date

Date and time values may be omitted from right to left, depending on the required precision. The following examples will all produce valid datetime values (note that time values must have both hours and minutes as a minimum):

const someDate = new Date("1955-02-25T12:30:45");
console.log(someDate);
// Date Fri Feb 25 1955 12:30:45 GMT+0000 (Greenwich Mean Time)

const someDate = new Date("1955-02-25T12:30");
console.log(someDate);
Date Fri Feb 25 1955 12:30:00 GMT+0000 (Greenwich Mean Time)

const someDate = new Date("1955-02-25");
console.log(someDate);
Date Fri Feb 25 1955 00:00:00 GMT+0000 (Greenwich Mean Time)

const someDate = new Date("1955-02");
console.log(someDate);
Date Tue Feb 01 1955 00:00:00 GMT+0000 (Greenwich Mean Time)

const someDate = new Date("1955");
console.log(someDate);
Date Sat Jan 01 1955 00:00:00 GMT+0000 (Greenwich Mean Time)

From these examples, we see that a Date object will store default values for any date or time values not supplied. When we only supply the year, for example, the UTC datetime value stored will represent a time of 00:00:00 and a date of 1st January of that year by default. When the Date() constructor is called with no arguments, it creates a Date object that contains the current date and time, according to the system clock, to the nearest millisecond.

You can also create a Date object by passing a value representing the number of milliseconds elapsed since (or prior to) the epoch (00:00:00 GMT on January 1, 1970). For example:

const epochPlus1E12 = new Date(1e+12);
console.log(epochPlus1E12);
// Date Sun Sep 09 2001 02:46:40 GMT+0100 (British Summer Time)

const epochMinus1E12 = new Date(-1e+12);
console.log(epochMinus1E12);
// Date Sun Apr 24 1938 23:13:20 GMT+0100 (British Summer Time)

For the sake of completeness we should also mention that a 2-digit value is also accepted as the year argument passed to the Date() constructor for reasons of backwards compatibility ("75" would be interpreted as "1975", for example), but it is considered good practice to always use four digits.

Timestamps

We typically create Date objects in order to store the precise instant in time at which some event has occurred in the past, or is expected to occur in the future. That instant in time is stored as an integer value representing the total time difference, in milliseconds, between the occurrence of that event and a point in time known as the epoch - 00:00:00 GMT on January 1st, 1970. This integer value is known as a timestamp. For events that occurred prior to the epoch, the timestamp will have a negative value. For events that have occurred since the epoch, it will have a positive value.

Even though the Date object stores a timestamp as an integer value, we can't use the equality operators (=== or ==) to check whether two Date objects both reference the same date and time because JavaScript objects are compared by reference, rather than value. This is illustrated by the following code:

const dateOne = new Date("2024-01-01");
const dateTwo = new Date("2024-01-01");

console.log(dateOne);
// Date Mon Jan 01 2024 00:00:00 GMT+0000 (Greenwich Mean Time)
console.log(dateTwo);
// Date Mon Jan 01 2024 00:00:00 GMT+0000 (Greenwich Mean Time)
console.log(dateTwo === dateOne); // false

We can extract the timestamp from a Date object using its valueOf() method. Consider the following code:

const dateOne = new Date("2024-01-01");
const dateTwo = new Date("2024-01-01");

let timeStampOne = dateOne.valueOf();
let timeStampTwo = dateTwo.valueOf();

console.log(timeStampOne); // 1704067200000
console.log(timeStampTwo); // 1704067200000
console.log(timeStampOne === timeStampTwo); // true

The timestamp values held by the variables timeStampOne and timeStampTwo are both represented by the same integer value, so testing these variables for equality will yield the result true, because we are comparing two variables of type number (a primitive data type as opposed to an object) that hold the same value.

We could also use the Date object's getTime() method to achieve the same results:

const dateOne = new Date("2024-01-01");
const dateTwo = new Date("2024-01-01");

let timeStampOne = dateOne.getTime();
let timeStampTwo = dateTwo.getTime();

console.log(timeStampOne); // 1704067200000
console.log(timeStampTwo); // 1704067200000
console.log(timeStampOne === timeStampTwo); // true

We can create a timestamp for the occurrence a specific event without creating a Date object by calling the static Date.now() method when that event occurs. For example:

this.addEventListener("load", getTimeStamp);

function getTimeStamp() {
const dateNow = new Date();
const timeStamp = Date.now();
console.log(dateNow);
// Date Wed Sep 11 2024 07:15:22 GMT+0100 (British Summer Time)
console.log("dateNow has type: " + typeof dateNow);
// dateNow has type: object
console.log(timeStamp);
// 1726035322389
console.log("timeStamp has type: " + typeof timeStamp);
// timeStamp has type: number
}

As you can see, the value returned by Date.now() is an integer, and has the type number, whereas the value returned by the Date() constructor (when used with the new keyword) has the type object. If the Date() constructor is used without the new keyword, the value returned will be a string representing the current date and time:

this.addEventListener("load", getDateStr);

function getDateStr() {
const dateNow = Date();
console.log(dateNow);
// Wed Sep 11 2024 07:17:08 GMT+0100 (British Summer Time)
console.log("dateNow has type: " + typeof dateNow);
// dateNow has type: string
}

Accessing datetime components

As we have seen, we can create a Date object by passing date and time values to the Date() constructor. We might do this for any number of reasons - for example, to store an employee's date of birth in a personnel record, or to schedule the start date of some planned project or event. There are other situations in which we want to record the date and time at which some random event occurs dynamically, by calling the Date() constructor without arguments when that event occurs, in order to create a timestamp.

Of course, there is little point in creating a timestamp unless we can retrieve the date and time information it represents as and when it is needed. It is also often the case that we only need to retrieve part of the information stored in a Date object, such as a calendar date or the time of day. We might also wish to retrieve various date and time values separately in order to be able to present the date and/or time information to the user in a specific format.

Fortunately, the Date object provides a number of methods that enable us to retrieve individual date and time components. The following code demonstrates how some of these methods might be used:

const summerSolstice24 = new Date("2024-06-20T20:51:00Z");

const zoneOffset = summerSolstice24.getTimezoneOffset() / 60;
const solYear = summerSolstice24.getFullYear();
const solMonth = summerSolstice24.getMonth();
const solDate = summerSolstice24.getDate();
const solDay = summerSolstice24.getDay();
const solHour = summerSolstice24.getHours() + zoneOffset;
const solMin = summerSolstice24.getMinutes();


const daysOfWeek = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
const months = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"]

let solStr = "This year's summer solstice falls on ";
solStr += daysOfWeek[solDay - 1] + ", ";
solStr += months[solMonth] + " " + solDate + " " + solYear;
solStr += " at " + solHour + ":" + solMin + " GMT";

console.log(solStr);
// This year's summer solstice falls on Thursday, June 20 2024 at 20:51 GMT

The first line of code here creates the Date object summerSolstice24 by passing the extended datetime string "2024-06-20T20:51:00Z" as an argument to the Date() constructor. We then use several of the methods provided by the Date object to retrieve the information we need from the newly created Date object instance, starting with the time zone offset (i.e. the difference between the local time and GMT) in minutes.

Note that the getMonth() method returns the month number as an integer in the range 0-11, so we can use that value without modification to index into the months array and retrieve the name of the month. The getDay() method also returns an integer value in the range 0-7, where 0 represents Sunday, 1 represents Monday, and so on. We can use this value in similar fashion to retrieve the correct day of the week from the daysOfWeek array.

The values returned by the getFullYear(), getDate(), getHours() and getMinutes() methods can be used without modification. We subsequently use the retrieved date and time elements to construct a user-friendly message informing the user of the day, date, and time (GMT) at which the summer solstice will occur in 2024.

Note that, although Greenwich Mean Time (GMT) is sometimes assumed to be synonymous with Coordinated Universal Time (UTC), the latter is a time standard used to synchronize time globally, whereas GMT is a specific time zone used by some countries, including the UK. For most practical purposes, it is safe to assume that times expressed as GMT will match those expressed as UTC.

Comparing dates

Sometimes, we want to compare two or more timestamps in order to sort them into the correct chronological order. A computerised library system, for example, might compare the date on which a book is due to be returned with the current date in order to determine whether or not the book is overdue. A warehouse stock control system might compare the production dates of different batches of the same materials in order to ensure that older materials are used first.

The ability to compare timestamps is also important when we want to plan things such as time-sensitive events, meeting schedules, and timetables. We have seen how timestamps can be created automatically at the instant some event occurs, but we can also create timestamps for planned events that have not yet occurred. There are two factors that determine the outcome when comparing timestamps. The first is the precision with which each datetime is specified. The second is the precision with which we want to compare the two timestamps.

If we create a record of when some historical event occurred, or schedule some future event such as a conference or business meeting, we can control the precision with which the date and time information associated with these events is stored in a Date object. The question then becomes one of how precisely we need to know when an event has occurred or will occur. When dealing with historical events, for example, it is often sufficient to record the date on which some event occurred.

If we create a timestamp dynamically by calling Date.now() at the exact moment some event occurs, it allows us to determine when that even occurred to an accuracy of one millisecond. This is useful when we want to find out how much time has elapsed between two events that occur in rapid succession, such as the start and end of a file download or some other time critical operation of relatively short duration. Often, however, we are only interested in comparing the dates on which two or more events occurred.

Given two events, A and B, we often want to determine whether events A and B occur on the same day or on different days, and if they occur on different days, does A occur before B or vice versa. For two or more events that happen on the same day, we might also be interested in determining the chronological order in which those events occur, but for now we'll only concern ourselves with comparing dates. Consider the following code:

const date1 = new Date("2024-06-30");
const date2 = new Date("2024-06-30");
console.log(date1 === date2);
// false

As we mentioned earlier in this article, it is not possible to test two objects for equality using the equality operator because JavaScript objects are compared by reference, rather than value. So, even though the date1 and date2 objects both have the same value, we cannot compare them directly. We can't therefore use any of JavaScript's equality or inequality operators (==, ===, != or !==) to compare two Date objects. We can, however, use JavaScript's other relational operators (>, <, >= and <=), as the following code demonstrates:

function dateCompare(date1, date2) {
const d1 = new Date(date1);
const d2 = new Date(date2);
if (d1 > d2) {
console.log(date2 + " occurs before " + date1);
}
else if (d2 > d1) {
console.log(date1 + " occurs before " + date2);
}
else {
console.log(date2 + " is the same as " + date1);
}
}

dateCompare("2023-06-30", "2024-01-01");
// 2023-06-30 occurs before 2024-01-01
dateCompare("2024-01-01", "2023-12-31");
// 2023-12-31 occurs before 2024-01-01
dateCompare("2024-01-01", "2024-01-01");
// 2024-01-01 is the same as 2024-01-01
dateCompare("2024-01-01T06:30", "2024-01-01T08:30");
// 2024-01-01T06:30 occurs before 2024-01-01T08:30
dateCompare("2024-06-30T06:30", "2024-06-30T06:30");
// 2024-06-30T06:30 is the same as 2024-06-30T06:30

This works because the greater than (>), greater than or equal (>=), less than (<), and less than or equal (<=) relational operators all call the valueOf() method on their operands behind the scenes before carrying out the comparison. This enables us to compare dates without first explicitly calling the valueOf() method. In the above example, we essentially establish the equality of two dates by default - if we compare two dates, and neither date is greater than the other, then they must have the same value.

Of course, there will be situations in which we don't know the precision with which a Date object has been created. If the date and time at which an event occurs is stored in a Date object dynamically by calling the Date() constructor (with the new keyword) without arguments, the date stored in that object will have millisecond precision. Comparing two dates that have been created in this fashion will invariably result in inequality, since the chances of the two events occurring at the exact same instant in time is remote, to say the least.

If we simply want to determine whether two events occurred in a certain chronological order regardless of how brief the time interval separating them is, this will not cause a problem. On the other hand, if we want to establish a chronological ordering based purely on calendar dates, we need to ignore any stored time values so that events that occur on the same date are evaluated as being equal.

If we only want to establish equality (or lack thereof) based on calendar dates, we can get around potential problems caused by dynamically created Date objects by using JavaScript's getFullYear(), getMonth(), and getDate() methods to extract the year, month and day components from the two Date objects we want to compare. If the values match, we know the two events occurred on the same date. Consider the following code:

const dateA = new Date();
const dateB = new Date(dateA.valueOf() + 1);

console.log(dateA.getTime());
// 1724326949102
console.log(dateB.getTime());
// 1724326949103
console.log(dateA.valueOf() === dateB.valueOf());
// false

function getVal(date) {
let y = date.getFullYear();
let m = date.getMonth();
let d = date.getDate();
const dateOnly = new Date(y, m, d);
return dateOnly.valueOf();
}

console.log(getVal(dateA));
// 1724277600000
console.log(getVal(dateB))
// 1724277600000
console.log(getVal(dateA) === getVal(dateB));
// true

In the above example, dateB has a timestamp that is 1 millisecond greater than dateA, so if we compare the two timestamps using the equality operator the result is guaranteed to be false, even though the interval separating the datetime values we are comparing is just one thousandth of a second.

The getVal() function extracts the year, month and date values from the Date object passed to it as an argument, and creates a new Date object using those values. It then returns the new Date object's timestamp. The getVal() function will return the same timestamp for all Date objects passed to it that represent the same calendar date, allowing us to test two Date objects for equality based purely on the date portions of the Date objects being compared.

Date arithmetic

We typically perform date arithmetic when we want to determine how much time has elapsed, or will elapse, between two dates, or between two instants in time. We might, for example, want to determine a person's current age, given their date of birth. This would involve subtracting their date of birth from the current date using date arithmetic to see how many years (and possibly also months and days) have elapsed since they were born.

We might also want to measure much smaller time intervals, such as the time taken for a digital file to download from a network server to a client computer. This would typically be a matter of minutes, seconds or even milliseconds, depending on the size of the file and the network conditions prevailing at the time the file is being downloaded.

We know that date and time information is stored in a JavaScript Date object as an integer value - the number of milliseconds that has elapsed between some event and 00:00:00 GMT on January 1st 1970, otherwise known as the epoch. It should therefore be relatively easy to calculate the difference between two timestamps in milliseconds, which is great if we are measuring relatively short time intervals of a few hours, minutes, or seconds - or even fractions of a second.

If we are working with much longer intervals covering days, weeks, months or years, we are probably not going to think in terms of milliseconds. When calculating someone's age from their date of birth, for example, we tend to think in terms of years, or maybe years and months. In most cases we simply want to know a person's current age in years. This can apply to many situations, such as deciding whether somebody is old enough to drive a motor vehicle or vote, or how long they still have to work before being able to retire.

The code below creates a web page containing a simple form that requires the user to enter their date of birth. The JavaScript code adds an event listener to the form's submit event that calls the calculateAge() event handler function, which calculates the user's age in years and displays it below the form. If no date is entered, or the date entered is not a valid date, an error message is displayed instead. Here is the code:

<!doctype html>
<html lang="en">

<head>
<meta charset="utf-8">
<title>JavaScript Demo 67</title>
<style>
h1, p { text-align: center; }
form {
max-width: max-content;
margin: auto;
border: solid 1px;
padding: 0.5em;
}
td { padding: 0.5em 0.5em; }
label span {
min-width: 100px;
display: inline-block;
}
</style>
</head>

<body>

<h1>Age Calculator</h1>

<p>Enter your date of birth:</p>

<form id="frm">
<table>
<tr>
<td>
<label><span>Date of Birth: </span><input type="date" name="dob"></label>
<td>
</tr>
<tr>
<td>
<input type="submit" value="Calculate age">
</td>
</tr>
</table>
</form>

<p id="output"></p>

<script>
const frm = document.getElementById("frm");
const out = document.getElementById("output");
frm.addEventListener("submit", calculateAge);

function calculateAge() {
event.preventDefault();
let dateStr = frm.dob.value;
const dob = new Date(dateStr);
if (isNaN(dob)) {
out.innerHTML = "Please enter a valid date.";
return;
}
const today = new Date();
let age = today.getFullYear() - dob.getFullYear();
let m = today.getMonth() - dob.getMonth();
if(m < 0 || (m === 0 && today.getDate() < dob.getDate())) {
age--;
}
out.innerHTML = "Your current age is: " + age + ".";
}
</script>

</body>
</html>

Copy and paste this code into a new file in your HTML editor, save the file as javascript-demo-67.html, and open the file in a web browser. Enter your date of birth in the input field and click on the "Calculate age" button. Depending on the date you entered, you should see something like the illustration below.


Your date of birth is used to find your current age

Your date of birth is used to find your current age


The age calculation is handled by the calculateAge() function using two dates - the current date, and the user's date of birth. The date entered by the user as their date of birth is passed to the Date() constructor, which creates a new Date object and assigns it to the variable dob:

let dateStr = frm.dob.value;
const dob = new Date(dateStr);

If dob does not hold a valid date, a message is displayed asking the user to enter a valid date, and the calculateAge() function returns immediately:

if (isNaN(dob)) {
out.innerHTML = "Please enter a valid date.";
return;
}

If dob holds a valid date, the Date() constructor is called without arguments to create a Date object variable called today, which holds the current date. The getFullYear() method is then called on both Date objects to extract two integer values representing the years in which these dates occur. The difference between these two values will be another integer value, which is assigned to the variable age:

let age = today.getFullYear() - dob.getFullYear();

If the month in which the user was born comes after the current month, then the user's age (in complete years) will be one less than the value stored in the variable age, so we use the following code to determine whether or not that is the case, and if so, decrement the value of age by one:

let m = today.getMonth() - dob.getMonth();
if(m < 0 || (m === 0 && today.getDate() < dob.getDate())) {
age--;
}

Let's look at a somewhat more complicated example of how date arithmetic works. We will create a web page that displays the current date, and the current time in hours, minutes and seconds, updated at intervals of one second (1000 ms). But we'll go a bit further, and include a countdown to a specific event that occurs on the same date in each calendar year (we've chosen Christmas day, but you can substitute this with any event you like as long as it occurs on the same day each year). Here is the code:

<!doctype html>
<html lang="en">

<head>
<meta charset="utf-8">
<title>JavaScript Demo 68</title>
<style>
h1, h2, p { text-align: center; }
#date, #time, #countdown {
margin: 1em 0.25em;
font-size: 32px;
color: white;
}
#countdown { white-space: pre-wrap; }
#display {
background-image: url('https://www.technologyuk.net/assets/demo-images/bgChristmas.jpg');
background-position: center;
text-align: center;
width: 640px;
max-width: 100%;
height: 320px;
margin: auto;
border: solid 1px;
}
</style>
</head>

<body>

<h1>Digital Clock/Calendar</h1>
<h2>with Christmas Countdown</h2>

<div id="display">
<p id="date"></p>
<p id="time"></p>
<p id="countdown"></p>
</div>

<p>
Image credit: <a class="inline" href="https://unsplash.com/@anniespratt" target="_blank">Annie Spratt - Unsplash</a>
</p>v
<script>
function updateDisplay() {
const dateNow = new Date();

let dateOut = document.getElementById("date");
let timeOut = document.getElementById("time");
let countdownOut = document.getElementById("countdown");

let zoneOffset = dateNow.getTimezoneOffset()/60;

let hh = dateNow.getHours().toString().padStart(2, "0");
let mm = dateNow.getMinutes().toString().padStart(2, "0");
let ss = dateNow.getSeconds().toString().padStart(2, "0");

let year = dateNow.getFullYear();
let month = dateNow.getMonth() + 1;
let day = dateNow.getDate();

let currentDate = "Date: " + day.toString().padStart(2, "0");
currentDate += "-" + month.toString().padStart(2, "0") + "-" + year;
let currentTime = "Time: " + hh + ":" + mm + ":" + ss;

dateOut.innerText = currentDate;
timeOut.innerText = currentTime;

let xmasYear = year;

if(month === 12 && day > 25) {
xmasYear += 1;
}

let xmasStr = xmasYear + "-12-25T00:00:00Z";
const xmas = new Date(xmasStr);
const timeLeft = xmas - dateNow;

let xdd = 0;
let xhh = 0;
let xmm = 0;
let xss = 0;

if (month !== 12 || (month === 12 && day !== 25)) {
xdd = Math.floor(timeLeft/86400000);
xhh = Math.floor(timeLeft/3600000) % 24 + zoneOffset;
xmm = Math.floor(timeLeft/60000) % 60;
xss = Math.floor(timeLeft/1000) % 60;
countdownOut.innerText = "\nCountdown to Christmas: \n" + xdd + " days, ";
countdownOut.innerText += xhh + " hours, ";
countdownOut.innerText += xmm + " minutes, ";
countdownOut.innerText += xss + " seconds.";
}
else {
countdownOut.innerText = "\nMerry Christmas!";
}

setTimeout(updateDisplay, 1000);
}
updateDisplay();
</script>

</body>
</html>

Copy and paste this code into a new file in your HTML editor, save the file as javascript-demo-68.html, and open the file in a web browser. Depending on the current date when you open the web page, and the time zone you happen to be in, you should see something like the illustration below.


A clock/calendar with a countdown to Christmas day

A clock/calendar with a countdown to Christmas day


Let's walk through the JavaScript code and see what is happening. The updateDisplay() function does all the work, and begins by using the Date() constructor without arguments to create a Date object and assign it to the dateNow variable:

const dateNow = new Date();

The next three lines of code assign the three HTML paragraph (<p>) elements we will used to display the date, time and countdown information to the variables dateOut, timeOut and countdownOut, respectively:

let dateOut = document.getElementById("date");
let timeOut = document.getElementById("time");
let countdownOut = document.getElementById("countdown");

The next line of code gets the time zone offset, i.e. the time difference between the user's local time and UTC/GMT. This is important because the date and time held by the dateNow variable will be the current date and time in the user's locale, and we want to calculate the time remaining until 00:00:00 on 25th December next in the user's local time zone. Note that the time zone offset is returned as an integer number of minutes, so we have to divide by 60 to get the time zone offset in hours:

let zoneOffset = dateNow.getTimezoneOffset()/60;

The next block of code extracts the hours, minutes and seconds values from dateNow, converts them to string values (padded with a leading zero if necessary), and assigns them to the variables hh, mm, and ss respectively:

let hh = dateNow.getHours().toString().padStart(2, "0");
let mm = dateNow.getMinutes().toString().padStart(2, "0");
let ss = dateNow.getSeconds().toString().padStart(2, "0");

We then get values for the year month and day in similar fashion and assign them to the numeric variables year, month and day:

let year = dateNow.getFullYear();
let month = dateNow.getMonth() + 1;
let day = dateNow.getDate();

The next few lines of code generate the strings that will be output for the current date and time and assign them to the innerText properties of the corresponding paragraph elements:

let currentDate = "Date: " + day.toString().padStart(2, "0");
currentDate += "-" + month.toString().padStart(2, "0") + "-" + year;
let currentTime = "Time: " + hh + ":" + mm + ":" + ss;

dateOut.innerText = currentDate;
timeOut.innerText = currentTime;

We now come to the countdown calculation, which is a bit trickier because we have to calculate the total amount of time that will elapse - in milliseconds - between the current date and time, and midnight on the next Christmas eve. We then need to convert that millisecond value into days, hours, minutes and seconds.

Things are further complicated by the fact that, for any date in December after the 25th but in the current year, we will be counting down to Christmas of the following year. Furthermore, we don't want to display a countdown on Christmas day itself because . . . well, because it's Christmas! So . . . we want to display some kind of Christmas greeting on Christmas day itself, and resume our countdown (to the next Christmas) at 00:00:00 on 26th December of the current year.

With the above in mind, the first thing to establish is whether we are counting down to Christmas in the current year or the following year (this will be the case if the current date occurs in the period starting December 26th and ending December 31st). The following code establishes the target year:

let xmasYear = year;
if(month === 12 && day > 25) {
xmasYear += 1;
}

Having established a value for xmasYear, we will now create a datetime string value for the next occurrence of 00:00:00 on Christmas day and use it to create a Date object which we'll assign to the variable xmas. We now have two Date objects - dateNow, which represents the current date and time, and xmas. We calculate the time left until Christmas (in milliseconds) by subtracting dateNow from xmas, and assign the result to the variable timeLeft:

let xmasStr = xmasYear + "-12-25T00:00:00Z";
const xmas = new Date(xmasStr);
const timeLeft = xmas - dateNow;

The next block of code simply creates the variables xdd, xhh, xmm and xss to hold the countdown values, i.e. the number of days, hours, minutes and seconds left until Christmas, and initialises them all to zero:

let xdd = 0;
let xhh = 0;
let xmm = 0;
let xss = 0;

If the current date is not December 25th, the next few lines of code will calculate the number of days, hours, minutes and seconds remaining until Christmas, formulate the output string to be displayed to the user for the countdown, and assign the output string to the innerText property of the paragraph element whose ID is "countdown".

if (month !== 12 || (month === 12 && day !== 25)) {
xdd = Math.floor(timeLeft/86400000);
xhh = Math.floor(timeLeft/3600000) % 24 + zoneOffset;
xmm = Math.floor(timeLeft/60000) % 60;
xss = Math.floor(timeLeft/1000) % 60;
countdownOut.innerText = "\nCountdown to Christmas: \n" + xdd + " days, ";
countdownOut.innerText += xhh + " hours, ";
countdownOut.innerText += xmm + " minutes, ";
countdownOut.innerText += xss + " seconds.";
}
else {
countdownOut.innerText = "\nMerry Christmas!";
}

If the current date is December 25th, the countdown will be replaced by the message "Merry Christmas!".

Let's look at how the values for the countdown are obtained. We know that the total amount of time left, which is held in the variable timeLeft, is the difference in milliseconds between two datetime values held in the variables dateNow and xmas.

In order to calculate the number of complete days remaining, we need to divide timeLeft by 86,400,000 (one day = 24 hours × 60 minutes × 60 seconds × 1,000 milliseconds = 86,400,000 millliseconds). The result is then rounded down to the nearest whole number using the Math.floor() method, and assigned to the variable xdd:

xdd = Math.floor(timeLeft/86400000);

To get the remaining hours, we divide timeLeft by 3,600,000 (one hour = 60 minutes × 60 seconds × 1,000 milliseconds = 3,600,000 millliseconds), and then use the modulus operator (%) to divide the result by 24 and return just the remainder. We again use the Math.floor() method to round the result of that calculation down to the nearest whole number. The last step is to add the value of zoneOffset (the time zone offset in hours) and assign the final result to the variable xhh:

xhh = Math.floor(timeLeft/3600000) % 24 + zoneOffset;

The remaining minutes and seconds are calculated in similar fashion by dividing timeleft by 60,000 (the number of milliseconds in a minute) and 1000 (the number of milliseconds in a second) respectively. For each of the two values obtained, we find the modulus of 60, round down to the nearest whole number using Math.floor(), and assign the results to variables xmm and xss respectively:

xmm = Math.floor(timeLeft/60000) % 60;
xss = Math.floor(timeLeft/1000) % 60;

The remaining code in the updateDisplay() function creates the user-friendly output string that will be used to display the countdown values, and (as we have already stated) assigns it to the innerText property of the HTML paragraph element whose ID is "countdown":

countdownOut.innerText = "\nCountdown to Christmas: \n" + xdd + " days, ";
countdownOut.innerText += xhh + " hours, ";
countdownOut.innerText += xmm + " minutes, ";
countdownOut.innerText += xss + " seconds.";

The last line of code in the updateDisplay() function sets a timeout value of 1000 milliseconds (one second) for the function using JavaScript's global setTimeout() method. This method sets a timer that executes a function (or some other piece of code) when the timer expires. Because the method is called within the updateDisplay() function itself, the function will be executed repeatedly, once every second.

setTimeout(updateDisplay, 1000);

The very last line of code within the <script> . . . </script> element calls the updateDisplay() function. This function is executed once the browser has read the JavaScript code.

Parsing datetime stings

As we have seen, we can create a new Date object using the Date() constructor, either with or without arguments. When no argument is supplied, the value of the Date object will be a timestamp representing the difference (in milliseconds) between the instant at which the Date() constructor was invoked and the epoch (00:00:00 UTC on 1st January 1970). When an argument is supplied, it can take a number of different forms, as we have already seen. For example:

console.log(new Date("2024-08-22T12:30"));
// Date Thu Aug 22 2024 12:30:00 GMT+0100 (British Summer Time)
console.log(new Date("Thu, 22 Aug 2024, 12:30 pm"));
// Date Thu Aug 22 2024 12:30:00 GMT+0100 (British Summer Time)
console.log(new Date("22 Aug 2024, 12:30"));
// Date Thu Aug 22 2024 12:30:00 GMT+0100 (British Summer Time)
console.log(new Date("August 22, 2024, 12:30"));
// Date Thu Aug 22 2024 12:30:00 GMT+0100 (British Summer Time)
console.log(new Date("8/22/2024 12:30"));
// Date Thu Aug 22 2024 12:30:00 GMT+0100 (British Summer Time)
console.log(new Date(2024,7,22,12,30));
// Date Thu Aug 22 2024 12:30:00 GMT+0100 (British Summer Time)
console.log(new Date(1724322600000));
// Date Thu Aug 22 2024 11:30:00 GMT+0100 (British Summer Time)

In the above example we have created six Date objects, all of which reference the same time and date (12:30 p.m., August 22nd, 2024). All of the Date objects are created using the Date() constructor, but in each case a different argument has been used. For the first five examples, five different but equivalent datetime strings have been passed to the constructor. The penultimate Date object is created using a comma-delimited sequence of integers representing the year, month, date, hours and minutes. The final Date object is created using a timestamp.

In light of the above, the question has probably occurred to you: which format should be used for Date() constructor arguments? We can state right away that the weekday is not actually required, since JavaScript is perfectly capable of determining the day of the week using the specified date.

The am and pm designations are only required if using a 12-hour time format (as opposed to a 24-hour format). In fact, using either am or pm for any time value greater than or equal to 13:00 will cause the Date() constructor to return Invalid Date, as illustrated by the following code:

console.log(new Date("August 22 2024 12:59 am"));
// Date Thu Aug 22 2024 00:59:00 GMT+0100 (British Summer Time)
console.log(new Date("August 22 2024 13:00 am"));
// Invalid Date
console.log(new Date("August 22 2024 12:59 pm"));
// Date Thu Aug 22 2024 12:59:00 GMT+0100 (British Summer Time)
console.log(new Date("August 22 2024 13:00 pm"));
// Invalid Date

Obviously, using the am or pm designation makes a difference as to how time values smaller than 13:00 are interpreted. As you can see from the above example, in the ISO 8601 24-hour time system, 12.59 am is interpreted as 00:59, whereas 12.59 pm is interpreted as 12:59.

For purely numeric dates in which the forward slash ("/") is used as a separator, the order in which the month, the day of the month, and the year appear is important. The year can appear either first or last in the sequence, but the month must always precede the day of the month. For example:

console.log(new Date("2024/8/22 12:30"));
// Date Thu Aug 22 2024 12:30:00 GMT+0100 (British Summer Time)
console.log(new Date("8/22/2024 12:30"));
// Date Thu Aug 22 2024 12:30:00 GMT+0100 (British Summer Time)
console.log(new Date("22/8/2024 12:30"));
// Invalid Date
console.log(new Date("2024/22/8 12:30"));
// Invalid Date

Apart from that, we have found from playing around with different formats that the following (very loose) rules apply for datetime string input in which month names are used:

We also need to bear in mind that support for non-standard date formats may vary between web browsers and other web applications. Requiring users to input date and time data in one of the universally accepted datetime string formats (ISO 8601 or IETF standard date format), or if necessary converting user input to one of these formats, is relatively easy to accomplish. Parsing datetime strings in archival materials can be somewhat more problematic.

We can test datetime strings for validity before creating, and assigning datetime values to, a Date object by using the static Date.parse() method. This method accepts a datetime string as its argument and returns a timestamp for a valid datetime string. If the datetime string is not valid, Date.parse() returns NaN. The Date.parse() method is guaranteed to accept strings that conform to either the JavaScript datetime string format or the IETF standard date format (see RFC 3339).

The differences between these formats are subtle. RFC 3339 does not allow 2-digit years and only allows a period character to be used as the decimal point for fractional second values. Conversely, RTF allows the date and time values to be separated by a space (as opposed to the upper-case character "T") to make the datetime string more readable. ISO 8601 requires the "T" unless all parties using the datetime representation have agreed otherwise.

Since JavaScript's Date.parse() method accepts either representation, we need not concern ourselves with the difference between the two formats. The timestamp produced will have the same value whichever format is used. For example:

console.log(Date.parse("1955-02-25T03:35"));
// -468620700000
console.log(Date.parse("55-02-25 03:35"));
// -468620700000

If a valid timestamp value is returned, it can be used to create a Date object using the Date() constructor. If NaN is returned, we can handle the problem programmatically. Since the date and time values entered by an end user these days are often handled using date and time pickers, or by selecting appropriate date and time values using a drop-down list, it should be relatively easy to write code that generates a valid datetime string behind the scenes.

The Date.parse() method accepts as its argument any datetime string that would be accepted by the Date() constructor, and generates a timestamp matching the value of the corresponding Date object. Its behaviour when presented with numeric arguments will produce very different results. For example, here's what happens if we attempt to use a comma-delimited numeric datetime representation:

console.log(Date.parse(2024,07,22,12,30));
// 1704067200000
console.log(new Date(2024,07,22,12,30).valueOf());
// 1724326200000

The Date.parse() method returns a timestamp value that differs significantly from that of the Date object created with the Date() constructor. In fact, because the Date.parse() method expects a string rather than a numeric value, the first value in the comma-delimited numeric sequence (i.e. the year value) is coerced to a string - in this case "2024". The remaining values are simply ignored. We can demonstrate this by passing the timestamp value generated by Date.parse() to the Date() constructor:

console.log(new Date(1704067200000));
// Date Mon Jan 01 2024 00:00:00 GMT+0000 (Greenwich Mean Time)

The Date.parse() method will accept a numeric value if it can be coerced to a string value representing a valid year, as we have seen, although the results are somewhat mixed. The Date.parse() method even accepts (at least in the browsers we tested) 1-digit values from 0 to 9, two-digit values from 10 to 12, and two-digit values from 32 to 99. Two-digit values in the range 13 to 31 result in NaN being returned by Date.parse(), while two-digit numbers in the range 50 to 99 are converted to values representing years in the range 1950 to 1999.

Three-digit numbers in the range 100 to 999 are represented without change, as are numbers with four digits or more from 1848 onwards. Numbers between 1000 and 1847 appear to be decremented by one. Beyond that, numbers with five or six digits in the range 10000 to 275760 are accepted at face value. All numeric arguments greater than 275760 result in NaN being returned, and all negative numbers are converted to their absolute values before being converted to strings. In short, presenting Date.parse() with numeric arguments results in behaviour that is somewhat erratic. We recommend that you avoid doing so.

As previously mentioned, for most web pages or web applications that handle times and dates, we can exercise considerable control over the format in which end users can input time and date values. Processing archival documents that might contain date information in an obscure format probably requires a somewhat more in-depth approach to dealing with datetime strings, and needs to be dealt with on a case-by-case basis.

Formatting dates and times

The way in which date and time information is displayed by a web browser or web application can vary considerably, depending on factors such as the end user's locale, the constraints imposed by the platform on which the date and/or time information is to be displayed, and the requirements of the web page or web application. The good news is that the timestamp stored in a Date object will always have the same value (unless we explicitly change that value) regardless of which date or time components we need to output, how we choose to display them, or the user's locale.

We have already seen how we can extract individual date and time components from the timestamp stored in a Date object using the Date object's built-in methods. Once we have the date and time components we want to display, we can do so in just about any way we see fit. We can, for example, display one- or two-digit values for the day of the month, and for the month itself. This will entail using leading zeros for values of 1 to 9, and is desirable in situations where we want to display dates in tables and in a numeric format, and align them within columns, for the sake of readability.

We may also choose to display the names of weekdays and months, either in full (e.g. "Monday", "January") or in their (typically three-character) short form (e.g. "Mon", "Jan"). One benefit of using the short form version for day and month names is that it still allows us to align dates in columns, if required.

The same principle applies to time values. It is common to see time values expressed using two-digits, where the hours, minutes and seconds are expressed as hh:mm:ss. This also removes any ambiguity when it comes to determining whether the time displayed in a 12-hour system is am (short for ante meridiem, i.e. before noon) or pm (short for post meridiem, i.e. after noon) even if we do not display these abbreviations, although most digital time representation these days use the 24-hour format.

Displaying ordinal day-of-the-month values - i.e. dates expressed as "1st", "2nd", "3rd", "4th" and so on, rather than as a purely numerical value, is slightly trickier to handle, because we need to determine which two-character ordinal indicator ("st", "nd", "rd", or "th") to use with a particular date value, but this can be achieved relatively easily using a little additional code.

The code below creates a web page contains a form that allows a user to enter a time and a date, and then experiment with different time and date formats. It creates a Date object from the time and date information submitted by the user and assigns the numeric value of the individual datetime components (day, month, year, hours and minutes) to variables using the Date object's built-in methods. The user can then select various option in order to control how the chosen time and date are displayed on the screen. Here is the code:

<!doctype html>
<html lang="en">

<head>
<meta charset="utf-8">
<title>JavaScript Demo 69</title>
<style>
h1, h2 { text-align: center; }
form {
border: 1px solid;
width: max-content;
max-width: 96%;
padding: 1em;
margin: auto;v }
input, output {v margin: 0.25em 0;
}
td {
vertical-align: top;
padding: 0.5em 1em;
}
</style>
</head>

<body>

<h1>Date Formats</h1>

<h2>Select a date, time and output format</h2>

<form name="frm" id="frm">
<table>
<tr>
<td>
<label for="time">Enter a time: </label>
<input type="time" id="timeIn" required />
</td>
</tr>
<tr>
<td>
<label for="date">Select a date: </label>
<input type="date" name="dateIn" required />
</td>
</tr>
<tr>
<td>
<h3>Date format</h3>
<label><input type="radio" name="dFormat" id="dmy" required value="dmy"> dd-mm-yyyy</label><br>
<label><input type="radio" name="dFormat" id="mdy" value="mdy"> mm-dd-yyyy</label><br>
<label><input type="radio" name="dFormat" id="ymd" value="ymd"> yyyy-mm-dd</label><br>
<label><input type="radio" name="dFormat" id="short" value="short"> Short month/day names</label><br>
<label><input type="radio" name="dFormat" id="long" value="long"> Long month/day names</label>
</td>
<td>
<h3>Time format</h3>
<label><input type="radio" name="tFormat" id="24hr" required value="24hr"> 24-hour</label><br>
<label><input type="radio" name="tFormat" id="12hr" value="12hr"> 12-hour</label>
</td>
</tr>
<tr>
<td>
<h3>Show weekday?</h3>
<label><input type="checkbox" name="wDay" disabled id="wDay"> Yes</label><br>
</td>
<td>
<h3>Show Ordinal dates?</h3>
<label><input type="checkbox" name="ord" disabled id="ord"> Yes</label><br>
</td>
</tr>
<tr>
<td colspan="2" style="text-align: center;">
<input type="submit" value="Display date and time in selected format"/>
</td>
</tr>
<tr>
<td colspan="2">
<output id="timeOut">Time: </output>
</td>
</tr>
<tr>
<td colspan="2">
<output id="dateOut">Date: </output>
</td>
</tr>
</table>
</form>

<script>
const weekday = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
const month = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];

const frm = document.querySelector("#frm");
const ord = document.querySelector("#ord");
const wDay = document.querySelector("#wDay");
const timeOut = document.getElementById("timeOut");
const dateOut = document.getElementById("dateOut");

let dFormat = document.frm.dFormat;
for (let i = 0; i < dFormat.length; i++) {
dFormat[i].addEventListener("change", dateFormatChange);
}

function dateFormatChange() {
let format = this.id;
if (format == "short" || format == "long") {
document.getElementById("ord").disabled = false;
document.getElementById("wDay").disabled = false;
}
else {
document.getElementById("ord").disabled = true;
document.getElementById("ord").checked = false;
document.getElementById("wDay").disabled = true;
document.getElementById("wDay").checked = false;
}
}

frm.addEventListener("submit", displayDateTime);

function displayDateTime() {
event.preventDefault();
const inputDate = new Date(frm.dateIn.value + "T" + frm.timeIn.value);
let yyyy = inputDate.getFullYear();
let MM = inputDate.getMonth();
let dd = inputDate.getDate();
let day = inputDate.getDay();
let hh = inputDate.getHours();
let mm = inputDate.getMinutes();
let ordinal = "";
outputDate(yyyy, MM, dd, day);
outputTime(hh, mm);
}

function outputDate(yyyy, MM, dd, day) {
let dateFormat = frm.elements["dFormat"].value;
let date, mon;
let dateTimeStr = "Date: ";
switch (dd) {
case 1:
case 21:
case 31:
ordinal = "st"
break;
case 2:
case 22:
ordinal = "nd"
break;
case 3:
case 23:
ordinal = "rd"
break;
default:
ordinal = "th"
break;
}
if (dateFormat == "dmy" || dateFormat == "mdy" || dateFormat == "ymd") {
date = dd.toString().padStart(2, "0");
mon = (MM+1).toString().padStart(2, "0");
}
switch (dateFormat) {
case "dmy":
dateOut.innerHTML = dateTimeStr + date + "-" + mon + "-" + yyyy;
break;
case "mdy":
dateOut.innerHTML = dateTimeStr + mon + "-" + date + "-" + yyyy;
break;
case "ymd":
dateOut.innerHTML = dateTimeStr + yyyy + "-" + mon + "-" + date;
break;
case "short":
if (document.getElementById("wDay").checked) {
dateTimeStr += weekday[day].substring(0,3) + ", ";
}
dateTimeStr += month[MM].substring(0,3) + " " + dd;
if (document.getElementById("ord").checked) {
dateTimeStr += ordinal;
}
dateTimeStr += ", " + yyyy;
dateOut.innerHTML = dateTimeStr;
break;
case "long":
if (document.getElementById("wDay").checked) {
dateTimeStr += weekday[day] + ", ";
}
dateTimeStr += month[MM] + " " + dd;
if (document.getElementById("ord").checked) {
dateTimeStr += ordinal;
}
dateTimeStr += ", " + yyyy;
dateOut.innerHTML = dateTimeStr;
break;
default:
break;
}
}

function outputTime(hh, mm) {
let timeFormat = frm.elements["tFormat"].value;
let min = mm.toString().padStart(2, "0");

if(timeFormat == "24hr") {
let hour = hh.toString().padStart(2, "0");
timeOut.innerHTML = "Time: " + hour + ":" + min;
}
else {
let suffix = " a.m.";
if (hh >= 12) {
suffix = " p.m.";
}
if (hh >= 13) {
hh -= 12;
}
timeOut.innerHTML = "Time: " + hh + ":" + min + suffix;
}
}
</script>

</body>
</html>

Copy and paste this code into a new file in your HTML editor, save the file as javascript-demo-69.html, and open the file in a web browser. Enter a time and a date and experiment with the formatting options provided. Depending on the date you entered and the formatting choices you made, you should see something like the illustration below.


Various time and date formats can be selected

Various time and date formats can be selected


There is quite a lot of code here, but it is relatively self explanatory. The essential point to take away from this is that, once we have a valid Date object, we can extract the relevant datetime values and display them in any format we want to. In the above example, we use the instance methods below to retrieve the date and time components we are interested in. Note that all of the values returned are integers:

getFullYear(); // returns the year (e.g. 2024)
getMonth(); // returns the month number, indexed from zero (0-11)
getDate(); // returns the day of the month (1-31)
getDay(); // returns the weekday number, indexed from zero (0-6)
getHours(); // returns the hours (0-23)
getMinutes(); // returns the minutes (0-59)

The fact that both months and weekdays are indexed from zero makes it slightly easier to output month and weekday names. As you can see from the above example, all we need to do is create an array of month names and an array of weekday names. We can then extract the full name of the month or weekday by using the month or weekday number to index into the corresponding array.

We use these same arrays when we want to display the short (three-character) version of a month or weekday name. The only difference is that we then need to extract the appropriate three-character substring from the name retrieved (e.g. ""Feb" from "February" or "Mon" from "Monday") using one of JavaScript's string handling functions, such as substring() or slice().

Adding an ordinal to a date is also relatively straightforward. We use a switch() statement to create the two-character ordinal string ("st", "nd", "rd" or "th") based on the value returned by getDate(). We can then simply append the ordinal string to the date before we display it. Here is the switch() statement we employ to determine which ordinal to use:

switch (dd) {
case 1:
case 21:
case 31:
ordinal = "st";
break;
case 2:
case 22:
ordinal = "nd";
break;
case 3:
case 23:
ordinal = "rd";
break;
default:
ordinal = "th";
break;
}

The JavaScript Date object does of course have several date formatting methods of its own, which we can also use to output dates and times. The toString() method, when used with a Date object, produces a string that tells us the date, time and local time zone, in that order. For example:

const someDate = new Date();
console.log(someDate.toString());
// Fri Aug 30 2024 14:57:31 GMT+0100 (British Summer Time)

The toDateString() and toTimeString() methods return the date and time portions, respectively, of the string returned by the toString() method, as demonstrated by this code:

const someDate = new Date();
console.log(someDate.toString());
// Fri Aug 30 2024 15:03:05 GMT+0100 (British Summer Time)
console.log(someDate.toDateString());
// Fri Aug 30 2024
console.log(someDate.toTimeString());
// 15:03:05 GMT+0100 (British Summer Time)

We can also output date and time information in a locale-specific format using the toLocaleString(), toLocaleDateString(), and toLocaleTimeString() methods. These methods differ from the toString(), toDateString() and toTimeString() methods in that they each accept two optional parameters. The first parameter in each case is locales - a string that typically identifies a specific language and region. The second parameter is options - an object whose attributes can be set to different values in order to "fine-tune" the format in which the date and/or time are displayed. For example:

const someDate = new Date();
const dateTimeOptions = {
weekday: "long",
year: "numeric",
month: "long",
day: "numeric",
hour: "numeric",
minute: "numeric",
second: "numeric"
};

console.log(someDate.toLocaleString("hi-IN", dateTimeOptions));
// शुक्रवार, 30 अगस्त 2024 को 3:29:52 pm बजे
console.log(someDate.toLocaleString("en-GB", dateTimeOptions));
// Friday, 30 August 2024 at 15:29:52

We have called toLocaleString() twice on the same Date object, using different values for the locales argument. As you can see, the actual date and time values displayed here are identical; they represent the date and time for the user's locale. The same object is used as the options argument in both cases, so the information has been displayed in the same format. However, as you can see, the language used depends on the locales argument.

In the first call to toLocaleString() we signified Hindi as the language, as spoken in India. In the second call, we specified English as the language, as spoken in Great Britain. Somewhat quirkily, JavaScript produces exactly the same results when the same locales and options arguments are used with toLocaleDateString() and toLocaleTimeString().

This is somewhat strange behaviour, because we would expect date-only and time-only output respectively from these two methods. When used with more appropriate options arguments, however, they produce the desired results. Here we call toLocaleTimeString() with appropriate time attributes:

const someDate = new Date();

const timeOptions = {
hour12: true,
dayPeriod: "long",
hour: "numeric",
minute: "numeric",
second: "numeric"
};

console.log(someDate.toLocaleTimeString("en-GB", timeOptions));
// 4:16:41 in the afternoon
console.log(someDate.toLocaleTimeString("de-DE", timeOptions));
// 4:16:41 nachmittags
console.log(someDate.toLocaleTimeString("el-GR", timeOptions));
// 4:16:41 το μεσημέρι

And here we call toLocaleDateString() with appropriate date attributes:

const someDate = new Date();

const dateOptions = {
weekday: "long",
year: "numeric",
month: "long",
day: "numeric",
};

console.log(someDate.toLocaleDateString("en-GB", dateOptions));
// Friday, 30 August 2024
console.log(someDate.toLocaleDateString("de-DE", dateOptions));
// Freitag, 30. August 2024
console.log(someDate.toLocaleDateString("el-GR", dateOptions));
// Παρασκευή 30 Αυγούστου 2024

There are two other methods that can be used to format date and time information. The first of these is toISOString(), which returns a string representation of the Date object on which it is called - essentially a simplified version of the ISO 8601 format - according to universal time. For example:

const someDate = new Date();

console.log(someDate);
// Date Fri Aug 30 2024 16:58:28 GMT+0100 (British Summer Time)
console.log(someDate.toISOString());
// 2024-08-30T15:58:28.659Z

The last date and time formatting method we will describe here is the toUTCString() method, which behaves in a similar fashion to the toString() method, except that it returns a string representing the Date object on which it is called interpreted according to universal time rather than local time. For example:

const someDate = new Date();

console.log(someDate);
// Date Fri Aug 30 2024 16:58:28 GMT+0100 (British Summer Time)
console.log(someDate.toUTCString());
// Fri, 30 Aug 2024 15:58:28 GMT

Working with time zones

A timestamp is an integer value that represents a precise instant in time. If the timestamp is wrapped up in a Date object, we can use the Date object's built-in methods to extract the date and time components individually and display them in the required format, as we have seen. The date and time seen by the user, however, is largely dependent on the user's locale. In other words, it is the local date and time that will be displayed on the user's device rather than the date and time according to Coordinated Universal Time (UTC).

Sometimes, however, we want to know what the time is in other parts of the world. If, for example, we want to make a personal call to a friend or relative on the other side of the world, we need to consider what the time is in their locale. Receiving a telephone call at 3.00 a.m., unless it's an emergency of some kind, can be extremely annoying. It is also important to be aware of what time it is in other countries when making international business calls, since out-of-hours calls are unlikely to be answered in most cases.

A user's local time will depend on what time zone they are in, which will in turn depend - to a large extent - on their longitude. Longitude is a measure of how far west or east a location is with respect to the Prime Meridian. Lines of longitude (or meridians) are 360 lines that run from the North Pole to the South pole, spaced at 1° intervals. The Prime Meridian is assigned a longitude of 0°, and is the line of longitude that passes through Greenwich, England.

Meridians to the west of the Prime Meridian are assigned a negative value based on how many degrees they are to west of it. Likewise, meridians to the east of the Prime Meridian have a positive value based on how many degrees they are east of it. The line that lies exactly ±180° from the prime meridian (i.e. the line directly opposite to it on the globe) is called the Antemeridian, and is often referred to as the International Date Line (IDL).

There are 24 equally-spaced nominal time zones around the globe, each of which is 15° degrees wide. The first of these is the Greenwich Mean Time (GMT) time zone, which is centred on the Prime Meridian. Each time zone is separated from its neighbours by one hour. The eleven time zones to the west of the GMT time zone are ahead of GMT, whilst the eleven time zones to its east are behind GMT. The eastern half of the time zone centred on the Antemeridian can be considered to be 12 hours ahead of GMT, while the western half is 12 hours behind GMT.

In reality, the picture is far more complicated. Many countries, for example, are simply too large to occupy a single time zone, while other countries, even if small enough to lie within a single time zone, lie on the boundary between two adjacent time zones. It would obviously make no sense to have to adjust your watch each time you step over an imaginary line. Each country or state must therefore formulate its own policy with respect to adopting time zones. The graphic below shows the time zones currently used in Europe.


European time zones

European time zones
Image: Heitordp


An added complication is that some countries adopt daylight saving schemes while others do not. Those that do will have a different time offset from UTC at different times of the year. Unfortunately, the sheer volume of information available with respect to different locales and the time zones they use renders it impractical to attempt to provide a comprehensive listing here. There are however a number of online sources available if you just need to find current time zone information for one or more specific locations. A good place to start might be the Time and Date website.

We want to ensure, however, that we are able to display the correct local time for a specific time-zone regardless of whether or not that time zone is affected by daylight saving. In order to do so, we need to be able to refer to the correct time zone by name, because specifying an offset in hours and minutes is a hard-wired solution that does not allow for changes in the offset due to daylight saving. With that in mind, we should consider using the Date object's toLocaleString(), toLocaleDateString(), and toLocaleTimeString() instance methods.

These methods can be used to display date and/or time information in a language and format suitable for the user's locale. Each of these methods takes two optional parameters - locales and options - that can be used to customise the method's behaviour.

The locales option is a string (officially referred to as a tag) that typically identifies a specific language and region. For example, en-GB consists of two sub-tags separated by the "-" character. The first sub-tag identifies the language (en = English), while the second sub-tag identifies the region (GB = United Kingdom). Using the locales option will determine how the date is displayed in different languages and regions. For example, British English uses a day-month-year order for dates whereas US English uses a month-day-year order.

Also of interest to us in the context of this article is the options parameter which, as its name suggests, is an object comprising a number of attributes whose values can be set in order to further customise the output of the toLocaleString(), toLocaleDateString(), and toLocaleTimeString() methods. The attribute we are interested in here is the timeZone attribute, which allows us to specify the time zone used to display date and time information.

The Internet Assigned Numbers Authority (IANA), which is responsible for the assignment of IP addresses, also maintains a database of representative locations and the time zones in which they reside. You can find a current (at the time of writing) text-based listing of IANA time zone locations here. An IANA representative location uses a country and/or region name followed by the name of a city (usually the most populous in that country or region). For example, Greece has only one time zone, which is represented by "Europe/Athens". We could use it as follows:

const date = new Date();
const options = { timeZone: "Europe/Athens" };
let hh = date.getUTCHours().toString().padStart(2,"0");
let mm = date.getUTCMinutes().toString().padStart(2,"0");
let ss = date.getUTCSeconds().toString().padStart(2,"0");
console.log(date);
// Date Thu Sep 12 2024 09:58:59 GMT+0100 (British Summer Time)
console.log("The current UTC time is: " + hh + ":" + mm + ":" + ss);
// The current UTC time is: 08:58:59
console.log("The current time in Greece is: " + date.toLocaleTimeString("en-GB", options));
// The current time in Greece is: 11:58:59

The time displayed is automatically adjusted for daylight savings if that is in effect in the specified locale. Note that most browsers use the Unicode Common Locale Data Repository (CLDR) database. There are well over three hundred representative locations in both the CLDR database and the IANA database. Be aware, however, that there is not a strict one-to-one correspondence. Some of the CLDR representative locations differ from their counterparts in the IANA database. You can find the most recent CLDR listing of representative locations here.

The HTML code below creates a web page that displays the current time in twenty cities worldwide that are home to important international finance centres. It retrieves the current time using the Date() constructor and displays a different time for each city using the toLocaleTimeString() method. The options argument in each case has its timeZone attribute set to the appropriate representative location. Here is the code:

<!doctype html><br>
<html lang="en">

<head>
<meta charset="utf-8">
<title>JavaScript Demo 70</title>
<style>
h1, h2 { text-align: center; }
table {
border-collapse: collapse;
border: 1px solid #ccc;
width: max-content;
max-width: 96%;
padding: 1em;
margin: auto;
}
td {
text-align: center;
vertical-align: top;
width: 120px;
padding: 0.5em 1em;
font-weight: bold;
color: #404040;
}
tr.city td {
background-color: #ccc;
border-left: 1px solid #eee;
}
tr.time td {
border-left: 1px solid #ccc;
}
</style>
</head>

<body>

<h1>Global Financial Centres</h1>

<h2>Current Local Times</h2>

<table>
<tr class="city">
<td>New York</td>
<td>London</td>
<td>Singapore</td>
<td>Hong Kong</td>
</tr>
<tr class="time">
<td id="new_york"></td>
<td id="london"></td>
<td id="singapore"></td>
<td id="hong_kong"></td>
</tr>

<tr class="city">
<td>San Francisco</td>
<td>Shanghai</td>
<td>Geneva</td>
<td>Los Angeles</td>
</tr>
<tr class="time">
<td id="san_francisco"></td>
<td id="shanghai"></td>
<td id="geneva"></td>
<td id="los_angeles"></td>
</tr>

<tr class="city">
<td>Chicago</td>
<td>Seoul</td>
<td>Shenzhen</td>
<td>Washington D.C.</td>
</tr>
<tr class="time">
<td id="chicago"></td>
<td id="seoul"></td>
<td id="shenzhen"></td>
<td id="washington_dc"></td>
</tr>

<tr class="city">
<td>Frankfurt</td>
<td>Paris</td>
<td>Beijing</td>
<td>Zürich</td>
</tr>
<tr class="time">
<td id="frankfurt"></td>
<td id="paris"></td>
<td id="beijing"></td>
<td id="zürich"></td>
</tr>

<tr class="city">
<td>Luxembourg</td>
<td>Sydney</td>
<td>Tokyo</td>
<td>Dubai</td>
</tr>
<tr class="time">
<td id="luxembourg"></td>
<td id="sydney"></td>
<td id="tokyo"></td>
<td id="dubai"></td>
</tr>
</table>

<script>
const new_york = document.querySelector("#new_york");
const london = document.querySelector("#london");
const singapore = document.querySelector("#singapore");
const hong_kong = document.querySelector("#hong_kong");
const san_francisco = document.querySelector("#san_francisco");
const shanghai = document.querySelector("#shanghai");
const geneva = document.querySelector("#geneva");
const los_angeles = document.querySelector("#los_angeles");
const chicago = document.querySelector("#chicago");
const seoul = document.querySelector("#seoul");
const shenzhen = document.querySelector("#shenzhen");
const washington_dc = document.querySelector("#washington_dc");
const frankfurt = document.querySelector("#frankfurt");
const paris = document.querySelector("#paris");
const beijing = document.querySelector("#beijing");
const zürich = document.querySelector("#zürich");
const luxembourg = document.querySelector("#luxembourg");
const sydney = document.querySelector("#sydney");
const tokyo = document.querySelector("#tokyo");
const dubai = document.querySelector("#dubai");

function updateDisplay() {
const dateNow = new Date();
new_york.innerText = dateNow.toLocaleTimeString("en-us", {timeZone: "America/New_York"});
london.innerText = dateNow.toLocaleTimeString("en-gb", {timeZone: "Europe/London"});
singapore.innerText = dateNow.toLocaleTimeString("zh-sg", {timeZone: "Asia/Singapore"});
hong_kong.innerText = dateNow.toLocaleTimeString("zh-hk", {timeZone: "Asia/Hong_Kong"});
san_francisco.innerText = dateNow.toLocaleTimeString("en-us", {timeZone: "America/Los_Angeles"});
shanghai.innerText = dateNow.toLocaleTimeString("zh-cn", {timeZone: "Asia/Shanghai"});
geneva.innerText = dateNow.toLocaleTimeString("fr-ch", {timeZone: "Europe/Zurich"});
los_angeles.innerText = dateNow.toLocaleTimeString("en-us", {timeZone: "America/Los_Angeles"});
chicago.innerText = dateNow.toLocaleTimeString("en-us", {timeZone: "America/Chicago"});
seoul.innerText = dateNow.toLocaleTimeString("ko", {timeZone: "Asia/Seoul"});
shenzhen.innerText = dateNow.toLocaleTimeString("zh-cn", {timeZone: "Asia/Shanghai"});
washington_dc.innerText = dateNow.toLocaleTimeString("en-us", {timeZone: "America/New_York"});
frankfurt.innerText = dateNow.toLocaleTimeString("de", {timeZone: "Europe/Berlin"});
paris.innerText = dateNow.toLocaleTimeString("fr", {timeZone: "Europe/Paris"});
beijing.innerText = dateNow.toLocaleTimeString("zh-cn", {timeZone: "Asia/Shanghai"});
zürich.innerText = dateNow.toLocaleTimeString("de-ch", {timeZone: "Europe/Zurich"});
luxembourg.innerText = dateNow.toLocaleTimeString("fr-lu", {timeZone: "Europe/Luxembourg"});
sydney.innerText = dateNow.toLocaleTimeString("en-au", {timeZone: "Australia/Sydney"});
tokyo.innerText = dateNow.toLocaleTimeString("ja", {timeZone: "Asia/Tokyo"});
dubai.innerText = dateNow.toLocaleTimeString("ar-ae", {timeZone: "Asia/Dubai"});
setTimeout(updateDisplay, 1000);
}
updateDisplay();
</script>

</body>
</html>

Copy and paste this code into a new file in your HTML editor, save the file as javascript-demo-70.html, and open the file in a web browser. Depending on the current UTC time, you should see something like the illustration below.


Local times in twenty global financial centres

Local times in twenty global financial centres


Internationalisation

JavaScript uses the ECMAScript Internationalisation API specification first published in 2012, and currently in its 11th iteration as the ECMAScript® 2024 internationalization API specification, published in June 2024. The API provides language-sensitive objects and methods, and complements (as opposed to being integrated within) the ECMAScript Language Specification as of version 5.1, published in June 2011.

The API essentially facilitates the implementation of ECMAScript objects and methods that support the linguistic and cultural conventions used by people in different locales around the world. Virtually all browser versions released from 2016 onwards support the ECMAScript internationalization API.

One important aspect of the API is that all of the methods implemented accept the locales and options arguments we have seen used with the JavaScript Date object methods toLocaleString(), toLocaleDateString() and toLocaleTimeString().

In this article we are concerned with dates and times. A full exploration of the ECMAScript internationalization API will be undertaken in a separate article elsewhere in this section, but we will briefly look at the Intl.DateTimeFormat() constructor method, which can be used to create objects that facilitate language-sensitive date and time formatting.

The HTML code below creates a web page that appears identical to the previous example. The difference is that we have used an Intl.DateTimeFormat() constructor (instead of the toLocaleTimeString() method) to create a formatted time string for each financial centre. As previously, the locales argument establishes the language and the country or region we are targeting, while the options argument in each case has its timeZone attribute set to the appropriate representative location. Here is the code:

<!doctype html>
<html lang="en">

<head>
<meta charset="utf-8">
<title>JavaScript Demo 71</title>
<style>
h1, h2 { text-align: center; }
table {
border-collapse: collapse;
border: 1px solid #ccc;
width: max-content;
max-width: 96%;
padding: 1em;
margin: auto;
}
td {
text-align: center;
vertical-align: top;
width: 120px;
padding: 0.5em 1em;
font-weight: bold;
color: #404040;
}
tr.city td {
background-color: #ccc;
border-left: 1px solid #eee;
}
tr.time td {
border-left: 1px solid #ccc;
}
</style>
</head>

<body>

<h1>Global Financial Centres</h1>

<h2>Current Local Times</h2>

<table>
<tr class="city">
<td>New York</td>
<td>London</td>
<td>Singapore</td>
<td>Hong Kong</td>
</tr>
<tr class="time">
<td id="new_york"></td>
<td id="london"></td>
<td id="singapore"></td>
<td id="hong_kong"></td>
</tr>

<tr class="city">
<td>San Francisco</td>
<td>Shanghai</td>
<td>Geneva</td>
<td>Los Angeles</td>
</tr>
<tr class="time">
<td id="san_francisco"></td>
<td id="shanghai"></td>
<td id="geneva"></td>
<td id="los_angeles"></td>
</tr>

<tr class="city">
<td>Chicago</td>
<td>Seoul</td>
<td>Shenzhen</td>
<td>Washington D.C.</td>
</tr>
<tr class="time">
<td id="chicago"></td>
<td id="seoul"></td>
<td id="shenzhen"></td>
<td id="washington_dc"></td>
</tr>

<tr class="city">
<td>Frankfurt</td>
<td>Paris</td>
<td>Beijing</td>
<td>Zürich</td>
</tr>
<tr class="time">
<td id="frankfurt"></td>
<td id="paris"></td>
<td id="beijing"></td>
<td id="zürich"></td>
</tr>

<tr class="city">
<td>Luxembourg</td>
<td>Sydney</td>
<td>Tokyo</td>
<td>Dubai</td>
</tr>
<tr class="time">
<td id="luxembourg"></td>
<td id="sydney"></td>
<td id="tokyo"></td>
<td id="dubai"></td>
</tr>
</table>

<script>
const new_york = document.querySelector("#new_york");
const london = document.querySelector("#london");
const singapore = document.querySelector("#singapore");
const hong_kong = document.querySelector("#hong_kong");
const san_francisco = document.querySelector("#san_francisco");
const shanghai = document.querySelector("#shanghai");
const geneva = document.querySelector("#geneva");
const los_angeles = document.querySelector("#los_angeles");
const chicago = document.querySelector("#chicago");
const seoul = document.querySelector("#seoul");
const shenzhen = document.querySelector("#shenzhen");v const washington_dc = document.querySelector("#washington_dc");
const frankfurt = document.querySelector("#frankfurt");
const paris = document.querySelector("#paris");
const beijing = document.querySelector("#beijing");
const zürich = document.querySelector("#zürich");
const luxembourg = document.querySelector("#luxembourg");
const sydney = document.querySelector("#sydney");
const tokyo = document.querySelector("#tokyo");
const dubai = document.querySelector("#dubai");

const timeFormat = 'hour\: "numeric", minute: "numeric", second: "numeric"';

function updateDisplay() {
const dateNow = new Date();
const nyFormat = new Intl.DateTimeFormat("en-us", {timeStyle: "medium", timeZone: "America/New_York"});
new_york.innerText = nyFormat.format(dateNow);
const londonFormat = new Intl.DateTimeFormat("en-gb", {timeStyle: "medium", timeZone: "Europe/London"});
london.innerText = londonFormat.format(dateNow);
const singaporeFormat = new Intl.DateTimeFormat("zh-sg", {timeStyle: "medium", timeZone: "Asia/Singapore"});
singapore.innerText = singaporeFormat.format(dateNow);
const hongKongFormat = new Intl.DateTimeFormat("zh-hk", {timeStyle: "medium", timeZone: "Asia/Hong_Kong"});
hong_kong.innerText = hongKongFormat.format(dateNow);
const sanFranciscoFormat = new Intl.DateTimeFormat("en-us", {timeStyle: "medium", timeZone: "America/Los_Angeles"});
san_francisco.innerText = sanFranciscoFormat.format(dateNow);
const shanghaiFormat = new Intl.DateTimeFormat("zh-cn", {timeStyle: "medium", timeZone: "Asia/Shanghai"});
shanghai.innerText = shanghaiFormat.format(dateNow);
const genevaFormat = new Intl.DateTimeFormat("fr-ch", {timeStyle: "medium", timeZone: "Europe/Zurich"});
geneva.innerText = genevaFormat.format(dateNow);
const laFormat = new Intl.DateTimeFormat("en-us", {timeStyle: "medium", timeZone: "America/Los_Angeles"});
los_angeles.innerText = laFormat.format(dateNow);
const chicagoFormat = new Intl.DateTimeFormat("en-us", {timeStyle: "medium", timeZone: "America/Chicago"});
chicago.innerText = chicagoFormat.format(dateNow);
const seoulFormat = new Intl.DateTimeFormat("ko", {timeStyle: "medium", timeZone: "Asia/Seoul"});
seoul.innerText = seoulFormat.format(dateNow);
const shenzhenFormat = new Intl.DateTimeFormat("zh-cn", {timeStyle: "medium", timeZone: "Asia/Shanghai"});
shenzhen.innerText = shenzhenFormat.format(dateNow);
const dcFormat = new Intl.DateTimeFormat("en-us", {timeStyle: "medium", timeZone: "America/New_York"});
washington_dc.innerText = dcFormat.format(dateNow);
const frankfurtFormat = new Intl.DateTimeFormat("de", {timeStyle: "medium", timeZone: "Europe/Berlin"});
frankfurt.innerText = frankfurtFormat.format(dateNow);
const parisFormat = new Intl.DateTimeFormat("fr", {timeStyle: "medium", timeZone: "Europe/Paris"});
paris.innerText = parisFormat.format(dateNow);
const beijingFormat = new Intl.DateTimeFormat("zh-cn", {timeStyle: "medium", timeZone: "Asia/Shanghai"});
beijing.innerText = beijingFormat.format(dateNow);
const zürichFormat = new Intl.DateTimeFormat("de-ch", {timeStyle: "medium", timeZone: "Europe/Zurich"});
zürich.innerText = zürichFormat.format(dateNow);
const luxFormat = new Intl.DateTimeFormat("fr-lu", {timeStyle: "medium", timeZone: "Europe/Luxembourg"});
luxembourg.innerText = luxFormat.format(dateNow);
const sydneyFormat = new Intl.DateTimeFormat("en-au", {timeStyle: "medium", timeZone: "Australia/Sydney"});
sydney.innerText = sydneyFormat.format(dateNow);
const tokyoFormat = new Intl.DateTimeFormat("ja", {timeStyle: "medium", timeZone: "Asia/Tokyo"});
tokyo.innerText = tokyoFormat.format(dateNow);
const dubaiFormat = new Intl.DateTimeFormat("ar-ae", {timeStyle: "medium", timeZone: "Asia/Dubai"});
dubai.innerText = dubaiFormat.format(dateNow);
setTimeout(updateDisplay, 1000);
}
updateDisplay();
</script>
</body>
</html>

Copy and paste this code into a new file in your HTML editor, save the file as javascript-demo-71.html, and open the file in a web browser. Depending on the current UTC time, you should see something like the illustration below.


Local times in twenty global financial centres . . . again!

Local times in twenty global financial centres . . . again!


We don't actually gain anything by writing our code using Intl.DateTimeFormat() - in fact we've actually written more code and, as you can see, the output is identical to that of the previous version. However, this is a relatively trivial example of how the Internationalisation API can be used. We offer this example merely to give you an idea of how it works. When used to its full potential, the API can provide a number of powerful features that facilitate writing code to handle applications intended for a diverse global audience.