This proposal specifies a mechanism for formatting sequences of measurement units (e.g., "feet and inches" or "meters and centimeters") within Intl.NumberFormat.
- Stage: 0
- Champion: @sffc
- Spec
- Original ECMA-402 issue
Presentations:
- 114th TC39: Slides
Measurement systems frequently employ multiple units in sequence to express a single magnitude. Examples include:
- Person height: "5 feet, 11 inches" or "1 meter, 80 centimeters"
- Mass: "2 pounds, 4 ounces"
Current ECMAScript implementations require manual composition of multiple Intl.NumberFormat outputs. This approach introduces risk regarding localized separators, unit ordering, and pluralization agreement, which vary significantly across locales. This proposal addresses these requirements by providing a standardized interface for multi-unit formatting.
Intl.NumberFormat is extended to support compound unit identifiers joined by the -and- separator (e.g., foot-and-inch, meter-and-centimeter, pound-and-ounce).
When a sequence unit is specified, format and formatToParts accept a JavaScript object as input. Each property of the object must correspond to a sub-unit identified in the sequence.
const nf = new Intl.NumberFormat('en-US', {
style: 'unit',
unit: 'foot-and-inch',
});
// "5 feet, 11 inches"
nf.format({ foot: 5, inch: 11 });
// "-5 feet, 11 inches" (Only the first unit renders the minus sign)
nf.format({ foot: -5, inch: -11 });
const massNf = new Intl.NumberFormat('en-US', {
style: 'unit',
unit: 'pound-and-ounce',
unitDisplay: 'long'
});
// "2 pounds, 4 ounces" (Actual output is locale-dependent)
massNf.format({ pound: 2, ounce: 4 });The formatting procedure for sequence units follows these steps:
- Validation: The sequence must be a valid combination of sanctioned units measuring the same quantity and arranged in descending order of magnitude. The sanctioned sequences are explicitly listed in the specification (e.g.,
foot-and-inch,kilogram-and-gram). Note that time units are excluded from sequence units, as they are managed byIntl.DurationFormat. - Extraction: Values are retrieved from the input object based on the sub-unit keys.
- Component Formatting:
- Intermediate Units: Required to be integers, and formatted as integers.
- Terminal Unit: Formatted according to the rounding and fraction settings configured on the
Intl.NumberFormatinstance.
- Composition: The resulting component strings are concatenated using
Intl.ListFormatwithtype: "unit"and the specifiedunitDisplaystyle to ensure locale-appropriate conjunctions and spacing.
Inputs to format or formatToParts must contain a value for every sub-unit defined in the unit identifier. Properties are read in the order they appear in the unit identifier sequence. If a required property is undefined or missing, a TypeError is thrown immediately.
After all properties have been read and converted to numbers, two final validations occur:
- Mixed Signs: All sub-units must have the same sign. Mixing positive and negative values (e.g.,
{ foot: 5, inch: -11 }) throws aRangeError. - Intermediate Integers: All intermediate sub-units (all but the final one) must be integers. Providing a non-integer intermediate value (e.g.,
{ foot: 5.5, inch: 6 }) throws aRangeError.
// Throws RangeError: invalid unit sequence (not in a sanctioned group or order)
new Intl.NumberFormat('en-US', { style: 'unit', unit: 'meter-and-foot' });
const nf = new Intl.NumberFormat('en-US', {
style: 'unit',
unit: 'foot-and-inch',
});
// Throws TypeError: 'inch' property is missing
nf.format({ foot: 5 });
// Throws RangeError: sub-units have mixed signs
nf.format({ foot: 5, inch: -11 });
// Throws RangeError: intermediate sub-unit is not an integer
nf.format({ foot: 5.5, inch: 6 });This proposal is compatible with the Intl Unit Protocol. When utilizing the protocol, the value property accepts the object mapping sub-units to their respective magnitudes:
const nf = new Intl.NumberFormat('en-US');
nf.format({
unit: "foot-and-inch",
value: { foot: 6, inch: 4 }
});This proposal aligns with the Amount proposal. To support sequence units, an Amount instance encapsulates a structured value (an object mapping sub-units to magnitudes) rather than a scalar numeric value.
The formatting requirements for sequence units are defined in Unicode Technical Standard #35 (LDML), Section Unit Sequences (Mixed Units). TR35 specifies the use of localized listPattern data to compose these sequences, which this proposal implements for ECMAScript.
Some units commonly used in sequences, such as arcminute and arcsecond, are not currently sanctioned in ECMA-402. Formatting angular measurements like "5° 30′ 12″" (which would conceptually use degree-and-arcminute-and-arcsecond) would require a separate proposal to add arcminute and arcsecond to the list of sanctioned single unit identifiers before they can be utilized as sequence units.
The possibility of accepting a single scalar value (e.g., 6.5) and automatically partitioning it into sequence units (e.g., "6 feet, 6 inches") was evaluated and rejected for the following reasons:
- Input Ambiguity: Scalar inputs do not inherently define which sub-unit of a sequence they represent (e.g., in
foot-and-inch, it is unclear if6.5refers to feet or inches). - Incomplete Conversion Data: The engine may not possess conversion factors for all possible unit combinations, particularly for custom or future units. This precludes reliable automated partitioning.
- Precision and Rounding: Automated partitioning via floating-point arithmetic can introduce errors. Requiring pre-partitioned input ensures the output accurately reflects the developer's data.