Stage 2 Draft / September 30, 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 The Intl.NumberFormat Constructor

The Intl.NumberFormat constructor:

  • is %Intl.NumberFormat%.
  • is the initial value of the "NumberFormat" property of the Intl object.

Behaviour common to all service constructor properties of the Intl object is specified in 9.1.

2.1.1 Intl.NumberFormat ( [ locales [ , options ] ] )

When the Intl.NumberFormat function is called with optional arguments locales and options, the following steps are taken:

  1. If NewTarget is undefined, let newTarget be the active function object, else let newTarget be NewTarget.
  2. Let numberFormat be ? OrdinaryCreateFromConstructor(newTarget, "%Intl.NumberFormat.prototype%", « [[InitializedNumberFormat]], [[Locale]], [[LocaleData]], [[NumberingSystem]], [[Style]], [[Unit]], [[UnitDisplay]], [[Currency]], [[CurrencyDisplay]], [[CurrencySign]], [[MinimumIntegerDigits]], [[MinimumFractionDigits]], [[MaximumFractionDigits]], [[MinimumSignificantDigits]], [[MaximumSignificantDigits]], [[RoundingType]], [[Notation]], [[CompactDisplay]], [[UseGrouping]], [[SignDisplay]], [[RoundingIncrement]], [[RoundingMode]], [[ComputedRoundingPriority]], [[TrailingZeroDisplay]], [[BoundFormat]] »).
  3. Let optionsResolution be ? ResolveOptions(%Intl.NumberFormat%, %Intl.NumberFormat%.[[LocaleData]], locales, options, « coerce-options »).
  4. Set options to optionsResolution.[[Options]].
  5. Let r be optionsResolution.[[ResolvedLocale]].
  6. Set numberFormat.[[Locale]] to r.[[Locale]].
  7. Set numberFormat.[[LocaleData]] to r.[[LocaleData]].
  8. Set numberFormat.[[NumberingSystem]] to r.[[nu]].
  9. Perform ? SetNumberFormatUnitOptions(numberFormat, options).
  10. Let style be numberFormat.[[Style]].
  11. Let notation be ? GetOption(options, "notation", string, « "standard", "scientific", "engineering", "compact" », "standard").
  12. Set numberFormat.[[Notation]] to notation.
  13. If style is "currency" and notation is "standard", then
    1. Let currency be numberFormat.[[Currency]].
    2. Let cDigits be CurrencyDigits(currency).
    3. Let mnfdDefault be cDigits.
    4. Let mxfdDefault be cDigits.
  14. Else,
    1. Let mnfdDefault be 0.
    2. If style is "percent", then
      1. Let mxfdDefault be 0.
    3. Else,
      1. Let mxfdDefault be 3.
  15. Perform ? SetNumberFormatDigitOptions(numberFormat, options, mnfdDefault, mxfdDefault, notation).
  16. If style is "unit" and StringIndexOf(numberFormat.[[Unit]], "-and-", 0) is not not-found, then
    1. If numberFormat.[[RoundingType]] is not fraction-digits, throw a RangeError exception.
  17. Let compactDisplay be ? GetOption(options, "compactDisplay", string, « "short", "long" », "short").
  18. Let defaultUseGrouping be "auto".
  19. If notation is "compact", then
    1. Set numberFormat.[[CompactDisplay]] to compactDisplay.
    2. Set defaultUseGrouping to "min2".
  20. NOTE: For historical reasons, the strings "true" and "false" are accepted and replaced with the default value.
  21. Let useGrouping be ? GetBooleanOrStringNumberFormatOption(options, "useGrouping", « "min2", "auto", "always", "true", "false" », defaultUseGrouping).
  22. If useGrouping is "true" or useGrouping is "false", set useGrouping to defaultUseGrouping.
  23. If useGrouping is true, set useGrouping to "always".
  24. Set numberFormat.[[UseGrouping]] to useGrouping.
  25. Let signDisplay be ? GetOption(options, "signDisplay", string, « "auto", "never", "always", "exceptZero", "negative" », "auto").
  26. Set numberFormat.[[SignDisplay]] to signDisplay.
  27. If the implementation supports the normative optional constructor mode of 4.3 Note 1, then
    1. Let this be the this value.
    2. Return ? ChainNumberFormat(numberFormat, NewTarget, this).
  28. Return numberFormat.

2.2 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.2.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.2.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.3 Abstract Operations for NumberFormat Objects

2.3.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 iteratorRecord be ? GetIterator(value, sync).
  4. Let iterableValues be ? IteratorToList(iteratorRecord).
  5. If the length of iterableValues is not the length of sequence, throw a RangeError exception.
  6. Let values be a new empty List.
  7. Let hasNegative be false.
  8. Let hasPositive be false.
  9. Let hasNonInteger be false.
  10. Let i be 0.
  11. For each ECMAScript language value subValue of iterableValues, do
    1. If i is the length of sequence - 1, let isLast be true; else let isLast be false.
    2. If subValue is undefined, throw a TypeError exception.
    3. Let x be ? ToIntlMathematicalValue(subValue).
    4. If isLast is false and x is not an integer, set hasNonInteger to true.
    5. If x < 0, set hasNegative to true.
    6. If x > 0, set hasPositive to true.
    7. Append x to values.
    8. Set i to i + 1.
  12. If hasNegative is true and hasPositive is true, throw a RangeError exception.
  13. If hasNonInteger is true, throw a RangeError exception.
  14. Return values.

2.3.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.3.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.3.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. Assert: numberFormat.[[RoundingType]] is fraction-digits.
  3. Let sequence be StringSplitToList(numberFormat.[[Unit]], "-and-").
  4. Let partitionedPartsList be a new empty List.
  5. Let lfOpts be OrdinaryObjectCreate(null).
  6. Perform ! CreateDataPropertyOrThrow(lfOpts, "type", "unit").
  7. Perform ! CreateDataPropertyOrThrow(lfOpts, "style", numberFormat.[[UnitDisplay]]).
  8. Let lf be ! Construct(%Intl.ListFormat%, « numberFormat.[[Locale]], lfOpts »).
  9. Let strings be a new empty List.
  10. Let originalUnit be numberFormat.[[Unit]].
  11. Let originalMinFD be numberFormat.[[MinimumFractionDigits]].
  12. Let originalMaxFD be numberFormat.[[MaximumFractionDigits]].
  13. Let i be 0.
  14. 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.[[MinimumFractionDigits]] to 0.
      2. Set numberFormat.[[MaximumFractionDigits]] to 3.
    7. Else,
      1. Set numberFormat.[[MinimumFractionDigits]] to originalMinFD.
      2. Set numberFormat.[[MaximumFractionDigits]] to originalMaxFD.
    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.
  15. Set numberFormat.[[Unit]] to originalUnit.
  16. Let formattedPartsList be CreatePartsFromList(lf, strings).
  17. Let flattenedPartsList be a new empty List.
  18. Let partitionedPartsIndex be 0.
  19. NOTE: The following loop substitutes the formatted elements returned by CreatePartsFromList with the detailed parts saved in partitionedPartsList.
  20. 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.
  21. 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.