Home

Awesome

Intl.DateTimeFormat.prototype.formatRange

Overview

Motivation

It's common for websites to display date intervals or date ranges to show the span of an event, such as a hotel reservation, the billing period of a service, or other similar uses. In order to implement this, websites often use localization libraries, such as Google Closure, to format the date range, or they may simply resort to formatting both dates independently.

If following the second alternative, web developers may encounter problems such as repeating fields that are common between the two dates, inappropriate order of the dates for the locale or using an incorrect delimiter for the locale.

For example:

let date1 = new Date(Date.UTC(2007, 0, 10)); // "Jan 10, 2007"
let date2 = new Date(Date.UTC(2007, 0, 20)); // "Jan 20, 2007"

let fmt = new Intl.DateTimeFormat('en', {
    year: 'numeric',
    month: 'long',
    day: 'numeric'
});

// The second date's 'month' and 'year' calendar fields are redundant, only
// 'day' provides new information.
console.log(`${fmt.format(date1)} – ${fmt.format(date2)}`);
// > 'January 10, 2007 – January 20, 2007'

Formatting date ranges in a concise and locale-aware way requires a non-negligible amount of raw or compiled data: localized patterns are needed for all the possible combinations of displayed calendar fields (e.g. month, day, hour), their lengths (e.g. 2-digit, numeric) and which is the largest different calendar field between the range's two dates.

For example, formatting the following ranges using the same options ({year: 'numeric', month: 'short', day: 'numeric'}) requires two different patterns:

Date range from January 10, 2017 to January 20, 2017:

Date range from January 10, 2017 to February 20, 2017:

Bringing this functionality into the platform will improve performance of the web and developer productivity as they will be able to format date ranges correctly, flexibly and concisely without the extra locale data size and overhead.

Status

Stage 4

Polyfill: @formatjs/intl-datetimeformat

Proposal

Add formatRange(date1, date2) and formatRangeToParts(date1, date2) to Intl.DateTimeFormat to enable date range formatting.

This proposal is based on the ICU Date Interval Formatter and on the Unicode CLDR TR-35 spec on date intervals.

API

Intl.DateTimeFormat.prototype.formatRange(date1, date2)

This method receives two Dates and formats the date range in the most concise way based on the locale and options provided when instantiating Intl.DateTimeFormat.

Intl.DateTimeFormat.prototype.formatRangeToParts(date1, date2)

This method receives two Dates and returns an Array of objects containing the locale-specific tokens representing each part of the formatted date range.

See: Intl.DateTimeFormat.prototype.formatToParts.

Example Usage

Intl.DateTimeFormat.prototype.formatRange(date1, date2)

let date1 = new Date(Date.UTC(2007, 0, 10, 10, 0, 0));
let date2 = new Date(Date.UTC(2007, 0, 10, 11, 0, 0));
let date3 = new Date(Date.UTC(2007, 0, 20, 10, 0, 0));
// > 'Wed, 10 Jan 2007 10:00:00 GMT'
// > 'Wed, 10 Jan 2007 11:00:00 GMT'
// > 'Sat, 20 Jan 2007 10:00:00 GMT'

let fmt1 = new Intl.DateTimeFormat("en", {
    year: '2-digit',
    month: 'numeric',
    day: 'numeric',
    hour: 'numeric',
    minute: 'numeric'
});
console.log(fmt1.format(date1));
console.log(fmt1.formatRange(date1, date2));
console.log(fmt1.formatRange(date1, date3));
// > '1/10/07, 10:00 AM'
// > '1/10/07, 10:00 – 11:00 AM'
// > '1/10/07, 10:00 AM – 1/20/07, 10:00 AM'

let fmt2 = new Intl.DateTimeFormat("en", {
    year: 'numeric',
    month: 'short',
    day: 'numeric'
});
console.log(fmt2.format(date1));
console.log(fmt2.formatRange(date1, date2));
console.log(fmt2.formatRange(date1, date3));
// > 'Jan 10, 2007'
// > 'Jan 10, 2007'
// > 'Jan 10 – 20, 2007'

Intl.DateTimeFormat.prototype.formatRangeToParts(date1, date2)

let date1 = new Date(Date.UTC(2007, 0, 10, 10, 0, 0));
let date2 = new Date(Date.UTC(2007, 0, 10, 11, 0, 0));
// > 'Wed, 10 Jan 2007 10:00:00 GMT'
// > 'Wed, 10 Jan 2007 11:00:00 GMT'

let fmt = new Intl.DateTimeFormat("en", {
    hour: 'numeric',
    minute: 'numeric'
});

console.log(fmt.formatRange(date1, date2));
// > '10:00 – 11:00 AM'

fmt.formatRangeToParts(date1, date2);
// return value:
// [
//   { type: 'hour',      value: '10',  source: "startRange" },
//   { type: 'literal',   value: ':',   source: "startRange" },
//   { type: 'minute',    value: '00',  source: "startRange" },
//   { type: 'literal',   value: ' – ', source: "shared"     },
//   { type: 'hour',      value: '11',  source: "endRange"   },
//   { type: 'literal',   value: ':',   source: "endRange"   },
//   { type: 'minute',    value: '00',  source: "endRange"   },
//   { type: 'literal',   value: ' ',   source: "shared"     },
//   { type: 'dayPeriod', value: 'AM',  source: "shared"     }
// ]

Alternatives

Intl.DateIntervalFormat

Instead of adding .formatRange() and .formatRangeToParts() to Intl.DateTimeFormat, a separate date interval formatter can be added. The API would be the following:

Benefits:

Drawbacks:

Prior art