From c2abdfca128224c707c1c2a13fe5f3bd8a8204b6 Mon Sep 17 00:00:00 2001 From: Ben Allen Date: Sun, 15 Oct 2023 15:22:09 -0700 Subject: [PATCH 1/5] Normative: Add support for cashDigits attribute from CLDR, which allows rounding currency values to the number of fractional digits corresponding to the smallest currency denomination in circulation --- spec/normative-references.html | 3 +++ spec/numberformat.html | 18 +++++++++++++++--- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/spec/normative-references.html b/spec/normative-references.html index eb01133d..0942a809 100644 --- a/spec/normative-references.html +++ b/spec/normative-references.html @@ -31,6 +31,9 @@

Normative References

  • IANA Time Zone Database
  • +
  • + The Unicode Common Locale Data Repository +
  • The Unicode Standard
  • diff --git a/spec/numberformat.html b/spec/numberformat.html index 5e3845c4..c3501db1 100644 --- a/spec/numberformat.html +++ b/spec/numberformat.html @@ -17,7 +17,7 @@

    Intl.NumberFormat ( [ _locales_ [ , _options_ ] ] )

    1. If NewTarget is *undefined*, let _newTarget_ be the active function object, else let _newTarget_ be NewTarget. - 1. Let _numberFormat_ be ? OrdinaryCreateFromConstructor(_newTarget_, *"%NumberFormat.prototype%"*, « [[InitializedNumberFormat]], [[Locale]], [[DataLocale]], [[NumberingSystem]], [[Style]], [[Unit]], [[UnitDisplay]], [[Currency]], [[CurrencyDisplay]], [[CurrencySign]], [[MinimumIntegerDigits]], [[MinimumFractionDigits]], [[MaximumFractionDigits]], [[MinimumSignificantDigits]], [[MaximumSignificantDigits]], [[RoundingType]], [[Notation]], [[CompactDisplay]], [[UseGrouping]], [[SignDisplay]], [[RoundingIncrement]], [[RoundingMode]], [[ComputedRoundingPriority]], [[TrailingZeroDisplay]], [[BoundFormat]] »). + 1. Let _numberFormat_ be ? OrdinaryCreateFromConstructor(_newTarget_, *"%NumberFormat.prototype%"*, « [[InitializedNumberFormat]], [[Locale]], [[DataLocale]], [[NumberingSystem]], [[Style]], [[Unit]], [[UnitDisplay]], [[Currency]], [[CurrencyDisplay]], [[CurrencyPrecision]], [[CurrencySign]], [[MinimumIntegerDigits]], [[MinimumFractionDigits]], [[MaximumFractionDigits]], [[MinimumSignificantDigits]], [[MaximumSignificantDigits]], [[RoundingType]], [[Notation]], [[CompactDisplay]], [[UseGrouping]], [[SignDisplay]], [[RoundingIncrement]], [[RoundingMode]], [[ComputedRoundingPriority]], [[TrailingZeroDisplay]], [[BoundFormat]] »). 1. Perform ? InitializeNumberFormat(_numberFormat_, _locales_, _options_). 1. If the implementation supports the normative optional constructor mode of , then 1. Let _this_ be the *this* value. @@ -77,7 +77,8 @@

    1. Let _style_ be _numberFormat_.[[Style]]. 1. If _style_ is *"currency"*, then 1. Let _currency_ be _numberFormat_.[[Currency]]. - 1. Let _cDigits_ be CurrencyDigits(_currency_). + 1. Let _currencyPrecision_ be _numberFormat_.[[CurrencyPrecision]]. + 1. Let _cDigits_ be CurrencyDigits(_currency_, _currencyPrecision_). 1. Let _mnfdDefault_ be _cDigits_. 1. Let _mxfdDefault_ be _cDigits_. 1. Else, @@ -215,6 +216,7 @@

    1. Else, 1. If IsWellFormedCurrencyCode(_currency_) is *false*, throw a *RangeError* exception. 1. Let _currencyDisplay_ be ? GetOption(_options_, *"currencyDisplay"*, ~string~, « *"code"*, *"symbol"*, *"narrowSymbol"*, *"name"* », *"symbol"*). + 1. Let _currencyPrecision_ be ? GetOption(_options_, *"currencyPrecision"*, ~string~, « *"cash"*, *"financial"* », *"financial"*). 1. Let _currencySign_ be ? GetOption(_options_, *"currencySign"*, ~string~, « *"standard"*, *"accounting"* », *"standard"*). 1. Let _unit_ be ? GetOption(_options_, *"unit"*, ~string~, ~empty~, *undefined*). 1. If _unit_ is *undefined*, then @@ -225,6 +227,7 @@

    1. If _style_ is *"currency"*, then 1. Set _intlObj_.[[Currency]] to the ASCII-uppercase of _currency_. 1. Set _intlObj_.[[CurrencyDisplay]] to _currencyDisplay_. + 1. Set _intlObj_.[[CurrencyPrecision]] to _currencyPrecision_. 1. Set _intlObj_.[[CurrencySign]] to _currencySign_. 1. If _style_ is *"unit"*, then 1. Set _intlObj_.[[Unit]] to _unit_. @@ -470,6 +473,11 @@

    Intl.NumberFormat.prototype.resolvedOptions ( )

    *"currencyDisplay"* + + [[CurrencyPrecision]] + *"currencyPrecision"* + + [[CurrencySign]] *"currencySign"* @@ -577,6 +585,7 @@

    Properties of Intl.NumberFormat Instances

  • [[Style]] is one of the String values *"decimal"*, *"currency"*, *"percent"*, or *"unit"*, identifying the type of quantity being measured.
  • [[Currency]] is a String value with the currency code identifying the currency to be used if formatting with the *"currency"* unit type. It is only used when [[Style]] has the value *"currency"*.
  • [[CurrencyDisplay]] is one of the String values *"code"*, *"symbol"*, *"narrowSymbol"*, or *"name"*, specifying whether to display the currency as an ISO 4217 alphabetic currency code, a localized currency symbol, or a localized currency name if formatting with the *"currency"* style. It is only used when [[Style]] has the value *"currency"*.
  • +
  • [[CurrencyPrecision]] is one of the String values *"cash"* or *"financial"*, specifying whether to round currency values to the smallest denomination of the currency in common usage, or instead to round to the number of fractional digits used for the currency in accounting and by financial institutions. It is only used when [[Style]] has the value *"currency"*.
  • [[CurrencySign]] is one of the String values *"standard"* or *"accounting"*, specifying whether to render negative numbers in accounting format, often signified by parenthesis. It is only used when [[Style]] has the value *"currency"* and when [[SignDisplay]] is not *"never"*.
  • [[Unit]] is a core unit identifier. It is only used when [[Style]] has the value *"unit"*.
  • [[UnitDisplay]] is one of the String values *"short"*, *"narrow"*, or *"long"*, specifying whether to display the unit as a symbol, narrow symbol, or localized long name if formatting with the *"unit"* style. It is only used when [[Style]] has the value *"unit"*.
  • @@ -713,6 +722,7 @@

    Abstract Operations for NumberFormat Objects

    CurrencyDigits ( _currency_: a String, + _currencyPrecision_: a String, ): a non-negative integer

    @@ -720,7 +730,9 @@

    1. Assert: IsWellFormedCurrencyCode(_currency_) is *true*. 1. Assert: _currency_ is equal to the ASCII-uppercase of _currency_. - 1. If the ISO 4217 currency and funds code list contains _currency_ as an alphabetic code, return the minor unit value corresponding to the _currency_ from the list; otherwise, return 2. + 1. If the Common Locale Data Repository supplemental data pertaining to fractional currency values contains an element with _currency_ as the value of its iso4217 attribute, then + 1. If _currencyPrecision_ is *"cash"* and the element with _currency_ as the value of its iso4217 attribute has a cashDigits attribute, return the value of that attribute; otherwise, return the value of that element's digits attribute. + 1. Return 2. From 1428546b6259cb312c7e0e44580d13e92bc7ca69 Mon Sep 17 00:00:00 2001 From: Ben Allen Date: Tue, 17 Oct 2023 16:25:45 -0700 Subject: [PATCH 2/5] Normative: Add support for cashRounding attribute from CLDR, allowing rounding to smallest cash denominations for CAD, CHF, DKK --- spec/numberformat.html | 25 +++++++++++++++++++++++-- spec/pluralrules.html | 2 +- 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/spec/numberformat.html b/spec/numberformat.html index c3501db1..24ab4bbf 100644 --- a/spec/numberformat.html +++ b/spec/numberformat.html @@ -79,9 +79,11 @@

    1. Let _currency_ be _numberFormat_.[[Currency]]. 1. Let _currencyPrecision_ be _numberFormat_.[[CurrencyPrecision]]. 1. Let _cDigits_ be CurrencyDigits(_currency_, _currencyPrecision_). + 1. Let _defaultRoundingIncrement_ be CurrencyRounding(_currency_, _currencyPrecision_). 1. Let _mnfdDefault_ be _cDigits_. 1. Let _mxfdDefault_ be _cDigits_. 1. Else, + 1. Let _defaultRoundingIncrement_ be 1. 1. Let _mnfdDefault_ be 0. 1. If _style_ is *"percent"*, then 1. Let _mxfdDefault_ be 0. @@ -89,7 +91,7 @@

    1. Let _mxfdDefault_ be 3. 1. Let _notation_ be ? GetOption(_options_, *"notation"*, ~string~, « *"standard"*, *"scientific"*, *"engineering"*, *"compact"* », *"standard"*). 1. Set _numberFormat_.[[Notation]] to _notation_. - 1. Perform ? SetNumberFormatDigitOptions(_numberFormat_, _options_, _mnfdDefault_, _mxfdDefault_, _notation_). + 1. Perform ? SetNumberFormatDigitOptions(_numberFormat_, _options_, _mnfdDefault_, _mxfdDefault_, _notation_, _defaultRoundingIncrement_). 1. Let _compactDisplay_ be ? GetOption(_options_, *"compactDisplay"*, ~string~, « *"short"*, *"long"* », *"short"*). 1. Let _defaultUseGrouping_ be *"auto"*. 1. If _notation_ is *"compact"*, then @@ -114,6 +116,7 @@

    _mnfdDefault_: an integer, _mxfdDefault_: an integer, _notation_: a String, + _defaultRoundingIncrement_: an integer, ): either a normal completion containing ~unused~ or a throw completion

    @@ -127,7 +130,7 @@

    1. Let _mnsd_ be ? Get(_options_, *"minimumSignificantDigits"*). 1. Let _mxsd_ be ? Get(_options_, *"maximumSignificantDigits"*). 1. Set _intlObj_.[[MinimumIntegerDigits]] to _mnid_. - 1. Let _roundingIncrement_ be ? GetNumberOption(_options_, *"roundingIncrement"*, 1, 5000, 1). + 1. Let _roundingIncrement_ be ? GetNumberOption(_options_, *"roundingIncrement"*, 1, 5000, _defaultRoundingIncrement_). 1. If _roundingIncrement_ is not in « 1, 2, 5, 10, 20, 25, 50, 100, 200, 250, 500, 1000, 2000, 2500, 5000 », throw a *RangeError* exception. 1. Let _roundingMode_ be ? GetOption(_options_, *"roundingMode"*, ~string~, « *"ceil"*, *"floor"*, *"expand"*, *"trunc"*, *"halfCeil"*, *"halfFloor"*, *"halfExpand"*, *"halfTrunc"*, *"halfEven"* », *"halfExpand"*). 1. Let _roundingPriority_ be ? GetOption(_options_, *"roundingPriority"*, ~string~, « *"auto"*, *"morePrecision"*, *"lessPrecision"* », *"auto"*). @@ -736,6 +739,24 @@

    + +

    + CurrencyRounding ( + _currency_: a String, + _currencyPrecision_: a String, + ): a non-negative integer +

    +
    +
    + + 1. Assert: IsWellFormedCurrencyCode(_currency_) is *true*. + 1. Assert: _currency_ is equal to the ASCII-uppercase of _currency_. + 1. If the Common Locale Repository supplemental data pertaining to fractional currency values contains an element with _currency_ as the value of its iso4217 attribute, then + 1. If _currencyPrecision_ is *"cash"* and the element with _currency_ as the value of its iso4217 attribute has a cashRounding attribute, return the value of that attribute. + 1. Return 1. + +
    +

    Number Format Functions

    diff --git a/spec/pluralrules.html b/spec/pluralrules.html index 1956bc37..4ed1d3d8 100644 --- a/spec/pluralrules.html +++ b/spec/pluralrules.html @@ -42,7 +42,7 @@

    1. Set _opt_.[[localeMatcher]] to _matcher_. 1. Let _t_ be ? GetOption(_options_, *"type"*, ~string~, « *"cardinal"*, *"ordinal"* », *"cardinal"*). 1. Set _pluralRules_.[[Type]] to _t_. - 1. Perform ? SetNumberFormatDigitOptions(_pluralRules_, _options_, 0, 3, *"standard"*). + 1. Perform ? SetNumberFormatDigitOptions(_pluralRules_, _options_, 0, 3, *"standard"*, 1). 1. Let _localeData_ be %PluralRules%.[[LocaleData]]. 1. Let _r_ be ResolveLocale(%PluralRules%.[[AvailableLocales]], _requestedLocales_, _opt_, %PluralRules%.[[RelevantExtensionKeys]], _localeData_). 1. Set _pluralRules_.[[Locale]] to _r_.[[locale]]. From ba8b71984240a38dcf0e85fb2867cbdfb07398bd Mon Sep 17 00:00:00 2001 From: Ben Allen Date: Mon, 23 Oct 2023 04:31:36 -0700 Subject: [PATCH 3/5] fixup! Normative: Add support for cashRounding attribute from CLDR, allowing rounding to smallest cash denominations for CAD, CHF, DKK --- spec/numberformat.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/numberformat.html b/spec/numberformat.html index 24ab4bbf..2386c0cf 100644 --- a/spec/numberformat.html +++ b/spec/numberformat.html @@ -79,7 +79,7 @@

    1. Let _currency_ be _numberFormat_.[[Currency]]. 1. Let _currencyPrecision_ be _numberFormat_.[[CurrencyPrecision]]. 1. Let _cDigits_ be CurrencyDigits(_currency_, _currencyPrecision_). - 1. Let _defaultRoundingIncrement_ be CurrencyRounding(_currency_, _currencyPrecision_). + 1. Let _defaultRoundingIncrement_ be CurrencyRoundingIncrement(_currency_, _currencyPrecision_). 1. Let _mnfdDefault_ be _cDigits_. 1. Let _mxfdDefault_ be _cDigits_. 1. Else, @@ -741,7 +741,7 @@

    - CurrencyRounding ( + CurrencyRoundingIncrement ( _currency_: a String, _currencyPrecision_: a String, ): a non-negative integer From 7d125bbefa7f808f821f33c2552984bd223983a1 Mon Sep 17 00:00:00 2001 From: Ben Allen Date: Sat, 28 Oct 2023 10:33:09 -0700 Subject: [PATCH 4/5] fixup! fixup! Normative: Add support for cashRounding attribute from CLDR, allowing rounding to smallest cash denominations for CAD, CHF, DKK --- spec/numberformat.html | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/spec/numberformat.html b/spec/numberformat.html index 2386c0cf..16506d01 100644 --- a/spec/numberformat.html +++ b/spec/numberformat.html @@ -130,7 +130,7 @@

    1. Let _mnsd_ be ? Get(_options_, *"minimumSignificantDigits"*). 1. Let _mxsd_ be ? Get(_options_, *"maximumSignificantDigits"*). 1. Set _intlObj_.[[MinimumIntegerDigits]] to _mnid_. - 1. Let _roundingIncrement_ be ? GetNumberOption(_options_, *"roundingIncrement"*, 1, 5000, _defaultRoundingIncrement_). + 1. Let _roundingIncrement_ be ? GetNumberOption(_options_, *"roundingIncrement"*, 1, 5000, 1). 1. If _roundingIncrement_ is not in « 1, 2, 5, 10, 20, 25, 50, 100, 200, 250, 500, 1000, 2000, 2500, 5000 », throw a *RangeError* exception. 1. Let _roundingMode_ be ? GetOption(_options_, *"roundingMode"*, ~string~, « *"ceil"*, *"floor"*, *"expand"*, *"trunc"*, *"halfCeil"*, *"halfFloor"*, *"halfExpand"*, *"halfTrunc"*, *"halfEven"* », *"halfExpand"*). 1. Let _roundingPriority_ be ? GetOption(_options_, *"roundingPriority"*, ~string~, « *"auto"*, *"morePrecision"*, *"lessPrecision"* », *"auto"*). @@ -173,6 +173,7 @@

    1. Else, 1. Set _intlObj_.[[MinimumFractionDigits]] to _mnfdDefault_. 1. Set _intlObj_.[[MaximumFractionDigits]] to _mxfdDefault_. + 1. Set _intlObj_.[[RoundingIncrement]] to _defaultRoundingIncrement_. 1. If _needSd_ is *false* and _needFd_ is *false*, then 1. Set _intlObj_.[[MinimumFractionDigits]] to 0. 1. Set _intlObj_.[[MaximumFractionDigits]] to 0. From 90db107306c85e09c56a840879d2341dea53cb96 Mon Sep 17 00:00:00 2001 From: Ben Allen Date: Sat, 28 Oct 2023 11:03:03 -0700 Subject: [PATCH 5/5] Make new CLDR requirement a recommendation instead --- spec/normative-references.html | 3 --- spec/numberformat.html | 15 +++++++++++---- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/spec/normative-references.html b/spec/normative-references.html index 0942a809..eb01133d 100644 --- a/spec/normative-references.html +++ b/spec/normative-references.html @@ -31,9 +31,6 @@

    Normative References

  • IANA Time Zone Database
  • -
  • - The Unicode Common Locale Data Repository -
  • The Unicode Standard
  • diff --git a/spec/numberformat.html b/spec/numberformat.html index 16506d01..49e7cc4d 100644 --- a/spec/numberformat.html +++ b/spec/numberformat.html @@ -734,10 +734,14 @@

    1. Assert: IsWellFormedCurrencyCode(_currency_) is *true*. 1. Assert: _currency_ is equal to the ASCII-uppercase of _currency_. - 1. If the Common Locale Data Repository supplemental data pertaining to fractional currency values contains an element with _currency_ as the value of its iso4217 attribute, then - 1. If _currencyPrecision_ is *"cash"* and the element with _currency_ as the value of its iso4217 attribute has a cashDigits attribute, return the value of that attribute; otherwise, return the value of that element's digits attribute. + 1. If _currencyPrecision_ is *"cash"*, then + 1. If there is available locale data that specifies the number of fractional digits to be used when dealing with cash values, return that number. + 1. If there is available locale data that specifies the number of fractional digits to be used when dealing with non-cash values, return that number. 1. Return 2. + + It is recommended that implementations use the Common Locale Data Repository supplemental data on currencies. In this case, if _currencyPrecison_ is *"cash"* and the element with _currency_ as the value of its iso4217 attribute has a cashDigits attribute, return the value of that attribute, otherwise, return the value of that element's digits attribute. + @@ -752,10 +756,13 @@

    1. Assert: IsWellFormedCurrencyCode(_currency_) is *true*. 1. Assert: _currency_ is equal to the ASCII-uppercase of _currency_. - 1. If the Common Locale Repository supplemental data pertaining to fractional currency values contains an element with _currency_ as the value of its iso4217 attribute, then - 1. If _currencyPrecision_ is *"cash"* and the element with _currency_ as the value of its iso4217 attribute has a cashRounding attribute, return the value of that attribute. + 1. If _currencyPrecision_ is *"cash"*, then + 1. If there is available locale data that specifies the number to which cash values should be rounded, return that number. 1. Return 1. + + It is recommended that implementations use the Common Locale Data Repository supplemental data on currencies. In this case, if _currencyRounding_ is *"cash"* and the element with _currency_ as the value of its iso4217 attribute has a cashRounding attribute, return the value of that attribute, +