Stage 0 Draft / May 8, 2026

Intl Sequence Units

1 Identification of Locales, Currencies, Time Zones, Measurement Units, Numbering Systems, Collations, and Calendars

1.1 Measurement Unit Identifiers

This specification identifies measurement units using a core unit identifier (or equivalently core unit ID) as defined by Unicode Technical Standard #35 Part 2 General, Section 6.2 Unit Identifiers. Their canonical form is a string containing only Unicode Basic Latin lowercase letters (U+0061 LATIN SMALL LETTER A through U+007A LATIN SMALL LETTER Z) with zero or more medial hyphens (U+002D HYPHEN-MINUS).

Only a limited set of core unit identifiers are sanctioned. Attempting to use an unsanctioned core unit identifier results in a RangeError.

1.1.1 IsWellFormedUnitIdentifier ( unitIdentifier )

The abstract operation IsWellFormedUnitIdentifier takes argument unitIdentifier (a String) and returns a Boolean. It verifies that the unitIdentifier argument represents a well-formed core unit identifier that is either a sanctioned single unit or a complex unit formed by division of two sanctioned single units. It performs the following steps when called:

  1. If IsSanctionedSingleUnitIdentifier(unitIdentifier) is true, then
    1. Return true.
  2. Let sequence be StringSplitToList(unitIdentifier, "-and-").
  3. If the length of sequence > 1, then
    1. For each row row of Table 1, do
      1. Let group be the list of simple unit identifiers listed in the Sequence column of row.
      2. If group contains sequence[0], then
        1. If any of the elements of sequence are not in group, return false.
        2. If the elements of sequence are not in the same order as they are in group, return false.
        3. Return true.
    2. Return false.
  4. Let i be StringIndexOf(unitIdentifier, "-per-", 0).
  5. If i is not-found or StringIndexOf(unitIdentifier, "-per-", i + 1) is not not-found, then
    1. Return false.
  6. Assert: The five-character substring "-per-" occurs exactly once in unitIdentifier, at index i.
  7. Let per be StringSplitToList(unitIdentifier, "-per-").
  8. If the length of per is not 2, return false.
  9. Let numerator be the substring of unitIdentifier from 0 to i per[0].
  10. Let denominator be the substring of unitIdentifier from i + 5 per[1].
  11. If IsSanctionedSingleUnitIdentifier(numerator) and IsSanctionedSingleUnitIdentifier(denominator) are both true, then
    1. Return true.
  12. Return false.
Table 1: Sanctioned Sequence Unit Groups
Sequence
« "mile", "yard", "foot", "inch" »
« "kilometer", "meter", "centimeter", "millimeter" »
« "stone", "pound", "ounce" »
« "kilogram", "gram" »
« "gallon", "fluid-ounce" »
« "liter", "milliliter" »

2 NumberFormat Objects

2.1 Properties of the Intl.NumberFormat Prototype Object

The Intl.NumberFormat prototype object:

  • is %Intl.NumberFormat.prototype%.
  • is an ordinary object.
  • is not an Intl.NumberFormat instance and does not have an [[InitializedNumberFormat]] internal slot or any of the other internal slots of Intl.NumberFormat instance objects.
  • has a [[Prototype]] internal slot whose value is %Object.prototype%.

2.1.1 Number Format Functions

A Number format function is an anonymous built-in function that has a [[NumberFormat]] internal slot.

When a Number format function F is called with optional argument value, the following steps are taken:

  1. Let nf be F.[[NumberFormat]].
  2. Assert: nf is an Object and nf has an [[InitializedNumberFormat]] internal slot.
  3. If value is not provided, let value be undefined.
  4. If nf.[[Style]] is "unit" and StringIndexOf(nf.[[Unit]], "-and-", 0) is not not-found, then
    1. Let values be ? ToSequenceUnitList(nf.[[Unit]], value).
    2. Return FormatNumericSequence(nf, values).
  5. Let x be ? ToIntlMathematicalValue(value).
  6. Return FormatNumeric(nf, x).

The "length" property of a Number format function is 1𝔽.

2.1.2 Intl.NumberFormat.prototype.formatToParts ( value )

When the formatToParts method is called with an optional argument value, the following steps are taken:

  1. Let nf be the this value.
  2. Perform ? RequireInternalSlot(nf, [[InitializedNumberFormat]]).
  3. If nf.[[Style]] is "unit" and StringIndexOf(nf.[[Unit]], "-and-", 0) is not not-found, then
    1. Let values be ? ToSequenceUnitList(nf.[[Unit]], value).
    2. Return FormatNumericSequenceToParts(nf, values).
  4. Let x be ? ToIntlMathematicalValue(value).
  5. Return FormatNumericToParts(nf, x).

2.2 Abstract Operations for NumberFormat Objects

2.2.1 ToSequenceUnitList ( unit, value )

The abstract operation ToSequenceUnitList takes arguments unit (a String) and value (an ECMAScript language value) and returns either a normal completion containing a List of Intl mathematical values, or a throw completion. It performs the following steps when called:

  1. If value is not an Object, throw a TypeError exception.
  2. Let sequence be StringSplitToList(unit, "-and-").
  3. Let values be a new empty List.
  4. Let hasNegative be false.
  5. Let hasPositive be false.
  6. Let hasNonInteger be false.
  7. Let i be 0.
  8. For each String subUnit of sequence, do
    1. If i is the length of sequence - 1, let isLast be true; else let isLast be false.
    2. Let subValue be ? Get(value, subUnit).
    3. If subValue is undefined, throw a TypeError exception.
    4. Let x be ? ToIntlMathematicalValue(subValue).
    5. If isLast is false and x is not an integer, set hasNonInteger to true.
    6. If x < 0, set hasNegative to true.
    7. If x > 0, set hasPositive to true.
    8. Append x to values.
    9. Set i to i + 1.
  9. If hasNegative is true and hasPositive is true, throw a RangeError exception.
  10. If hasNonInteger is true, throw a RangeError exception.
  11. Return values.

2.2.2 FormatNumericSequence ( numberFormat, values )

The abstract operation FormatNumericSequence takes arguments numberFormat (an Intl.NumberFormat) and values (a List of Intl mathematical values) and returns a String. It performs the following steps when called:

  1. Let parts be PartitionSequenceUnitPattern(numberFormat, values).
  2. Let result be the empty String.
  3. For each Record { [[Type]], [[Value]] } part of parts, do
    1. Set result to the string-concatenation of result and part.[[Value]].
  4. Return result.

2.2.3 FormatNumericSequenceToParts ( numberFormat, values )

The abstract operation FormatNumericSequenceToParts takes arguments numberFormat (an Intl.NumberFormat) and values (a List of Intl mathematical values) and returns an Array. It performs the following steps when called:

  1. Let parts be PartitionSequenceUnitPattern(numberFormat, values).
  2. Let result be ! ArrayCreate(0).
  3. Let n be 0.
  4. For each Record { [[Type]], [[Value]] } part of parts, do
    1. Let O be OrdinaryObjectCreate(%Object.prototype%).
    2. Perform ! CreateDataPropertyOrThrow(O, "type", part.[[Type]]).
    3. Perform ! CreateDataPropertyOrThrow(O, "value", part.[[Value]]).
    4. Perform ! CreateDataPropertyOrThrow(result, ! ToString(𝔽(n)), O).
    5. Increment n by 1.
  5. Return result.

2.2.4 PartitionSequenceUnitPattern ( numberFormat, values )

The abstract operation PartitionSequenceUnitPattern takes arguments numberFormat (an Intl.NumberFormat) and values (a List of Intl mathematical values) and returns a List of Records with fields [[Type]] (a String) and [[Value]] (a String). It performs the following steps when called:

  1. Assert: numberFormat.[[Style]] is "unit" and StringIndexOf(numberFormat.[[Unit]], "-and-", 0) is not not-found.
  2. Let sequence be StringSplitToList(numberFormat.[[Unit]], "-and-").
  3. Let partitionedPartsList be a new empty List.
  4. Let lfOpts be OrdinaryObjectCreate(null).
  5. Perform ! CreateDataPropertyOrThrow(lfOpts, "type", "unit").
  6. Perform ! CreateDataPropertyOrThrow(lfOpts, "style", numberFormat.[[UnitDisplay]]).
  7. Let lf be ! Construct(%Intl.ListFormat%, « numberFormat.[[Locale]], lfOpts »).
  8. Let strings be a new empty List.
  9. Let originalUnit be numberFormat.[[Unit]].
  10. Let originalRoundingType be numberFormat.[[RoundingType]].
  11. Let originalMinFD be numberFormat.[[MinimumFractionDigits]].
  12. Let originalMaxFD be numberFormat.[[MaximumFractionDigits]].
  13. Let originalMinSD be numberFormat.[[MinimumSignificantDigits]].
  14. Let originalMaxSD be numberFormat.[[MaximumSignificantDigits]].
  15. Let i be 0.
  16. For each Intl mathematical value x of values, do
    1. If i is 0, let isFirst be true; else let isFirst be false.
    2. If i is the length of sequence - 1, let isLast be true; else let isLast be false.
    3. If isFirst is false, then
      1. If x < 0, set x to -x.
      2. If x is negative-zero, set x to 0.
    4. Let subUnit be sequence[i].
    5. Set numberFormat.[[Unit]] to subUnit.
    6. If isLast is false, then
      1. Set numberFormat.[[RoundingType]] to fraction-digits.
      2. Set numberFormat.[[MinimumFractionDigits]] to 0.
      3. Set numberFormat.[[MaximumFractionDigits]] to 3.
    7. Else,
      1. Set numberFormat.[[RoundingType]] to originalRoundingType.
      2. Set numberFormat.[[MinimumFractionDigits]] to originalMinFD.
      3. Set numberFormat.[[MaximumFractionDigits]] to originalMaxFD.
      4. Set numberFormat.[[MinimumSignificantDigits]] to originalMinSD.
      5. Set numberFormat.[[MaximumSignificantDigits]] to originalMaxSD.
    8. Let parts be PartitionNumberPattern(numberFormat, x).
    9. Append parts to partitionedPartsList.
    10. Let string be the empty String.
    11. For each Record { [[Type]], [[Value]] } part in parts, do
      1. Set string to the string-concatenation of string and part.[[Value]].
    12. Append string to strings.
    13. Set i to i + 1.
  17. Set numberFormat.[[Unit]] to originalUnit.
  18. Let formattedPartsList be CreatePartsFromList(lf, strings).
  19. Let flattenedPartsList be a new empty List.
  20. Let partitionedPartsIndex be 0.
  21. NOTE: The following loop substitutes the formatted elements returned by CreatePartsFromList with the detailed parts saved in partitionedPartsList.
  22. For each Record { [[Type]], [[Value]] } listPart in formattedPartsList, do
    1. If listPart.[[Type]] is "element", then
      1. Let parts be partitionedPartsList[partitionedPartsIndex].
      2. Append the elements of parts to flattenedPartsList.
      3. Set partitionedPartsIndex to partitionedPartsIndex + 1.
    2. Else,
      1. Assert: listPart.[[Type]] is "literal".
      2. Append listPart to flattenedPartsList.
  23. Return flattenedPartsList.

Copyright & Software License

Software License

All Software contained in this document ("Software") is protected by copyright and is being made available under the "BSD License", included below. This Software may be subject to third party rights (rights from parties other than Ecma International), including patent rights, and no licenses under such third party rights are granted under this license even if the third party concerned is a member of Ecma International. SEE THE ECMA CODE OF CONDUCT IN PATENT MATTERS AVAILABLE AT https://ecma-international.org/memento/codeofconduct.htm FOR INFORMATION REGARDING THE LICENSING OF PATENT CLAIMS THAT ARE REQUIRED TO IMPLEMENT ECMA INTERNATIONAL STANDARDS.

Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

  1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
  2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
  3. Neither the name of the authors nor Ecma International may be used to endorse or promote products derived from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE ECMA INTERNATIONAL "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL ECMA INTERNATIONAL BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.