-
Notifications
You must be signed in to change notification settings - Fork 106
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[4th Edition] NumberFormat.prototype.formatToParts() #79
Conversation
/cc @littledan, @stasm will get this ready for you to review it. |
I'd like to do another pass tomorrow and I'll let you know when this is ready. I'll work on the polyfill implementation next week. A huge thank-you to @caridy for his help with this! |
e358e35
to
a3beb10
Compare
@@ -409,7 +473,7 @@ | |||
|
|||
<ul> | |||
<li>The array that is the value of the "nu" property of any locale property of [[localeData]] must not include the values "native", "traditio", or "finance".</li> | |||
<li>[[localeData]][locale] must have a patterns property for all locale values. The value of this property must be an object, which must have properties with the names of the three number format styles: *"decimal"*, *"percent"*, and *"currency"*. Each of these properties in turn must be an object with the properties positivePattern and negativePattern. The value of these properties must be string values that contain a substring *"{number}"*; the values within the currency property must also contain a substring *"{currency}"*. The pattern strings must not contain any characters in the General Category “Number, decimal digit" as specified by the Unicode Standard.</li> | |||
<li>[[localeData]][locale] must have a patterns property for all locale values. The value of this property must be an object, which must have properties with the names of the three number format styles: *"decimal"*, *"percent"*, and *"currency"*. Each of these properties in turn must be an object with the properties positivePattern and negativePattern. The value of these properties must be string values that must contain the substring *"{number}"* and may contain the substrings *"{minusSign}"*, and *"{plusSign}"*; the values within the percent property must also contain the substring *"{percentSign}"*; the values within the currency property must also contain the substring *"{currency}"*. The pattern strings must not contain any characters in the General Category “Number, decimal digit" as specified by the Unicode Standard.</li> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@caridy Should we use earmuffs here for property names? E.g. a *"patterns"* property
.
I filed http://unicode.org/cldr/trac/ticket/9284 to unify the names of separator parts in CLDR. |
…and CLDR people said changing the names would introduce too much incompatibility. I suggest we stick to the names currently present in CLDR. I changed my PR as follows:
N.B. The name |
1. Let _length_ be the number of code units in _pattern_. | ||
1. Repeat while _beginIndex_ is an integer index into _pattern_: | ||
1. Set _endIndex_ to Call(*%StringProto_indexOf%*, _pattern_, *"}"*, _beginIndex_) | ||
1. If _endIndex_ is *false*, throw new Error exception. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@caridy Should this read false
or perhaps -1
since we're calling indexOf
?
The polyfill is now ready: andyearnshaw/Intl.js#149 |
e3f5a58
to
8c3ee7e
Compare
I rebased this PR on top of the current master. I fixed conflicts caused by #80. |
1. Assert: Type(_nf_) is Object and _nf_ has an [[initializedNumberFormat]] internal slot whose value is *true*. | ||
1. If _value_ is not provided, let _value_ be *undefined*. | ||
1. Let _x_ be ? ToNumber(_value_). | ||
1. Return ? FormatNumberToParts(_nf_, _x_). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think you can just directly return the result of FormatNumberToParts to ECMAScript. FormatNumberToParts returns a List of Records. You have to convert the List of Records into an Array of Objects.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@littledan - this is fixed now
|
||
<emu-alg> | ||
1. Let _nf_ be *this* value. | ||
1. Assert: Type(_nf_) is Object and _nf_ has an [[initializedNumberFormat]] internal slot whose value is *true*. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@caridy said we should not have Asserts here. Instead, we should throw - see https://tc39.github.io/ecma402/#sec-intl.datetimeformat.prototype.format for example
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
use assertion to signal that ecmascript is broken, use throw to signal users are doing something wrong.
@stasm I have identified few issues when trying to release the polyfill, and I have commented here. Also, I have updated the polyfill to align better with spec text, you can see the details here: andyearnshaw/Intl.js@ccd5a41 |
Thanks, @caridy, I'll make the fixes early next week. |
@caridy Thank you for the feedback! I addressed your comments. |
LGTM |
@stasm we need some rebase here. then we should wait for consensus at TC39 before merging this. |
@caridy Would like me to also squash all the commits into a single one? |
@stasm yes. |
> const usd = Intl.NumberFormat('en', { style: 'currency', currency: 'USD' }); > usd.format(123456.789) '$123,456.79' > usd.formatToParts(123456.789) [ { type: 'currency', value: '$' }, { type: 'integer', value: '123' }, { type: 'group', value: ',' }, { type: 'integer', value: '456' }, { type: 'decimal', value: '.' }, { type: 'fraction', value: '79' } ] > const pc = Intl.NumberFormat('en', { style: 'percent', minimumFractionDigits: 2 }) > pc.format(-0.123456) '-12.35%' > pc.formatToParts(-0.123456) [ { type: 'minusSign', value: '-' }, { type: 'integer', value: '12' }, { type: 'decimal', value: '.' }, { type: 'fraction', value: '35' }, { type: 'literal', value: '%' } ]
be83e3f
to
a131147
Compare
Rebased and squashed. Thanks for all the help to get this ready. |
1. Return _nf_.[[boundFormat]]. | ||
</emu-alg> | ||
</emu-clause> | ||
|
||
<emu-clause id="sec-intl.numberformat.prototype.formattoparts"> | ||
<h1>Intl.NumberFormat.prototype.formatToParts ([ value ])</h1> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Making the value
argument to Intl.NumberFormat.prototype.formatToParts
optional is consistent with the DateTimeFormat
version of the function. But it doesn't seem to me that it should be.
There's a somewhat-understandable default value that can be applied for dates, namely the current date/time. But there's not really a good default value for numbers. You could argue for it being zero, but wouldn't it be more sensible for the user to pass 0
in that case? Or you could do what the current patch seems to do and make the default NaN
(probably not by deliberate choice? it's just a consequence of ToNumber(undefined)
evaluating to it). But it hardly seems like people will generally want to format NaN
into parts. As often as not, NaN
appearing somewhere means some earlier step in execution went awry, and now things are in la-la land.
I think you should make the value
argument here non-optional by removing the brackets and eliminating the step where "value
is not provided". Just let lack-of-argument be filled in with undefined
by normal spec semantics. This won't stop users from failing to provide the argument, and if they don't pass the argument, they'll still get dumb NaN
-handling. But it'll make Intl.NumberFormat().formatToParts.length
have a value consistent with how people are expected to use it, and will overwhelmingly use it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hey @jswalden, IIRC, the decision was based on the idea to align with format
: Intl.NumberFormat.prototype.format([value])
, which is specified here: https://tc39.github.io/ecma402/#sec-number-format-functions, and specifically, the idea of the progression from format to formatToParts has a way to evolve your ui. If moving from format to formatToParts requires extra logic, then it seems to me like a refactor hazard.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@caridy This wouldn't change any logic -- it would simply change the .length
of the function, which wouldn't be consulted by anything using .format
directly (because if you know the identity of your callee that way, you wouldn't have any reason to check its .length
). If you wanted to be more clear that there's no semantic change in the function's behavior, and that the only change is to the value of its .length
, you could have the exact same effect by adding, "The value of the length
property of the formatToParts
method is 1." just as Intl.Collator.supportedLocalesOf
has such language.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think I agree with @jswalden here. The current default behavior doesn't make much sense (NaN), so I'm ok with requiring an argument.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@zbraniecki I don't think that's what @jswalden is proposing. He is just proposing to set the length to 1, but keeping value as optional.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be nice for value
to be truly non-optional, but the ship's sailed on having JS APIs that enforce arity. So I think only that the length should be 1, not 0. Intl.NumberFormat().formatToParts()
would act as if NaN
were passed, even though it's dumb and doesn't make much sense, because it's how JS APIs generally work, that failure to provide an argument is identical to passing undefined
for that argument.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm supportive of length to be 1, and we can probably have a discussion about whether or not we want value
to be optional, it is not too late for that!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess I wanted @jswalden's opinion to be the one I presented ;) #argumentumadverecundiam
merged as 4a711f0 |
`Intl.NumberFormat.formatToParts` was first propsed in tc39#30. The spec for it was created in tc39#79 and merged in tc39#100. Due to browser implemantations not being ready at the time, it was moved back to Stage 3 in tc39#101. The internal refactorings were kept in master and the user-facing method `formatToParts` was removed from the spec in tc39#102. As of August 1st, 2017, `Intl.NumberFormat.prototype.formatToParts` has shipped in two engines (behind a flag): SpiderMonkey and V8. This PR brings `Intl.NumberFormat.formatToParts` back as Stage 4 proposal. > const usd = Intl.NumberFormat('en', { style: 'currency', currency: 'USD' }); > usd.format(123456.789) '$123,456.79' > usd.formatToParts(123456.789) [ { type: 'currency', value: '$' }, { type: 'integer', value: '123' }, { type: 'group', value: ',' }, { type: 'integer', value: '456' }, { type: 'decimal', value: '.' }, { type: 'fraction', value: '79' } ] > const pc = Intl.NumberFormat('en', { style: 'percent', minimumFractionDigits: 2 }) > pc.format(-0.123456) '-12.35%' > pc.formatToParts(-0.123456) [ { type: 'minusSign', value: '-' }, { type: 'integer', value: '12' }, { type: 'decimal', value: '.' }, { type: 'fraction', value: '35' }, { type: 'literal', value: '%' } ]
`Intl.NumberFormat.formatToParts` was first propsed in tc39#30. The spec for it was created in tc39#79 and merged in tc39#100 (with follow-ups). Due to browser implementations not being ready at the time, it was moved back to Stage 3 in tc39#101. The internal refactoring were kept in master and the user-facing method `formatToParts` was removed from the spec in tc39#102. As of August 1st, 2017, `Intl.NumberFormat.prototype.formatToParts` has shipped in two engines (behind a flag): SpiderMonkey and V8. This PR brings `Intl.NumberFormat.formatToParts` back as Stage 4 proposal. > const usd = Intl.NumberFormat('en', { style: 'currency', currency: 'USD' }); > usd.format(123456.789) '$123,456.79' > usd.formatToParts(123456.789) [ { type: 'currency', value: '$' }, { type: 'integer', value: '123' }, { type: 'group', value: ',' }, { type: 'integer', value: '456' }, { type: 'decimal', value: '.' }, { type: 'fraction', value: '79' } ] > const pc = Intl.NumberFormat('en', { style: 'percent', minimumFractionDigits: 2 }) > pc.format(-0.123456) '-12.35%' > pc.formatToParts(-0.123456) [ { type: 'minusSign', value: '-' }, { type: 'integer', value: '12' }, { type: 'decimal', value: '.' }, { type: 'fraction', value: '35' }, { type: 'literal', value: '%' } ]
Related Issue: #30