Skip to content

Add GetAmountConvertToUnit definitions to both ECMA-262 and ECMA-402 - #110

Merged
jessealama merged 40 commits into
mainfrom
convert-options
Sep 28, 2026
Merged

jessealama merged 40 commits into
mainfrom
convert-options

Conversation

@eemeli

@eemeli eemeli commented May 16, 2026 •

Copy link
Copy Markdown
Member

Fixes #109 by adding an AO to ECMA-262 that's superseded by a redefinition in ECMA-402.

The actual extraction of preferred units from the CLDR units.xml is left as a TODO. Edit: now included.

The preferences depend on unit category, usage, region, and value thresholds. The categories we get from validity/unit.xml, the region is calculated from the first-choice locale, and the preferences themselves are in supplemental/units.xml.

As the data always includes 001 ("the world") as one of the regions for each supported category + usage combination, no locale fallback is ever done during .convertTo(), as all well-formed locales are supported.

@github-actions

github-actions Bot commented May 16, 2026 •

Copy link
Copy Markdown
PR Preview Action v1.8.1
Preview removed because the pull request was closed.
2026-09-28 01:16 UTC

@eemeli
eemeli force-pushed the convert-options branch from 46f7cdf to 3c8bebf Compare May 17, 2026 19:45
@eemeli
eemeli requested a review from jessealama May 17, 2026 19:50
@jessealama

Copy link
Copy Markdown
Collaborator

This looks good. Thanks!

Comment thread spec.emu Outdated
Comment thread spec.emu

@gibson042 gibson042 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! I have many suggestions, but this is an excellent starting point.

Comment thread spec.emu Outdated
Comment thread intl.emu Outdated
Comment thread intl.emu Outdated
Comment thread intl.emu Outdated
Comment thread intl.emu Outdated
Comment thread intl.emu Outdated
Comment thread intl.emu Outdated
Comment thread intl.emu Outdated
Comment thread intl.emu Outdated
Comment thread intl.emu
@eemeli
eemeli requested a review from gibson042 May 18, 2026 15:16

@gibson042 gibson042 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd like to name the Records that are passed between operations, but won't insist on it being part of this PR. The rest of this review is a large handful of non-blocking suggestions.

Comment thread spec.emu Outdated
Comment thread spec.emu Outdated
Comment thread spec.emu Outdated
Comment thread spec.emu Outdated
Comment thread intl.emu
Comment thread intl.emu Outdated
Comment thread intl.emu
@jessealama
jessealama force-pushed the convert-options branch 3 times, most recently from fd51602 to 964a6d1 Compare August 19, 2026 08:31
jessealama added a commit that referenced this pull request Aug 19, 2026
Options should be read in alphabetical order (see #95 and review
feedback on #110), and alphabetically "locale" precedes "usage".
@jessealama
jessealama requested a review from gibson042 August 19, 2026 12:14

@gibson042 gibson042 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is still slightly too far off from #95.

Comment thread intl.emu Outdated
Comment thread spec.emu Outdated
Comment thread spec.emu Outdated
Comment thread intl.emu Outdated
Comment thread intl.emu
Comment on lines +60 to +61
1. Let _requestedLocales_ be ? CanonicalizeLocaleList(_locale_).
1. If _requestedLocales_ is an empty List, let _resolvedLocale_ be DefaultLocale(); else let _resolvedLocale_ be the first element of _requestedLocales_.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should use actual locale negotiation rather than just picking the first element; again, see #95.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree. I propose to punt that to a separate branch that tackles #95.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nope, for conversion every entry includes a 001 region, which means that the region of a valid locale will always be supported, and that therefore we will never fall back to a second-choice locale.

This does raise the question of whether we should have a region option instead of a locale option, as the data is region-dependent rather than language-dependent.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've tweaked things so that we use RegionPreference here, which also picks up region. In light of the fact taht every category and usage has a 001 entry, no requested locale can go unsupported, so I'm not sure I see how negotiation should go. I may be missing something. Perhaps file an issue to tackle this?

On region versus locale, I'd lean toward keeping locale. UTS 35 takes a locale as input, and it matches how Intl.Locale handles region-based data (calendars, hour cycles). That strikes me as sufficient, or do we need more here?

Comment thread intl.emu
1. Let _unit_ be ~unset~.
1. For each element _entry_ of _preferredUnits_, do
1. Set _unit_ to _entry_.[[Unit]].
1. Let _convertedValue_ be ? ConvertUnitValue(_sourceValue_, _sourceUnit_, _unit_).

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a bit awkward to specify O(n) ConvertUnitValue calls (n - 1 in SelectTargetUnit and the last in Amount.prototype.convertTo). I'm not objecting, but we should look for ways to improve that.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree. Would you like to file a follow-on issue to track this?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment thread intl.emu Outdated
Comment thread intl.emu
Comment thread intl.emu Outdated
jessealama and others added 6 commits September 7, 2026 09:25
Co-authored-by: Richard Gibson <richard.gibson@gmail.com>
The String branch of `.convertTo` collapsed "-0" to the
mathematical value 0 before building the Number used for the
conversion, so the sign got lost.
Move every observable property read into `SetAmountOptions`
that ECMA-402 redefines with the help of a new
`[[ExtendedOptions]]` field. `SetAmountOptions` also
validates unit syntax as soon as the unit is read.
@jessealama
jessealama requested a review from gibson042 September 7, 2026 08:55
Comment thread README.md Outdated

@gibson042 gibson042 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM after a large handful of tweaks.

Comment thread README.md
so `amount.convertTo("milliliter")` is the same as `amount.convertTo({ unit: "milliliter" })`.
* `locale` (String or Array of Strings or undefined):
The locale for which the preferred unit of the corresponding category is determined.
Unit preferences depend only on the region of the locale, determined as in ECMA-402's [RegionPreference](https://tc39.es/ecma402/#sec-regionpreference): (1) the `rg` Unicode extension, if present ('US' for `en-GB-u-rg-uszzzz`), (2) the locale's region subtag (`GB` for `en-GB`), and finally (3) the most likely region for the locale (`US` for `en`).

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Unit preferences depend only on the region of the locale, determined as in ECMA-402's [RegionPreference](https://tc39.es/ecma402/#sec-regionpreference): (1) the `rg` Unicode extension, if present ('US' for `en-GB-u-rg-uszzzz`), (2) the locale's region subtag (`GB` for `en-GB`), and finally (3) the most likely region for the locale (`US` for `en`).
Unit preferences depend only on the region of the locale, determined as in ECMA-402's [RegionPreference](https://tc39.es/ecma402/#sec-regionpreference): (1) the `rg` Unicode extension if present ("US" for `en-GB-u-rg-uszzzz`), (2) the locale's region subtag if present ("CH" for `de-CH`), the most likely region for the locale if known ("BR" for `pt`), and finally "001" (the World) as an ultimate fallback.

Comment thread spec.emu
<h1>Amount Options Record</h1>
<p>
An <dfn variants="Amount Options Records">Amount Options Record</dfn> is a Record holding the resolved values of the options
accepted by the Amount constructor and by <emu-xref href="#sec-amount.prototype.convertto">Amount.prototype.convertTo</emu-xref>.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Simplification:

Suggested change
accepted by the Amount constructor and by <emu-xref href="#sec-amount.prototype.convertto">Amount.prototype.convertTo</emu-xref>.
accepted by the Amount constructor and by <emu-xref href="#sec-amount.prototype.convertto" title></emu-xref>.

Comment thread spec.emu
1. Set _roundingMode_ to ? GetOption(_opts_, *"roundingMode"*, ~string~, « *"ceil"*, *"floor"*, *"expand"*, *"trunc"*, *"halfCeil"*, *"halfFloor"*, *"halfExpand"*, *"halfTrunc"*, *"halfEven"* », *"halfEven"*).
1. Set _significantDigits_ to ? GetOption(_opts_, *"significantDigits"*, ~number~, ~empty~, *undefined*).
1. Let _unit_ be ? GetOption(_opts_, *"unit"*, ~string~, ~empty~, *undefined*).
1. If ParseText(_opts_, |UnitIdentifier|) is not a Parse Node, throw a *RangeError* exception.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This may change based on discussion in next week's plenary, but I at least am advocating for

Suggested change
1. If ParseText(_opts_, |UnitIdentifier|) is not a Parse Node, throw a *RangeError* exception.
1. If ParseText(_opts_, |UnitIdentifier|) is not a Parse Node, throw a *SyntaxError* exception.

Comment thread spec.emu
Comment on lines +169 to +174
<p>
An ECMAScript implementation that includes the ECMA-402 Internationalization API
must implement this abstract operation as specified in the ECMA-402 specification.
If an ECMAScript implementation does not include the ECMA-402 API,
SetAmountOptions performs the following steps when called:
</p>

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit:

Suggested change
<p>
An ECMAScript implementation that includes the ECMA-402 Internationalization API
must implement this abstract operation as specified in the ECMA-402 specification.
If an ECMAScript implementation does not include the ECMA-402 API,
SetAmountOptions performs the following steps when called:
</p>
<p>
An ECMAScript implementation that includes the ECMA-402 Internationalization API
must implement this abstract operation as specified in ECMA-402.
Otherwise, SetAmountOptions performs the following steps when called:
</p>

(mirroring e.g. AvailableNamedTimeZoneIdentifiers)

Comment thread spec.emu
Comment on lines +160 to +163
SetAmountOptions (
_target_: an Amount Options Record,
_source_: an Object,
): either a normal completion containing ~unused~ or a throw completion

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we want to read "locale" and "usage" for conversion but not construction, which means that distinction should be expressed in a parameter.

Suggested change
SetAmountOptions (
_target_: an Amount Options Record,
_source_: an Object,
): either a normal completion containing ~unused~ or a throw completion
SetAmountOptions (
_target_: an Amount Options Record,
_source_: an Object,
_purpose_: ~construction~ or ~unit-conversion~,
): either a normal completion containing ~unused~ or a throw completion

Comment thread intl.emu

<emu-clause id="sup-amount-options-record">
<h1>Amount Options Record</h1>
<p>In an implementation that includes the ECMA-402 Internationalization API, the [[ExtendedOptions]] field of an <emu-xref href="#sec-amount-options-record">Amount Options Record</emu-xref> that has been populated by SetAmountOptions is a Record with fields [[Locale]] (an ECMAScript language value) and [[Usage]] (a String or *undefined*).</p>

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
<p>In an implementation that includes the ECMA-402 Internationalization API, the [[ExtendedOptions]] field of an <emu-xref href="#sec-amount-options-record">Amount Options Record</emu-xref> that has been populated by SetAmountOptions is a Record with fields [[Locale]] (an ECMAScript language value) and [[Usage]] (a String or *undefined*).</p>
<p>In an implementation that includes the ECMA-402 Internationalization API, the [[ExtendedOptions]] field of an <emu-xref href="#sec-amount-options-record">Amount Options Record</emu-xref> that has been populated by SetAmountOptions is a Record with fields [[Locales]] (a Language Priority List or *undefined*) and [[Usage]] (a String or *undefined*).</p>

Comment thread intl.emu
Comment on lines +85 to +97
1. Let _locale_ be _extendedOpts_.[[Locale]].
1. Let _usage_ be _extendedOpts_.[[Usage]].
1. If _unit_ is not *undefined*, then
1. If _usage_ is not *undefined* or _locale_ is not *undefined*, throw a *TypeError* exception.
1. Return _unit_.
1. If _usage_ is *undefined*, then
1. If _locale_ is *undefined*, throw a *TypeError* exception.
1. Set _usage_ to *"default"*.
1. Let _sourceUnit_ be _conversionSource_.[[Unit]].
1. Let _sourceValue_ be GetAmountNumericValue(_conversionSource_).
1. Let _category_ be ? GetUnitCategory(_sourceUnit_).
1. Let _requestedLocales_ be ? CanonicalizeLocaleList(_locale_).
1. If _requestedLocales_ is an empty List, let _resolvedLocale_ be DefaultLocale(); else let _resolvedLocale_ be the first element of _requestedLocales_.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
1. Let _locale_ be _extendedOpts_.[[Locale]].
1. Let _usage_ be _extendedOpts_.[[Usage]].
1. If _unit_ is not *undefined*, then
1. If _usage_ is not *undefined* or _locale_ is not *undefined*, throw a *TypeError* exception.
1. Return _unit_.
1. If _usage_ is *undefined*, then
1. If _locale_ is *undefined*, throw a *TypeError* exception.
1. Set _usage_ to *"default"*.
1. Let _sourceUnit_ be _conversionSource_.[[Unit]].
1. Let _sourceValue_ be GetAmountNumericValue(_conversionSource_).
1. Let _category_ be ? GetUnitCategory(_sourceUnit_).
1. Let _requestedLocales_ be ? CanonicalizeLocaleList(_locale_).
1. If _requestedLocales_ is an empty List, let _resolvedLocale_ be DefaultLocale(); else let _resolvedLocale_ be the first element of _requestedLocales_.
1. Let _requestedLocales_ be _extendedOpts_.[[Locales]].
1. Let _usage_ be _extendedOpts_.[[Usage]].
1. If _unit_ is not *undefined*, then
1. If _usage_ is not *undefined* or _requestedLocales_ is not *undefined*, throw a *TypeError* exception.
1. Return _unit_.
1. If _usage_ is *undefined*, then
1. If _requestedLocales_ is *undefined*, throw a *TypeError* exception.
1. Set _usage_ to *"default"*.
1. Let _sourceUnit_ be _conversionSource_.[[Unit]].
1. Let _sourceValue_ be GetAmountNumericValue(_conversionSource_).
1. Let _category_ be ? GetUnitCategory(_sourceUnit_).
1. If _requestedLocales_ is an empty List, let _resolvedLocale_ be DefaultLocale(); else let _resolvedLocale_ be the first element of _requestedLocales_.

Comment thread spec.emu Outdated
Comment thread intl.emu
Comment thread intl.emu Outdated
jessealama and others added 4 commits September 28, 2026 03:09
Co-authored-by: Richard Gibson <richard.gibson@gmail.com>
Co-authored-by: Richard Gibson <richard.gibson@gmail.com>
Co-authored-by: Richard Gibson <richard.gibson@gmail.com>
@jessealama
jessealama merged commit cd03999 into main Sep 28, 2026
3 checks passed
@jessealama
jessealama deleted the convert-options branch September 28, 2026 01:16
gibson042 added a commit to gibson042/proposal-amount that referenced this pull request Sep 28, 2026
gibson042 added a commit to gibson042/proposal-amount that referenced this pull request Sep 28, 2026
gibson042 added a commit to gibson042/proposal-amount that referenced this pull request Sep 28, 2026
eemeli pushed a commit that referenced this pull request Sep 29, 2026
* Meta: Mention "001" as the ultimate region fallback
* Editorial: Simplify internal references
* Editorial: Use conventional prose for describing operations superseded in ECMA-402
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add spec text for handling for locale and usage conversion options Default locale in unit conversion

3 participants