1 | // 11.1 The Intl.NumberFormat constructor
|
2 | // ======================================
|
3 |
|
4 | import {
|
5 | IsWellFormedCurrencyCode,
|
6 | } from "./6.locales-currencies-tz.js";
|
7 |
|
8 | import {
|
9 | Intl,
|
10 | } from "./8.intl.js";
|
11 |
|
12 | import {
|
13 | CanonicalizeLocaleList,
|
14 | SupportedLocales,
|
15 | ResolveLocale,
|
16 | GetNumberOption,
|
17 | GetOption,
|
18 | } from "./9.negotiation.js";
|
19 |
|
20 | import {
|
21 | internals,
|
22 | log10Floor,
|
23 | List,
|
24 | toObject,
|
25 | arrPush,
|
26 | arrJoin,
|
27 | arrShift,
|
28 | Record,
|
29 | hop,
|
30 | defineProperty,
|
31 | es3,
|
32 | fnBind,
|
33 | getInternalProperties,
|
34 | createRegExpRestore,
|
35 | secret,
|
36 | objCreate,
|
37 | } from "./util.js";
|
38 |
|
39 | // Currency minor units output from get-4217 grunt task, formatted
|
40 | const currencyMinorUnits = {
|
41 | BHD: 3, BYR: 0, XOF: 0, BIF: 0, XAF: 0, CLF: 4, CLP: 0, KMF: 0, DJF: 0,
|
42 | XPF: 0, GNF: 0, ISK: 0, IQD: 3, JPY: 0, JOD: 3, KRW: 0, KWD: 3, LYD: 3,
|
43 | OMR: 3, PYG: 0, RWF: 0, TND: 3, UGX: 0, UYI: 0, VUV: 0, VND: 0,
|
44 | };
|
45 |
|
46 | // Define the NumberFormat constructor internally so it cannot be tainted
|
47 | export function NumberFormatConstructor () {
|
48 | let locales = arguments[0];
|
49 | let options = arguments[1];
|
50 |
|
51 | if (!this || this === Intl) {
|
52 | return new Intl.NumberFormat(locales, options);
|
53 | }
|
54 |
|
55 | return InitializeNumberFormat(toObject(this), locales, options);
|
56 | }
|
57 |
|
58 | defineProperty(Intl, 'NumberFormat', {
|
59 | configurable: true,
|
60 | writable: true,
|
61 | value: NumberFormatConstructor,
|
62 | });
|
63 |
|
64 | // Must explicitly set prototypes as unwritable
|
65 | defineProperty(Intl.NumberFormat, 'prototype', {
|
66 | writable: false,
|
67 | });
|
68 |
|
69 | /**
|
70 | * The abstract operation InitializeNumberFormat accepts the arguments
|
71 | * numberFormat (which must be an object), locales, and options. It initializes
|
72 | * numberFormat as a NumberFormat object.
|
73 | */
|
74 | export function /*11.1.1.1 */InitializeNumberFormat (numberFormat, locales, options) {
|
75 | // This will be a internal properties object if we're not already initialized
|
76 | let internal = getInternalProperties(numberFormat);
|
77 |
|
78 | // Create an object whose props can be used to restore the values of RegExp props
|
79 | let regexpState = createRegExpRestore();
|
80 |
|
81 | // 1. If numberFormat has an [[initializedIntlObject]] internal property with
|
82 | // value true, throw a TypeError exception.
|
83 | if (internal['[[initializedIntlObject]]'] === true)
|
84 | throw new TypeError('`this` object has already been initialized as an Intl object');
|
85 |
|
86 | // Need this to access the `internal` object
|
87 | defineProperty(numberFormat, '__getInternalProperties', {
|
88 | value: function () {
|
89 | // NOTE: Non-standard, for internal use only
|
90 | if (arguments[0] === secret)
|
91 | return internal;
|
92 | },
|
93 | });
|
94 |
|
95 | // 2. Set the [[initializedIntlObject]] internal property of numberFormat to true.
|
96 | internal['[[initializedIntlObject]]'] = true;
|
97 |
|
98 | // 3. Let requestedLocales be the result of calling the CanonicalizeLocaleList
|
99 | // abstract operation (defined in 9.2.1) with argument locales.
|
100 | let requestedLocales = CanonicalizeLocaleList(locales);
|
101 |
|
102 | // 4. If options is undefined, then
|
103 | if (options === undefined)
|
104 | // a. Let options be the result of creating a new object as if by the
|
105 | // expression new Object() where Object is the standard built-in constructor
|
106 | // with that name.
|
107 | options = {};
|
108 |
|
109 | // 5. Else
|
110 | else
|
111 | // a. Let options be ToObject(options).
|
112 | options = toObject(options);
|
113 |
|
114 | // 6. Let opt be a new Record.
|
115 | let opt = new Record(),
|
116 |
|
117 | // 7. Let matcher be the result of calling the GetOption abstract operation
|
118 | // (defined in 9.2.9) with the arguments options, "localeMatcher", "string",
|
119 | // a List containing the two String values "lookup" and "best fit", and
|
120 | // "best fit".
|
121 | matcher = GetOption(options, 'localeMatcher', 'string', new List('lookup', 'best fit'), 'best fit');
|
122 |
|
123 | // 8. Set opt.[[localeMatcher]] to matcher.
|
124 | opt['[[localeMatcher]]'] = matcher;
|
125 |
|
126 | // 9. Let NumberFormat be the standard built-in object that is the initial value
|
127 | // of Intl.NumberFormat.
|
128 | // 10. Let localeData be the value of the [[localeData]] internal property of
|
129 | // NumberFormat.
|
130 | let localeData = internals.NumberFormat['[[localeData]]'];
|
131 |
|
132 | // 11. Let r be the result of calling the ResolveLocale abstract operation
|
133 | // (defined in 9.2.5) with the [[availableLocales]] internal property of
|
134 | // NumberFormat, requestedLocales, opt, the [[relevantExtensionKeys]]
|
135 | // internal property of NumberFormat, and localeData.
|
136 | let r = ResolveLocale(
|
137 | internals.NumberFormat['[[availableLocales]]'], requestedLocales,
|
138 | opt, internals.NumberFormat['[[relevantExtensionKeys]]'], localeData
|
139 | );
|
140 |
|
141 | // 12. Set the [[locale]] internal property of numberFormat to the value of
|
142 | // r.[[locale]].
|
143 | internal['[[locale]]'] = r['[[locale]]'];
|
144 |
|
145 | // 13. Set the [[numberingSystem]] internal property of numberFormat to the value
|
146 | // of r.[[nu]].
|
147 | internal['[[numberingSystem]]'] = r['[[nu]]'];
|
148 |
|
149 | // The specification doesn't tell us to do this, but it's helpful later on
|
150 | internal['[[dataLocale]]'] = r['[[dataLocale]]'];
|
151 |
|
152 | // 14. Let dataLocale be the value of r.[[dataLocale]].
|
153 | let dataLocale = r['[[dataLocale]]'];
|
154 |
|
155 | // 15. Let s be the result of calling the GetOption abstract operation with the
|
156 | // arguments options, "style", "string", a List containing the three String
|
157 | // values "decimal", "percent", and "currency", and "decimal".
|
158 | let s = GetOption(options, 'style', 'string', new List('decimal', 'percent', 'currency'), 'decimal');
|
159 |
|
160 | // 16. Set the [[style]] internal property of numberFormat to s.
|
161 | internal['[[style]]'] = s;
|
162 |
|
163 | // 17. Let c be the result of calling the GetOption abstract operation with the
|
164 | // arguments options, "currency", "string", undefined, and undefined.
|
165 | let c = GetOption(options, 'currency', 'string');
|
166 |
|
167 | // 18. If c is not undefined and the result of calling the
|
168 | // IsWellFormedCurrencyCode abstract operation (defined in 6.3.1) with
|
169 | // argument c is false, then throw a RangeError exception.
|
170 | if (c !== undefined && !IsWellFormedCurrencyCode(c))
|
171 | throw new RangeError("'" + c + "' is not a valid currency code");
|
172 |
|
173 | // 19. If s is "currency" and c is undefined, throw a TypeError exception.
|
174 | if (s === 'currency' && c === undefined)
|
175 | throw new TypeError('Currency code is required when style is currency');
|
176 |
|
177 | let cDigits;
|
178 |
|
179 | // 20. If s is "currency", then
|
180 | if (s === 'currency') {
|
181 | // a. Let c be the result of converting c to upper case as specified in 6.1.
|
182 | c = c.toUpperCase();
|
183 |
|
184 | // b. Set the [[currency]] internal property of numberFormat to c.
|
185 | internal['[[currency]]'] = c;
|
186 |
|
187 | // c. Let cDigits be the result of calling the CurrencyDigits abstract
|
188 | // operation (defined below) with argument c.
|
189 | cDigits = CurrencyDigits(c);
|
190 | }
|
191 |
|
192 | // 21. Let cd be the result of calling the GetOption abstract operation with the
|
193 | // arguments options, "currencyDisplay", "string", a List containing the
|
194 | // three String values "code", "symbol", and "name", and "symbol".
|
195 | let cd = GetOption(options, 'currencyDisplay', 'string', new List('code', 'symbol', 'name'), 'symbol');
|
196 |
|
197 | // 22. If s is "currency", then set the [[currencyDisplay]] internal property of
|
198 | // numberFormat to cd.
|
199 | if (s === 'currency')
|
200 | internal['[[currencyDisplay]]'] = cd;
|
201 |
|
202 | // 23. Let mnid be the result of calling the GetNumberOption abstract operation
|
203 | // (defined in 9.2.10) with arguments options, "minimumIntegerDigits", 1, 21,
|
204 | // and 1.
|
205 | let mnid = GetNumberOption(options, 'minimumIntegerDigits', 1, 21, 1);
|
206 |
|
207 | // 24. Set the [[minimumIntegerDigits]] internal property of numberFormat to mnid.
|
208 | internal['[[minimumIntegerDigits]]'] = mnid;
|
209 |
|
210 | // 25. If s is "currency", then let mnfdDefault be cDigits; else let mnfdDefault
|
211 | // be 0.
|
212 | let mnfdDefault = s === 'currency' ? cDigits : 0;
|
213 |
|
214 | // 26. Let mnfd be the result of calling the GetNumberOption abstract operation
|
215 | // with arguments options, "minimumFractionDigits", 0, 20, and mnfdDefault.
|
216 | let mnfd = GetNumberOption(options, 'minimumFractionDigits', 0, 20, mnfdDefault);
|
217 |
|
218 | // 27. Set the [[minimumFractionDigits]] internal property of numberFormat to mnfd.
|
219 | internal['[[minimumFractionDigits]]'] = mnfd;
|
220 |
|
221 | // 28. If s is "currency", then let mxfdDefault be max(mnfd, cDigits); else if s
|
222 | // is "percent", then let mxfdDefault be max(mnfd, 0); else let mxfdDefault
|
223 | // be max(mnfd, 3).
|
224 | let mxfdDefault = s === 'currency' ? Math.max(mnfd, cDigits)
|
225 | : (s === 'percent' ? Math.max(mnfd, 0) : Math.max(mnfd, 3));
|
226 |
|
227 | // 29. Let mxfd be the result of calling the GetNumberOption abstract operation
|
228 | // with arguments options, "maximumFractionDigits", mnfd, 20, and mxfdDefault.
|
229 | let mxfd = GetNumberOption(options, 'maximumFractionDigits', mnfd, 20, mxfdDefault);
|
230 |
|
231 | // 30. Set the [[maximumFractionDigits]] internal property of numberFormat to mxfd.
|
232 | internal['[[maximumFractionDigits]]'] = mxfd;
|
233 |
|
234 | // 31. Let mnsd be the result of calling the [[Get]] internal method of options
|
235 | // with argument "minimumSignificantDigits".
|
236 | let mnsd = options.minimumSignificantDigits;
|
237 |
|
238 | // 32. Let mxsd be the result of calling the [[Get]] internal method of options
|
239 | // with argument "maximumSignificantDigits".
|
240 | let mxsd = options.maximumSignificantDigits;
|
241 |
|
242 | // 33. If mnsd is not undefined or mxsd is not undefined, then:
|
243 | if (mnsd !== undefined || mxsd !== undefined) {
|
244 | // a. Let mnsd be the result of calling the GetNumberOption abstract
|
245 | // operation with arguments options, "minimumSignificantDigits", 1, 21,
|
246 | // and 1.
|
247 | mnsd = GetNumberOption(options, 'minimumSignificantDigits', 1, 21, 1);
|
248 |
|
249 | // b. Let mxsd be the result of calling the GetNumberOption abstract
|
250 | // operation with arguments options, "maximumSignificantDigits", mnsd,
|
251 | // 21, and 21.
|
252 | mxsd = GetNumberOption(options, 'maximumSignificantDigits', mnsd, 21, 21);
|
253 |
|
254 | // c. Set the [[minimumSignificantDigits]] internal property of numberFormat
|
255 | // to mnsd, and the [[maximumSignificantDigits]] internal property of
|
256 | // numberFormat to mxsd.
|
257 | internal['[[minimumSignificantDigits]]'] = mnsd;
|
258 | internal['[[maximumSignificantDigits]]'] = mxsd;
|
259 | }
|
260 | // 34. Let g be the result of calling the GetOption abstract operation with the
|
261 | // arguments options, "useGrouping", "boolean", undefined, and true.
|
262 | let g = GetOption(options, 'useGrouping', 'boolean', undefined, true);
|
263 |
|
264 | // 35. Set the [[useGrouping]] internal property of numberFormat to g.
|
265 | internal['[[useGrouping]]'] = g;
|
266 |
|
267 | // 36. Let dataLocaleData be the result of calling the [[Get]] internal method of
|
268 | // localeData with argument dataLocale.
|
269 | let dataLocaleData = localeData[dataLocale];
|
270 |
|
271 | // 37. Let patterns be the result of calling the [[Get]] internal method of
|
272 | // dataLocaleData with argument "patterns".
|
273 | let patterns = dataLocaleData.patterns;
|
274 |
|
275 | // 38. Assert: patterns is an object (see 11.2.3)
|
276 |
|
277 | // 39. Let stylePatterns be the result of calling the [[Get]] internal method of
|
278 | // patterns with argument s.
|
279 | let stylePatterns = patterns[s];
|
280 |
|
281 | // 40. Set the [[positivePattern]] internal property of numberFormat to the
|
282 | // result of calling the [[Get]] internal method of stylePatterns with the
|
283 | // argument "positivePattern".
|
284 | internal['[[positivePattern]]'] = stylePatterns.positivePattern;
|
285 |
|
286 | // 41. Set the [[negativePattern]] internal property of numberFormat to the
|
287 | // result of calling the [[Get]] internal method of stylePatterns with the
|
288 | // argument "negativePattern".
|
289 | internal['[[negativePattern]]'] = stylePatterns.negativePattern;
|
290 |
|
291 | // 42. Set the [[boundFormat]] internal property of numberFormat to undefined.
|
292 | internal['[[boundFormat]]'] = undefined;
|
293 |
|
294 | // 43. Set the [[initializedNumberFormat]] internal property of numberFormat to
|
295 | // true.
|
296 | internal['[[initializedNumberFormat]]'] = true;
|
297 |
|
298 | // In ES3, we need to pre-bind the format() function
|
299 | if (es3)
|
300 | numberFormat.format = GetFormatNumber.call(numberFormat);
|
301 |
|
302 | // Restore the RegExp properties
|
303 | regexpState.exp.test(regexpState.input);
|
304 |
|
305 | // Return the newly initialised object
|
306 | return numberFormat;
|
307 | }
|
308 |
|
309 | function CurrencyDigits(currency) {
|
310 | // When the CurrencyDigits abstract operation is called with an argument currency
|
311 | // (which must be an upper case String value), the following steps are taken:
|
312 |
|
313 | // 1. If the ISO 4217 currency and funds code list contains currency as an
|
314 | // alphabetic code, then return the minor unit value corresponding to the
|
315 | // currency from the list; else return 2.
|
316 | return currencyMinorUnits[currency] !== undefined
|
317 | ? currencyMinorUnits[currency]
|
318 | : 2;
|
319 | }
|
320 |
|
321 | /* 11.2.3 */internals.NumberFormat = {
|
322 | '[[availableLocales]]': [],
|
323 | '[[relevantExtensionKeys]]': ['nu'],
|
324 | '[[localeData]]': {},
|
325 | };
|
326 |
|
327 | /**
|
328 | * When the supportedLocalesOf method of Intl.NumberFormat is called, the
|
329 | * following steps are taken:
|
330 | */
|
331 | /* 11.2.2 */
|
332 | defineProperty(Intl.NumberFormat, 'supportedLocalesOf', {
|
333 | configurable: true,
|
334 | writable: true,
|
335 | value: fnBind.call(function (locales) {
|
336 | // Bound functions only have the `this` value altered if being used as a constructor,
|
337 | // this lets us imitate a native function that has no constructor
|
338 | if (!hop.call(this, '[[availableLocales]]'))
|
339 | throw new TypeError('supportedLocalesOf() is not a constructor');
|
340 |
|
341 | // Create an object whose props can be used to restore the values of RegExp props
|
342 | let regexpState = createRegExpRestore(),
|
343 |
|
344 | // 1. If options is not provided, then let options be undefined.
|
345 | options = arguments[1],
|
346 |
|
347 | // 2. Let availableLocales be the value of the [[availableLocales]] internal
|
348 | // property of the standard built-in object that is the initial value of
|
349 | // Intl.NumberFormat.
|
350 |
|
351 | availableLocales = this['[[availableLocales]]'],
|
352 |
|
353 | // 3. Let requestedLocales be the result of calling the CanonicalizeLocaleList
|
354 | // abstract operation (defined in 9.2.1) with argument locales.
|
355 | requestedLocales = CanonicalizeLocaleList(locales);
|
356 |
|
357 | // Restore the RegExp properties
|
358 | regexpState.exp.test(regexpState.input);
|
359 |
|
360 | // 4. Return the result of calling the SupportedLocales abstract operation
|
361 | // (defined in 9.2.8) with arguments availableLocales, requestedLocales,
|
362 | // and options.
|
363 | return SupportedLocales(availableLocales, requestedLocales, options);
|
364 | }, internals.NumberFormat),
|
365 | });
|
366 |
|
367 | /**
|
368 | * This named accessor property returns a function that formats a number
|
369 | * according to the effective locale and the formatting options of this
|
370 | * NumberFormat object.
|
371 | */
|
372 | /* 11.3.2 */defineProperty(Intl.NumberFormat.prototype, 'format', {
|
373 | configurable: true,
|
374 | get: GetFormatNumber,
|
375 | });
|
376 |
|
377 | function GetFormatNumber() {
|
378 | let internal = this !== null && typeof this === 'object' && getInternalProperties(this);
|
379 |
|
380 | // Satisfy test 11.3_b
|
381 | if (!internal || !internal['[[initializedNumberFormat]]'])
|
382 | throw new TypeError('`this` value for format() is not an initialized Intl.NumberFormat object.');
|
383 |
|
384 | // The value of the [[Get]] attribute is a function that takes the following
|
385 | // steps:
|
386 |
|
387 | // 1. If the [[boundFormat]] internal property of this NumberFormat object
|
388 | // is undefined, then:
|
389 | if (internal['[[boundFormat]]'] === undefined) {
|
390 | // a. Let F be a Function object, with internal properties set as
|
391 | // specified for built-in functions in ES5, 15, or successor, and the
|
392 | // length property set to 1, that takes the argument value and
|
393 | // performs the following steps:
|
394 | let F = function (value) {
|
395 | // i. If value is not provided, then let value be undefined.
|
396 | // ii. Let x be ToNumber(value).
|
397 | // iii. Return the result of calling the FormatNumber abstract
|
398 | // operation (defined below) with arguments this and x.
|
399 | return FormatNumber(this, /* x = */Number(value));
|
400 | };
|
401 |
|
402 | // b. Let bind be the standard built-in function object defined in ES5,
|
403 | // 15.3.4.5.
|
404 | // c. Let bf be the result of calling the [[Call]] internal method of
|
405 | // bind with F as the this value and an argument list containing
|
406 | // the single item this.
|
407 | let bf = fnBind.call(F, this);
|
408 |
|
409 | // d. Set the [[boundFormat]] internal property of this NumberFormat
|
410 | // object to bf.
|
411 | internal['[[boundFormat]]'] = bf;
|
412 | }
|
413 | // Return the value of the [[boundFormat]] internal property of this
|
414 | // NumberFormat object.
|
415 | return internal['[[boundFormat]]'];
|
416 | }
|
417 |
|
418 | Intl.NumberFormat.prototype.formatToParts = function(value) {
|
419 | let internal = this !== null && typeof this === 'object' && getInternalProperties(this);
|
420 | if (!internal || !internal['[[initializedNumberFormat]]'])
|
421 | throw new TypeError('`this` value for formatToParts() is not an initialized Intl.NumberFormat object.');
|
422 |
|
423 | let x = Number(value);
|
424 | return FormatNumberToParts(this, x);
|
425 | };
|
426 |
|
427 | /*
|
428 | * @spec[stasm/ecma402/number-format-to-parts/spec/numberformat.html]
|
429 | * @clause[sec-formatnumbertoparts]
|
430 | */
|
431 | function FormatNumberToParts(numberFormat, x) {
|
432 | // 1. Let parts be ? PartitionNumberPattern(numberFormat, x).
|
433 | let parts = PartitionNumberPattern(numberFormat, x);
|
434 | // 2. Let result be ArrayCreate(0).
|
435 | let result = [];
|
436 | // 3. Let n be 0.
|
437 | let n = 0;
|
438 | // 4. For each part in parts, do:
|
439 | for (let i = 0; parts.length > i; i++) {
|
440 | let part = parts[i];
|
441 | // a. Let O be ObjectCreate(%ObjectPrototype%).
|
442 | let O = {};
|
443 | // a. Perform ? CreateDataPropertyOrThrow(O, "type", part.[[type]]).
|
444 | O.type = part['[[type]]'];
|
445 | // a. Perform ? CreateDataPropertyOrThrow(O, "value", part.[[value]]).
|
446 | O.value = part['[[value]]'];
|
447 | // a. Perform ? CreateDataPropertyOrThrow(result, ? ToString(n), O).
|
448 | result[n] = O;
|
449 | // a. Increment n by 1.
|
450 | n += 1;
|
451 | }
|
452 | // 5. Return result.
|
453 | return result;
|
454 | }
|
455 |
|
456 | /*
|
457 | * @spec[stasm/ecma402/number-format-to-parts/spec/numberformat.html]
|
458 | * @clause[sec-partitionnumberpattern]
|
459 | */
|
460 | function PartitionNumberPattern(numberFormat, x) {
|
461 |
|
462 | let internal = getInternalProperties(numberFormat),
|
463 | locale = internal['[[dataLocale]]'],
|
464 | nums = internal['[[numberingSystem]]'],
|
465 | data = internals.NumberFormat['[[localeData]]'][locale],
|
466 | ild = data.symbols[nums] || data.symbols.latn,
|
467 | pattern;
|
468 |
|
469 | // 1. If x is not NaN and x < 0, then:
|
470 | if (!isNaN(x) && x < 0) {
|
471 | // a. Let x be -x.
|
472 | x = -x;
|
473 | // a. Let pattern be the value of numberFormat.[[negativePattern]].
|
474 | pattern = internal['[[negativePattern]]'];
|
475 | }
|
476 | // 2. Else,
|
477 | else {
|
478 | // a. Let pattern be the value of numberFormat.[[positivePattern]].
|
479 | pattern = internal['[[positivePattern]]'];
|
480 | }
|
481 | // 3. Let result be a new empty List.
|
482 | let result = new List();
|
483 | // 4. Let beginIndex be Call(%StringProto_indexOf%, pattern, "{", 0).
|
484 | let beginIndex = pattern.indexOf('{', 0);
|
485 | // 5. Let endIndex be 0.
|
486 | let endIndex = 0;
|
487 | // 6. Let nextIndex be 0.
|
488 | let nextIndex = 0;
|
489 | // 7. Let length be the number of code units in pattern.
|
490 | let length = pattern.length;
|
491 | // 8. Repeat while beginIndex is an integer index into pattern:
|
492 | while (beginIndex > -1 && beginIndex < length) {
|
493 | // a. Set endIndex to Call(%StringProto_indexOf%, pattern, "}", beginIndex)
|
494 | endIndex = pattern.indexOf('}', beginIndex);
|
495 | // a. If endIndex = -1, throw new Error exception.
|
496 | if (endIndex === -1) throw new Error();
|
497 | // a. If beginIndex is greater than nextIndex, then:
|
498 | if (beginIndex > nextIndex) {
|
499 | // i. Let literal be a substring of pattern from position nextIndex, inclusive, to position beginIndex, exclusive.
|
500 | let literal = pattern.substring(nextIndex, beginIndex);
|
501 | // ii. Add new part record { [[type]]: "literal", [[value]]: literal } as a new element of the list result.
|
502 | arrPush.call(result, { '[[type]]': 'literal', '[[value]]': literal });
|
503 | }
|
504 | // a. Let p be the substring of pattern from position beginIndex, exclusive, to position endIndex, exclusive.
|
505 | let p = pattern.substring(beginIndex + 1, endIndex);
|
506 | // a. If p is equal "number", then:
|
507 | if (p === "number") {
|
508 | // i. If x is NaN,
|
509 | if (isNaN(x)) {
|
510 | // 1. Let n be an ILD String value indicating the NaN value.
|
511 | let n = ild.nan;
|
512 | // 2. Add new part record { [[type]]: "nan", [[value]]: n } as a new element of the list result.
|
513 | arrPush.call(result, { '[[type]]': 'nan', '[[value]]': n });
|
514 | }
|
515 | // ii. Else if isFinite(x) is false,
|
516 | else if (!isFinite(x)) {
|
517 | // 1. Let n be an ILD String value indicating infinity.
|
518 | let n = ild.infinity;
|
519 | // 2. Add new part record { [[type]]: "infinity", [[value]]: n } as a new element of the list result.
|
520 | arrPush.call(result, { '[[type]]': 'infinity', '[[value]]': n });
|
521 | }
|
522 | // iii. Else,
|
523 | else {
|
524 | // 1. If the value of numberFormat.[[style]] is "percent" and isFinite(x), let x be 100 × x.
|
525 | if (internal['[[style]]'] === 'percent' && isFinite(x)) x *= 100;
|
526 |
|
527 | let n;
|
528 | // 2. If the numberFormat.[[minimumSignificantDigits]] and numberFormat.[[maximumSignificantDigits]] are present, then
|
529 | if (hop.call(internal, '[[minimumSignificantDigits]]') && hop.call(internal, '[[maximumSignificantDigits]]')) {
|
530 | // a. Let n be ToRawPrecision(x, numberFormat.[[minimumSignificantDigits]], numberFormat.[[maximumSignificantDigits]]).
|
531 | n = ToRawPrecision(x, internal['[[minimumSignificantDigits]]'], internal['[[maximumSignificantDigits]]']);
|
532 | }
|
533 | // 3. Else,
|
534 | else {
|
535 | // a. Let n be ToRawFixed(x, numberFormat.[[minimumIntegerDigits]], numberFormat.[[minimumFractionDigits]], numberFormat.[[maximumFractionDigits]]).
|
536 | n = ToRawFixed(x, internal['[[minimumIntegerDigits]]'], internal['[[minimumFractionDigits]]'], internal['[[maximumFractionDigits]]']);
|
537 | }
|
538 | // 4. If the value of the numberFormat.[[numberingSystem]] matches one of the values in the "Numbering System" column of Table 2 below, then
|
539 | if (numSys[nums]) {
|
540 | // a. Let digits be an array whose 10 String valued elements are the UTF-16 string representations of the 10 digits specified in the "Digits" column of the matching row in Table 2.
|
541 | let digits = numSys[nums];
|
542 | // a. Replace each digit in n with the value of digits[digit].
|
543 | n = String(n).replace(/\d/g, (digit) => {
|
544 | return digits[digit];
|
545 | });
|
546 | }
|
547 | // 5. Else use an implementation dependent algorithm to map n to the appropriate representation of n in the given numbering system.
|
548 | else n = String(n); // ###TODO###
|
549 |
|
550 | let integer;
|
551 | let fraction;
|
552 | // 6. Let decimalSepIndex be Call(%StringProto_indexOf%, n, ".", 0).
|
553 | let decimalSepIndex = n.indexOf('.', 0);
|
554 | // 7. If decimalSepIndex > 0, then:
|
555 | if (decimalSepIndex > 0) {
|
556 | // a. Let integer be the substring of n from position 0, inclusive, to position decimalSepIndex, exclusive.
|
557 | integer = n.substring(0, decimalSepIndex);
|
558 | // a. Let fraction be the substring of n from position decimalSepIndex, exclusive, to the end of n.
|
559 | fraction = n.substring(decimalSepIndex + 1, decimalSepIndex.length);
|
560 | }
|
561 | // 8. Else:
|
562 | else {
|
563 | // a. Let integer be n.
|
564 | integer = n;
|
565 | // a. Let fraction be undefined.
|
566 | fraction = undefined;
|
567 | }
|
568 | // 9. If the value of the numberFormat.[[useGrouping]] is true,
|
569 | if (internal['[[useGrouping]]'] === true) {
|
570 | // a. Let groupSepSymbol be the ILND String representing the grouping separator.
|
571 | let groupSepSymbol = ild.group;
|
572 | // a. Let groups be a List whose elements are, in left to right order, the substrings defined by ILND set of locations within the integer.
|
573 | let groups = [];
|
574 | // ----> implementation:
|
575 | // Primary group represents the group closest to the decimal
|
576 | let pgSize = data.patterns.primaryGroupSize || 3;
|
577 | // Secondary group is every other group
|
578 | let sgSize = data.patterns.secondaryGroupSize || pgSize;
|
579 | // Group only if necessary
|
580 | if (integer.length > pgSize) {
|
581 | // Index of the primary grouping separator
|
582 | let end = integer.length - pgSize;
|
583 | // Starting index for our loop
|
584 | let idx = end % sgSize;
|
585 | let start = integer.slice(0, idx);
|
586 | if (start.length) arrPush.call(groups, start);
|
587 | // Loop to separate into secondary grouping digits
|
588 | while (idx < end) {
|
589 | arrPush.call(groups, integer.slice(idx, idx + sgSize));
|
590 | idx += sgSize;
|
591 | }
|
592 | // Add the primary grouping digits
|
593 | arrPush.call(groups, integer.slice(end));
|
594 | } else {
|
595 | arrPush.call(groups, integer);
|
596 | }
|
597 | // a. Assert: The number of elements in groups List is greater than 0.
|
598 | if (groups.length === 0) throw new Error();
|
599 | // a. Repeat, while groups List is not empty:
|
600 | while (groups.length) {
|
601 | // i. Remove the first element from groups and let integerGroup be the value of that element.
|
602 | let integerGroup = arrShift.call(groups);
|
603 | // ii. Add new part record { [[type]]: "integer", [[value]]: integerGroup } as a new element of the list result.
|
604 | arrPush.call(result, { '[[type]]': 'integer', '[[value]]': integerGroup });
|
605 | // iii. If groups List is not empty, then:
|
606 | if (groups.length) {
|
607 | // 1. Add new part record { [[type]]: "group", [[value]]: groupSepSymbol } as a new element of the list result.
|
608 | arrPush.call(result, { '[[type]]': 'group', '[[value]]': groupSepSymbol });
|
609 | }
|
610 | }
|
611 | }
|
612 | // 10. Else,
|
613 | else {
|
614 | // a. Add new part record { [[type]]: "integer", [[value]]: integer } as a new element of the list result.
|
615 | arrPush.call(result, { '[[type]]': 'integer', '[[value]]': integer });
|
616 | }
|
617 | // 11. If fraction is not undefined, then:
|
618 | if (fraction !== undefined) {
|
619 | // a. Let decimalSepSymbol be the ILND String representing the decimal separator.
|
620 | let decimalSepSymbol = ild.decimal;
|
621 | // a. Add new part record { [[type]]: "decimal", [[value]]: decimalSepSymbol } as a new element of the list result.
|
622 | arrPush.call(result, { '[[type]]': 'decimal', '[[value]]': decimalSepSymbol });
|
623 | // a. Add new part record { [[type]]: "fraction", [[value]]: fraction } as a new element of the list result.
|
624 | arrPush.call(result, { '[[type]]': 'fraction', '[[value]]': fraction });
|
625 | }
|
626 | }
|
627 | }
|
628 | // a. Else if p is equal "plusSign", then:
|
629 | else if (p === "plusSign") {
|
630 | // i. Let plusSignSymbol be the ILND String representing the plus sign.
|
631 | let plusSignSymbol = ild.plusSign;
|
632 | // ii. Add new part record { [[type]]: "plusSign", [[value]]: plusSignSymbol } as a new element of the list result.
|
633 | arrPush.call(result, { '[[type]]': 'plusSign', '[[value]]': plusSignSymbol });
|
634 | }
|
635 | // a. Else if p is equal "minusSign", then:
|
636 | else if (p === "minusSign") {
|
637 | // i. Let minusSignSymbol be the ILND String representing the minus sign.
|
638 | let minusSignSymbol = ild.minusSign;
|
639 | // ii. Add new part record { [[type]]: "minusSign", [[value]]: minusSignSymbol } as a new element of the list result.
|
640 | arrPush.call(result, { '[[type]]': 'minusSign', '[[value]]': minusSignSymbol });
|
641 | }
|
642 | // a. Else if p is equal "percentSign" and numberFormat.[[style]] is "percent", then:
|
643 | else if (p === "percentSign" && internal['[[style]]'] === "percent") {
|
644 | // i. Let percentSignSymbol be the ILND String representing the percent sign.
|
645 | let percentSignSymbol = ild.percentSign;
|
646 | // ii. Add new part record { [[type]]: "percentSign", [[value]]: percentSignSymbol } as a new element of the list result.
|
647 | arrPush.call(result, { '[[type]]': 'literal', '[[value]]': percentSignSymbol });
|
648 | }
|
649 | // a. Else if p is equal "currency" and numberFormat.[[style]] is "currency", then:
|
650 | else if (p === "currency" && internal['[[style]]'] === "currency") {
|
651 | // i. Let currency be the value of numberFormat.[[currency]].
|
652 | let currency = internal['[[currency]]'];
|
653 |
|
654 | let cd;
|
655 |
|
656 | // ii. If numberFormat.[[currencyDisplay]] is "code", then
|
657 | if (internal['[[currencyDisplay]]'] === "code") {
|
658 | // 1. Let cd be currency.
|
659 | cd = currency;
|
660 | }
|
661 | // iii. Else if numberFormat.[[currencyDisplay]] is "symbol", then
|
662 | else if (internal['[[currencyDisplay]]'] === "symbol") {
|
663 | // 1. Let cd be an ILD string representing currency in short form. If the implementation does not have such a representation of currency, use currency itself.
|
664 | cd = data.currencies[currency] || currency;
|
665 | }
|
666 | // iv. Else if numberFormat.[[currencyDisplay]] is "name", then
|
667 | else if (internal['[[currencyDisplay]]'] === "name") {
|
668 | // 1. Let cd be an ILD string representing currency in long form. If the implementation does not have such a representation of currency, then use currency itself.
|
669 | cd = currency;
|
670 | }
|
671 | // v. Add new part record { [[type]]: "currency", [[value]]: cd } as a new element of the list result.
|
672 | arrPush.call(result, { '[[type]]': 'currency', '[[value]]': cd });
|
673 | }
|
674 | // a. Else,
|
675 | else {
|
676 | // i. Let literal be the substring of pattern from position beginIndex, inclusive, to position endIndex, inclusive.
|
677 | let literal = pattern.substring(beginIndex, endIndex);
|
678 | // ii. Add new part record { [[type]]: "literal", [[value]]: literal } as a new element of the list result.
|
679 | arrPush.call(result, { '[[type]]': 'literal', '[[value]]': literal });
|
680 | }
|
681 | // a. Set nextIndex to endIndex + 1.
|
682 | nextIndex = endIndex + 1;
|
683 | // a. Set beginIndex to Call(%StringProto_indexOf%, pattern, "{", nextIndex)
|
684 | beginIndex = pattern.indexOf('{', nextIndex);
|
685 | }
|
686 | // 9. If nextIndex is less than length, then:
|
687 | if (nextIndex < length) {
|
688 | // a. Let literal be the substring of pattern from position nextIndex, inclusive, to position length, exclusive.
|
689 | let literal = pattern.substring(nextIndex, length);
|
690 | // a. Add new part record { [[type]]: "literal", [[value]]: literal } as a new element of the list result.
|
691 | arrPush.call(result, { '[[type]]': 'literal', '[[value]]': literal });
|
692 | }
|
693 | // 10. Return result.
|
694 | return result;
|
695 | }
|
696 |
|
697 | /*
|
698 | * @spec[stasm/ecma402/number-format-to-parts/spec/numberformat.html]
|
699 | * @clause[sec-formatnumber]
|
700 | */
|
701 | export function FormatNumber(numberFormat, x) {
|
702 | // 1. Let parts be ? PartitionNumberPattern(numberFormat, x).
|
703 | let parts = PartitionNumberPattern(numberFormat, x);
|
704 | // 2. Let result be an empty String.
|
705 | let result = '';
|
706 | // 3. For each part in parts, do:
|
707 | for (let i = 0; parts.length > i; i++) {
|
708 | let part = parts[i];
|
709 | // a. Set result to a String value produced by concatenating result and part.[[value]].
|
710 | result += part['[[value]]'];
|
711 | }
|
712 | // 4. Return result.
|
713 | return result;
|
714 | }
|
715 |
|
716 | /**
|
717 | * When the ToRawPrecision abstract operation is called with arguments x (which
|
718 | * must be a finite non-negative number), minPrecision, and maxPrecision (both
|
719 | * must be integers between 1 and 21) the following steps are taken:
|
720 | */
|
721 | function ToRawPrecision (x, minPrecision, maxPrecision) {
|
722 | // 1. Let p be maxPrecision.
|
723 | let p = maxPrecision;
|
724 |
|
725 | let m, e;
|
726 |
|
727 | // 2. If x = 0, then
|
728 | if (x === 0) {
|
729 | // a. Let m be the String consisting of p occurrences of the character "0".
|
730 | m = arrJoin.call(Array (p + 1), '0');
|
731 | // b. Let e be 0.
|
732 | e = 0;
|
733 | }
|
734 | // 3. Else
|
735 | else {
|
736 | // a. Let e and n be integers such that 10ᵖ⁻¹ ≤ n < 10ᵖ and for which the
|
737 | // exact mathematical value of n × 10ᵉ⁻ᵖ⁺¹ – x is as close to zero as
|
738 | // possible. If there are two such sets of e and n, pick the e and n for
|
739 | // which n × 10ᵉ⁻ᵖ⁺¹ is larger.
|
740 | e = log10Floor(Math.abs(x));
|
741 |
|
742 | // Easier to get to m from here
|
743 | let f = Math.round(Math.exp((Math.abs(e - p + 1)) * Math.LN10));
|
744 |
|
745 | // b. Let m be the String consisting of the digits of the decimal
|
746 | // representation of n (in order, with no leading zeroes)
|
747 | m = String(Math.round(e - p + 1 < 0 ? x * f : x / f));
|
748 | }
|
749 |
|
750 | // 4. If e ≥ p, then
|
751 | if (e >= p)
|
752 | // a. Return the concatenation of m and e-p+1 occurrences of the character "0".
|
753 | return m + arrJoin.call(Array(e-p+1 + 1), '0');
|
754 |
|
755 | // 5. If e = p-1, then
|
756 | else if (e === p - 1)
|
757 | // a. Return m.
|
758 | return m;
|
759 |
|
760 | // 6. If e ≥ 0, then
|
761 | else if (e >= 0)
|
762 | // a. Let m be the concatenation of the first e+1 characters of m, the character
|
763 | // ".", and the remaining p–(e+1) characters of m.
|
764 | m = m.slice(0, e + 1) + '.' + m.slice(e + 1);
|
765 |
|
766 | // 7. If e < 0, then
|
767 | else if (e < 0)
|
768 | // a. Let m be the concatenation of the String "0.", –(e+1) occurrences of the
|
769 | // character "0", and the string m.
|
770 | m = '0.' + arrJoin.call(Array (-(e+1) + 1), '0') + m;
|
771 |
|
772 | // 8. If m contains the character ".", and maxPrecision > minPrecision, then
|
773 | if (m.indexOf(".") >= 0 && maxPrecision > minPrecision) {
|
774 | // a. Let cut be maxPrecision – minPrecision.
|
775 | let cut = maxPrecision - minPrecision;
|
776 |
|
777 | // b. Repeat while cut > 0 and the last character of m is "0":
|
778 | while (cut > 0 && m.charAt(m.length-1) === '0') {
|
779 | // i. Remove the last character from m.
|
780 | m = m.slice(0, -1);
|
781 |
|
782 | // ii. Decrease cut by 1.
|
783 | cut--;
|
784 | }
|
785 |
|
786 | // c. If the last character of m is ".", then
|
787 | if (m.charAt(m.length-1) === '.')
|
788 | // i. Remove the last character from m.
|
789 | m = m.slice(0, -1);
|
790 | }
|
791 | // 9. Return m.
|
792 | return m;
|
793 | }
|
794 |
|
795 | /**
|
796 | * @spec[tc39/ecma402/master/spec/numberformat.html]
|
797 | * @clause[sec-torawfixed]
|
798 | * When the ToRawFixed abstract operation is called with arguments x (which must
|
799 | * be a finite non-negative number), minInteger (which must be an integer between
|
800 | * 1 and 21), minFraction, and maxFraction (which must be integers between 0 and
|
801 | * 20) the following steps are taken:
|
802 | */
|
803 | function ToRawFixed(x, minInteger, minFraction, maxFraction) {
|
804 | // 1. Let f be maxFraction.
|
805 | let f = maxFraction;
|
806 | // 2. Let n be an integer for which the exact mathematical value of n ÷ 10f – x is as close to zero as possible. If there are two such n, pick the larger n.
|
807 | let n = Math.pow(10, f) * x; // diverging...
|
808 | // 3. If n = 0, let m be the String "0". Otherwise, let m be the String consisting of the digits of the decimal representation of n (in order, with no leading zeroes).
|
809 | let m = (n === 0 ? "0" : n.toFixed(0)); // divering...
|
810 |
|
811 | {
|
812 | // this diversion is needed to take into consideration big numbers, e.g.:
|
813 | // 1.2344501e+37 -> 12344501000000000000000000000000000000
|
814 | let idx;
|
815 | let exp = (idx = m.indexOf('e')) > -1 ? m.slice(idx + 1) : 0;
|
816 | if (exp) {
|
817 | m = m.slice(0, idx).replace('.', '');
|
818 | m += arrJoin.call(Array(exp - (m.length - 1) + 1), '0');
|
819 | }
|
820 | }
|
821 |
|
822 | let int;
|
823 | // 4. If f ≠ 0, then
|
824 | if (f !== 0) {
|
825 | // a. Let k be the number of characters in m.
|
826 | let k = m.length;
|
827 | // a. If k ≤ f, then
|
828 | if (k <= f) {
|
829 | // i. Let z be the String consisting of f+1–k occurrences of the character "0".
|
830 | let z = arrJoin.call(Array(f + 1 - k + 1), '0');
|
831 | // ii. Let m be the concatenation of Strings z and m.
|
832 | m = z + m;
|
833 | // iii. Let k be f+1.
|
834 | k = f + 1;
|
835 | }
|
836 | // a. Let a be the first k–f characters of m, and let b be the remaining f characters of m.
|
837 | let a = m.substring(0, k - f), b = m.substring(k - f, m.length);
|
838 | // a. Let m be the concatenation of the three Strings a, ".", and b.
|
839 | m = a + "." + b;
|
840 | // a. Let int be the number of characters in a.
|
841 | int = a.length;
|
842 | }
|
843 | // 5. Else, let int be the number of characters in m.
|
844 | else int = m.length;
|
845 | // 6. Let cut be maxFraction – minFraction.
|
846 | let cut = maxFraction - minFraction;
|
847 | // 7. Repeat while cut > 0 and the last character of m is "0":
|
848 | while (cut > 0 && m.slice(-1) === "0") {
|
849 | // a. Remove the last character from m.
|
850 | m = m.slice(0, -1);
|
851 | // a. Decrease cut by 1.
|
852 | cut--;
|
853 | }
|
854 | // 8. If the last character of m is ".", then
|
855 | if (m.slice(-1) === ".") {
|
856 | // a. Remove the last character from m.
|
857 | m = m.slice(0, -1);
|
858 | }
|
859 | // 9. If int < minInteger, then
|
860 | if (int < minInteger) {
|
861 | // a. Let z be the String consisting of minInteger–int occurrences of the character "0".
|
862 | let z = arrJoin.call(Array(minInteger - int + 1), '0');
|
863 | // a. Let m be the concatenation of Strings z and m.
|
864 | m = z + m;
|
865 | }
|
866 | // 10. Return m.
|
867 | return m;
|
868 | }
|
869 |
|
870 | // Sect 11.3.2 Table 2, Numbering systems
|
871 | // ======================================
|
872 | let numSys = {
|
873 | arab: ['\u0660', '\u0661', '\u0662', '\u0663', '\u0664', '\u0665', '\u0666', '\u0667', '\u0668', '\u0669'],
|
874 | arabext: ['\u06F0', '\u06F1', '\u06F2', '\u06F3', '\u06F4', '\u06F5', '\u06F6', '\u06F7', '\u06F8', '\u06F9'],
|
875 | bali: ['\u1B50', '\u1B51', '\u1B52', '\u1B53', '\u1B54', '\u1B55', '\u1B56', '\u1B57', '\u1B58', '\u1B59'],
|
876 | beng: ['\u09E6', '\u09E7', '\u09E8', '\u09E9', '\u09EA', '\u09EB', '\u09EC', '\u09ED', '\u09EE', '\u09EF'],
|
877 | deva: ['\u0966', '\u0967', '\u0968', '\u0969', '\u096A', '\u096B', '\u096C', '\u096D', '\u096E', '\u096F'],
|
878 | fullwide: ['\uFF10', '\uFF11', '\uFF12', '\uFF13', '\uFF14', '\uFF15', '\uFF16', '\uFF17', '\uFF18', '\uFF19'],
|
879 | gujr: ['\u0AE6', '\u0AE7', '\u0AE8', '\u0AE9', '\u0AEA', '\u0AEB', '\u0AEC', '\u0AED', '\u0AEE', '\u0AEF'],
|
880 | guru: ['\u0A66', '\u0A67', '\u0A68', '\u0A69', '\u0A6A', '\u0A6B', '\u0A6C', '\u0A6D', '\u0A6E', '\u0A6F'],
|
881 | hanidec: ['\u3007', '\u4E00', '\u4E8C', '\u4E09', '\u56DB', '\u4E94', '\u516D', '\u4E03', '\u516B', '\u4E5D'],
|
882 | khmr: ['\u17E0', '\u17E1', '\u17E2', '\u17E3', '\u17E4', '\u17E5', '\u17E6', '\u17E7', '\u17E8', '\u17E9'],
|
883 | knda: ['\u0CE6', '\u0CE7', '\u0CE8', '\u0CE9', '\u0CEA', '\u0CEB', '\u0CEC', '\u0CED', '\u0CEE', '\u0CEF'],
|
884 | laoo: ['\u0ED0', '\u0ED1', '\u0ED2', '\u0ED3', '\u0ED4', '\u0ED5', '\u0ED6', '\u0ED7', '\u0ED8', '\u0ED9'],
|
885 | latn: ['\u0030', '\u0031', '\u0032', '\u0033', '\u0034', '\u0035', '\u0036', '\u0037', '\u0038', '\u0039'],
|
886 | limb: ['\u1946', '\u1947', '\u1948', '\u1949', '\u194A', '\u194B', '\u194C', '\u194D', '\u194E', '\u194F'],
|
887 | mlym: ['\u0D66', '\u0D67', '\u0D68', '\u0D69', '\u0D6A', '\u0D6B', '\u0D6C', '\u0D6D', '\u0D6E', '\u0D6F'],
|
888 | mong: ['\u1810', '\u1811', '\u1812', '\u1813', '\u1814', '\u1815', '\u1816', '\u1817', '\u1818', '\u1819'],
|
889 | mymr: ['\u1040', '\u1041', '\u1042', '\u1043', '\u1044', '\u1045', '\u1046', '\u1047', '\u1048', '\u1049'],
|
890 | orya: ['\u0B66', '\u0B67', '\u0B68', '\u0B69', '\u0B6A', '\u0B6B', '\u0B6C', '\u0B6D', '\u0B6E', '\u0B6F'],
|
891 | tamldec: ['\u0BE6', '\u0BE7', '\u0BE8', '\u0BE9', '\u0BEA', '\u0BEB', '\u0BEC', '\u0BED', '\u0BEE', '\u0BEF'],
|
892 | telu: ['\u0C66', '\u0C67', '\u0C68', '\u0C69', '\u0C6A', '\u0C6B', '\u0C6C', '\u0C6D', '\u0C6E', '\u0C6F'],
|
893 | thai: ['\u0E50', '\u0E51', '\u0E52', '\u0E53', '\u0E54', '\u0E55', '\u0E56', '\u0E57', '\u0E58', '\u0E59'],
|
894 | tibt: ['\u0F20', '\u0F21', '\u0F22', '\u0F23', '\u0F24', '\u0F25', '\u0F26', '\u0F27', '\u0F28', '\u0F29'],
|
895 | };
|
896 |
|
897 | /**
|
898 | * This function provides access to the locale and formatting options computed
|
899 | * during initialization of the object.
|
900 | *
|
901 | * The function returns a new object whose properties and attributes are set as
|
902 | * if constructed by an object literal assigning to each of the following
|
903 | * properties the value of the corresponding internal property of this
|
904 | * NumberFormat object (see 11.4): locale, numberingSystem, style, currency,
|
905 | * currencyDisplay, minimumIntegerDigits, minimumFractionDigits,
|
906 | * maximumFractionDigits, minimumSignificantDigits, maximumSignificantDigits, and
|
907 | * useGrouping. Properties whose corresponding internal properties are not present
|
908 | * are not assigned.
|
909 | */
|
910 | /* 11.3.3 */defineProperty(Intl.NumberFormat.prototype, 'resolvedOptions', {
|
911 | configurable: true,
|
912 | writable: true,
|
913 | value: function () {
|
914 | let prop,
|
915 | descs = new Record(),
|
916 | props = [
|
917 | 'locale', 'numberingSystem', 'style', 'currency', 'currencyDisplay',
|
918 | 'minimumIntegerDigits', 'minimumFractionDigits', 'maximumFractionDigits',
|
919 | 'minimumSignificantDigits', 'maximumSignificantDigits', 'useGrouping',
|
920 | ],
|
921 | internal = this !== null && typeof this === 'object' && getInternalProperties(this);
|
922 |
|
923 | // Satisfy test 11.3_b
|
924 | if (!internal || !internal['[[initializedNumberFormat]]'])
|
925 | throw new TypeError('`this` value for resolvedOptions() is not an initialized Intl.NumberFormat object.');
|
926 |
|
927 | for (let i = 0, max = props.length; i < max; i++) {
|
928 | if (hop.call(internal, prop = '[['+ props[i] +']]'))
|
929 | descs[props[i]] = { value: internal[prop], writable: true, configurable: true, enumerable: true };
|
930 | }
|
931 |
|
932 | return objCreate({}, descs);
|
933 | },
|
934 | });
|