UNPKG

intl

Version:

Polyfill the ECMA-402 Intl API (except collation)

935 lines (821 loc) 44.6 kB
// 11.1 The Intl.NumberFormat constructor // ====================================== import { IsWellFormedCurrencyCode, } from "./6.locales-currencies-tz.js"; import { Intl, } from "./8.intl.js"; import { CanonicalizeLocaleList, SupportedLocales, ResolveLocale, GetNumberOption, GetOption, } from "./9.negotiation.js"; import { internals, log10Floor, List, toObject, arrPush, arrJoin, arrShift, Record, hop, defineProperty, es3, fnBind, getInternalProperties, createRegExpRestore, secret, objCreate, } from "./util.js"; // Currency minor units output from get-4217 grunt task, formatted const currencyMinorUnits = { BHD: 3, BYR: 0, XOF: 0, BIF: 0, XAF: 0, CLF: 4, CLP: 0, KMF: 0, DJF: 0, XPF: 0, GNF: 0, ISK: 0, IQD: 3, JPY: 0, JOD: 3, KRW: 0, KWD: 3, LYD: 3, OMR: 3, PYG: 0, RWF: 0, TND: 3, UGX: 0, UYI: 0, VUV: 0, VND: 0, }; // Define the NumberFormat constructor internally so it cannot be tainted export function NumberFormatConstructor () { let locales = arguments[0]; let options = arguments[1]; if (!this || this === Intl) { return new Intl.NumberFormat(locales, options); } return InitializeNumberFormat(toObject(this), locales, options); } defineProperty(Intl, 'NumberFormat', { configurable: true, writable: true, value: NumberFormatConstructor, }); // Must explicitly set prototypes as unwritable defineProperty(Intl.NumberFormat, 'prototype', { writable: false, }); /** * The abstract operation InitializeNumberFormat accepts the arguments * numberFormat (which must be an object), locales, and options. It initializes * numberFormat as a NumberFormat object. */ export function /*11.1.1.1 */InitializeNumberFormat (numberFormat, locales, options) { // This will be a internal properties object if we're not already initialized let internal = getInternalProperties(numberFormat); // Create an object whose props can be used to restore the values of RegExp props let regexpState = createRegExpRestore(); // 1. If numberFormat has an [[initializedIntlObject]] internal property with // value true, throw a TypeError exception. if (internal['[[initializedIntlObject]]'] === true) throw new TypeError('`this` object has already been initialized as an Intl object'); // Need this to access the `internal` object defineProperty(numberFormat, '__getInternalProperties', { value: function () { // NOTE: Non-standard, for internal use only if (arguments[0] === secret) return internal; }, }); // 2. Set the [[initializedIntlObject]] internal property of numberFormat to true. internal['[[initializedIntlObject]]'] = true; // 3. Let requestedLocales be the result of calling the CanonicalizeLocaleList // abstract operation (defined in 9.2.1) with argument locales. let requestedLocales = CanonicalizeLocaleList(locales); // 4. If options is undefined, then if (options === undefined) // a. Let options be the result of creating a new object as if by the // expression new Object() where Object is the standard built-in constructor // with that name. options = {}; // 5. Else else // a. Let options be ToObject(options). options = toObject(options); // 6. Let opt be a new Record. let opt = new Record(), // 7. Let matcher be the result of calling the GetOption abstract operation // (defined in 9.2.9) with the arguments options, "localeMatcher", "string", // a List containing the two String values "lookup" and "best fit", and // "best fit". matcher = GetOption(options, 'localeMatcher', 'string', new List('lookup', 'best fit'), 'best fit'); // 8. Set opt.[[localeMatcher]] to matcher. opt['[[localeMatcher]]'] = matcher; // 9. Let NumberFormat be the standard built-in object that is the initial value // of Intl.NumberFormat. // 10. Let localeData be the value of the [[localeData]] internal property of // NumberFormat. let localeData = internals.NumberFormat['[[localeData]]']; // 11. Let r be the result of calling the ResolveLocale abstract operation // (defined in 9.2.5) with the [[availableLocales]] internal property of // NumberFormat, requestedLocales, opt, the [[relevantExtensionKeys]] // internal property of NumberFormat, and localeData. let r = ResolveLocale( internals.NumberFormat['[[availableLocales]]'], requestedLocales, opt, internals.NumberFormat['[[relevantExtensionKeys]]'], localeData ); // 12. Set the [[locale]] internal property of numberFormat to the value of // r.[[locale]]. internal['[[locale]]'] = r['[[locale]]']; // 13. Set the [[numberingSystem]] internal property of numberFormat to the value // of r.[[nu]]. internal['[[numberingSystem]]'] = r['[[nu]]']; // The specification doesn't tell us to do this, but it's helpful later on internal['[[dataLocale]]'] = r['[[dataLocale]]']; // 14. Let dataLocale be the value of r.[[dataLocale]]. let dataLocale = r['[[dataLocale]]']; // 15. Let s be the result of calling the GetOption abstract operation with the // arguments options, "style", "string", a List containing the three String // values "decimal", "percent", and "currency", and "decimal". let s = GetOption(options, 'style', 'string', new List('decimal', 'percent', 'currency'), 'decimal'); // 16. Set the [[style]] internal property of numberFormat to s. internal['[[style]]'] = s; // 17. Let c be the result of calling the GetOption abstract operation with the // arguments options, "currency", "string", undefined, and undefined. let c = GetOption(options, 'currency', 'string'); // 18. If c is not undefined and the result of calling the // IsWellFormedCurrencyCode abstract operation (defined in 6.3.1) with // argument c is false, then throw a RangeError exception. if (c !== undefined && !IsWellFormedCurrencyCode(c)) throw new RangeError("'" + c + "' is not a valid currency code"); // 19. If s is "currency" and c is undefined, throw a TypeError exception. if (s === 'currency' && c === undefined) throw new TypeError('Currency code is required when style is currency'); let cDigits; // 20. If s is "currency", then if (s === 'currency') { // a. Let c be the result of converting c to upper case as specified in 6.1. c = c.toUpperCase(); // b. Set the [[currency]] internal property of numberFormat to c. internal['[[currency]]'] = c; // c. Let cDigits be the result of calling the CurrencyDigits abstract // operation (defined below) with argument c. cDigits = CurrencyDigits(c); } // 21. Let cd be the result of calling the GetOption abstract operation with the // arguments options, "currencyDisplay", "string", a List containing the // three String values "code", "symbol", and "name", and "symbol". let cd = GetOption(options, 'currencyDisplay', 'string', new List('code', 'symbol', 'name'), 'symbol'); // 22. If s is "currency", then set the [[currencyDisplay]] internal property of // numberFormat to cd. if (s === 'currency') internal['[[currencyDisplay]]'] = cd; // 23. Let mnid be the result of calling the GetNumberOption abstract operation // (defined in 9.2.10) with arguments options, "minimumIntegerDigits", 1, 21, // and 1. let mnid = GetNumberOption(options, 'minimumIntegerDigits', 1, 21, 1); // 24. Set the [[minimumIntegerDigits]] internal property of numberFormat to mnid. internal['[[minimumIntegerDigits]]'] = mnid; // 25. If s is "currency", then let mnfdDefault be cDigits; else let mnfdDefault // be 0. let mnfdDefault = s === 'currency' ? cDigits : 0; // 26. Let mnfd be the result of calling the GetNumberOption abstract operation // with arguments options, "minimumFractionDigits", 0, 20, and mnfdDefault. let mnfd = GetNumberOption(options, 'minimumFractionDigits', 0, 20, mnfdDefault); // 27. Set the [[minimumFractionDigits]] internal property of numberFormat to mnfd. internal['[[minimumFractionDigits]]'] = mnfd; // 28. If s is "currency", then let mxfdDefault be max(mnfd, cDigits); else if s // is "percent", then let mxfdDefault be max(mnfd, 0); else let mxfdDefault // be max(mnfd, 3). let mxfdDefault = s === 'currency' ? Math.max(mnfd, cDigits) : (s === 'percent' ? Math.max(mnfd, 0) : Math.max(mnfd, 3)); // 29. Let mxfd be the result of calling the GetNumberOption abstract operation // with arguments options, "maximumFractionDigits", mnfd, 20, and mxfdDefault. let mxfd = GetNumberOption(options, 'maximumFractionDigits', mnfd, 20, mxfdDefault); // 30. Set the [[maximumFractionDigits]] internal property of numberFormat to mxfd. internal['[[maximumFractionDigits]]'] = mxfd; // 31. Let mnsd be the result of calling the [[Get]] internal method of options // with argument "minimumSignificantDigits". let mnsd = options.minimumSignificantDigits; // 32. Let mxsd be the result of calling the [[Get]] internal method of options // with argument "maximumSignificantDigits". let mxsd = options.maximumSignificantDigits; // 33. If mnsd is not undefined or mxsd is not undefined, then: if (mnsd !== undefined || mxsd !== undefined) { // a. Let mnsd be the result of calling the GetNumberOption abstract // operation with arguments options, "minimumSignificantDigits", 1, 21, // and 1. mnsd = GetNumberOption(options, 'minimumSignificantDigits', 1, 21, 1); // b. Let mxsd be the result of calling the GetNumberOption abstract // operation with arguments options, "maximumSignificantDigits", mnsd, // 21, and 21. mxsd = GetNumberOption(options, 'maximumSignificantDigits', mnsd, 21, 21); // c. Set the [[minimumSignificantDigits]] internal property of numberFormat // to mnsd, and the [[maximumSignificantDigits]] internal property of // numberFormat to mxsd. internal['[[minimumSignificantDigits]]'] = mnsd; internal['[[maximumSignificantDigits]]'] = mxsd; } // 34. Let g be the result of calling the GetOption abstract operation with the // arguments options, "useGrouping", "boolean", undefined, and true. let g = GetOption(options, 'useGrouping', 'boolean', undefined, true); // 35. Set the [[useGrouping]] internal property of numberFormat to g. internal['[[useGrouping]]'] = g; // 36. Let dataLocaleData be the result of calling the [[Get]] internal method of // localeData with argument dataLocale. let dataLocaleData = localeData[dataLocale]; // 37. Let patterns be the result of calling the [[Get]] internal method of // dataLocaleData with argument "patterns". let patterns = dataLocaleData.patterns; // 38. Assert: patterns is an object (see 11.2.3) // 39. Let stylePatterns be the result of calling the [[Get]] internal method of // patterns with argument s. let stylePatterns = patterns[s]; // 40. Set the [[positivePattern]] internal property of numberFormat to the // result of calling the [[Get]] internal method of stylePatterns with the // argument "positivePattern". internal['[[positivePattern]]'] = stylePatterns.positivePattern; // 41. Set the [[negativePattern]] internal property of numberFormat to the // result of calling the [[Get]] internal method of stylePatterns with the // argument "negativePattern". internal['[[negativePattern]]'] = stylePatterns.negativePattern; // 42. Set the [[boundFormat]] internal property of numberFormat to undefined. internal['[[boundFormat]]'] = undefined; // 43. Set the [[initializedNumberFormat]] internal property of numberFormat to // true. internal['[[initializedNumberFormat]]'] = true; // In ES3, we need to pre-bind the format() function if (es3) numberFormat.format = GetFormatNumber.call(numberFormat); // Restore the RegExp properties regexpState.exp.test(regexpState.input); // Return the newly initialised object return numberFormat; } function CurrencyDigits(currency) { // When the CurrencyDigits abstract operation is called with an argument currency // (which must be an upper case String value), the following steps are taken: // 1. If the ISO 4217 currency and funds code list contains currency as an // alphabetic code, then return the minor unit value corresponding to the // currency from the list; else return 2. return currencyMinorUnits[currency] !== undefined ? currencyMinorUnits[currency] : 2; } /* 11.2.3 */internals.NumberFormat = { '[[availableLocales]]': [], '[[relevantExtensionKeys]]': ['nu'], '[[localeData]]': {}, }; /** * When the supportedLocalesOf method of Intl.NumberFormat is called, the * following steps are taken: */ /* 11.2.2 */ defineProperty(Intl.NumberFormat, 'supportedLocalesOf', { configurable: true, writable: true, value: fnBind.call(function (locales) { // Bound functions only have the `this` value altered if being used as a constructor, // this lets us imitate a native function that has no constructor if (!hop.call(this, '[[availableLocales]]')) throw new TypeError('supportedLocalesOf() is not a constructor'); // Create an object whose props can be used to restore the values of RegExp props let regexpState = createRegExpRestore(), // 1. If options is not provided, then let options be undefined. options = arguments[1], // 2. Let availableLocales be the value of the [[availableLocales]] internal // property of the standard built-in object that is the initial value of // Intl.NumberFormat. availableLocales = this['[[availableLocales]]'], // 3. Let requestedLocales be the result of calling the CanonicalizeLocaleList // abstract operation (defined in 9.2.1) with argument locales. requestedLocales = CanonicalizeLocaleList(locales); // Restore the RegExp properties regexpState.exp.test(regexpState.input); // 4. Return the result of calling the SupportedLocales abstract operation // (defined in 9.2.8) with arguments availableLocales, requestedLocales, // and options. return SupportedLocales(availableLocales, requestedLocales, options); }, internals.NumberFormat), }); /** * This named accessor property returns a function that formats a number * according to the effective locale and the formatting options of this * NumberFormat object. */ /* 11.3.2 */defineProperty(Intl.NumberFormat.prototype, 'format', { configurable: true, get: GetFormatNumber, }); function GetFormatNumber() { let internal = this !== null && typeof this === 'object' && getInternalProperties(this); // Satisfy test 11.3_b if (!internal || !internal['[[initializedNumberFormat]]']) throw new TypeError('`this` value for format() is not an initialized Intl.NumberFormat object.'); // The value of the [[Get]] attribute is a function that takes the following // steps: // 1. If the [[boundFormat]] internal property of this NumberFormat object // is undefined, then: if (internal['[[boundFormat]]'] === undefined) { // a. Let F be a Function object, with internal properties set as // specified for built-in functions in ES5, 15, or successor, and the // length property set to 1, that takes the argument value and // performs the following steps: let F = function (value) { // i. If value is not provided, then let value be undefined. // ii. Let x be ToNumber(value). // iii. Return the result of calling the FormatNumber abstract // operation (defined below) with arguments this and x. return FormatNumber(this, /* x = */Number(value)); }; // b. Let bind be the standard built-in function object defined in ES5, // 15.3.4.5. // c. Let bf be the result of calling the [[Call]] internal method of // bind with F as the this value and an argument list containing // the single item this. let bf = fnBind.call(F, this); // d. Set the [[boundFormat]] internal property of this NumberFormat // object to bf. internal['[[boundFormat]]'] = bf; } // Return the value of the [[boundFormat]] internal property of this // NumberFormat object. return internal['[[boundFormat]]']; } Intl.NumberFormat.prototype.formatToParts = function(value) { let internal = this !== null && typeof this === 'object' && getInternalProperties(this); if (!internal || !internal['[[initializedNumberFormat]]']) throw new TypeError('`this` value for formatToParts() is not an initialized Intl.NumberFormat object.'); let x = Number(value); return FormatNumberToParts(this, x); }; /* * @spec[stasm/ecma402/number-format-to-parts/spec/numberformat.html] * @clause[sec-formatnumbertoparts] */ function FormatNumberToParts(numberFormat, x) { // 1. Let parts be ? PartitionNumberPattern(numberFormat, x). let parts = PartitionNumberPattern(numberFormat, x); // 2. Let result be ArrayCreate(0). let result = []; // 3. Let n be 0. let n = 0; // 4. For each part in parts, do: for (let i = 0; parts.length > i; i++) { let part = parts[i]; // a. Let O be ObjectCreate(%ObjectPrototype%). let O = {}; // a. Perform ? CreateDataPropertyOrThrow(O, "type", part.[[type]]). O.type = part['[[type]]']; // a. Perform ? CreateDataPropertyOrThrow(O, "value", part.[[value]]). O.value = part['[[value]]']; // a. Perform ? CreateDataPropertyOrThrow(result, ? ToString(n), O). result[n] = O; // a. Increment n by 1. n += 1; } // 5. Return result. return result; } /* * @spec[stasm/ecma402/number-format-to-parts/spec/numberformat.html] * @clause[sec-partitionnumberpattern] */ function PartitionNumberPattern(numberFormat, x) { let internal = getInternalProperties(numberFormat), locale = internal['[[dataLocale]]'], nums = internal['[[numberingSystem]]'], data = internals.NumberFormat['[[localeData]]'][locale], ild = data.symbols[nums] || data.symbols.latn, pattern; // 1. If x is not NaN and x < 0, then: if (!isNaN(x) && x < 0) { // a. Let x be -x. x = -x; // a. Let pattern be the value of numberFormat.[[negativePattern]]. pattern = internal['[[negativePattern]]']; } // 2. Else, else { // a. Let pattern be the value of numberFormat.[[positivePattern]]. pattern = internal['[[positivePattern]]']; } // 3. Let result be a new empty List. let result = new List(); // 4. Let beginIndex be Call(%StringProto_indexOf%, pattern, "{", 0). let beginIndex = pattern.indexOf('{', 0); // 5. Let endIndex be 0. let endIndex = 0; // 6. Let nextIndex be 0. let nextIndex = 0; // 7. Let length be the number of code units in pattern. let length = pattern.length; // 8. Repeat while beginIndex is an integer index into pattern: while (beginIndex > -1 && beginIndex < length) { // a. Set endIndex to Call(%StringProto_indexOf%, pattern, "}", beginIndex) endIndex = pattern.indexOf('}', beginIndex); // a. If endIndex = -1, throw new Error exception. if (endIndex === -1) throw new Error(); // a. If beginIndex is greater than nextIndex, then: if (beginIndex > nextIndex) { // i. Let literal be a substring of pattern from position nextIndex, inclusive, to position beginIndex, exclusive. let literal = pattern.substring(nextIndex, beginIndex); // ii. Add new part record { [[type]]: "literal", [[value]]: literal } as a new element of the list result. arrPush.call(result, { '[[type]]': 'literal', '[[value]]': literal }); } // a. Let p be the substring of pattern from position beginIndex, exclusive, to position endIndex, exclusive. let p = pattern.substring(beginIndex + 1, endIndex); // a. If p is equal "number", then: if (p === "number") { // i. If x is NaN, if (isNaN(x)) { // 1. Let n be an ILD String value indicating the NaN value. let n = ild.nan; // 2. Add new part record { [[type]]: "nan", [[value]]: n } as a new element of the list result. arrPush.call(result, { '[[type]]': 'nan', '[[value]]': n }); } // ii. Else if isFinite(x) is false, else if (!isFinite(x)) { // 1. Let n be an ILD String value indicating infinity. let n = ild.infinity; // 2. Add new part record { [[type]]: "infinity", [[value]]: n } as a new element of the list result. arrPush.call(result, { '[[type]]': 'infinity', '[[value]]': n }); } // iii. Else, else { // 1. If the value of numberFormat.[[style]] is "percent" and isFinite(x), let x be 100 × x. if (internal['[[style]]'] === 'percent' && isFinite(x)) x *= 100; let n; // 2. If the numberFormat.[[minimumSignificantDigits]] and numberFormat.[[maximumSignificantDigits]] are present, then if (hop.call(internal, '[[minimumSignificantDigits]]') && hop.call(internal, '[[maximumSignificantDigits]]')) { // a. Let n be ToRawPrecision(x, numberFormat.[[minimumSignificantDigits]], numberFormat.[[maximumSignificantDigits]]). n = ToRawPrecision(x, internal['[[minimumSignificantDigits]]'], internal['[[maximumSignificantDigits]]']); } // 3. Else, else { // a. Let n be ToRawFixed(x, numberFormat.[[minimumIntegerDigits]], numberFormat.[[minimumFractionDigits]], numberFormat.[[maximumFractionDigits]]). n = ToRawFixed(x, internal['[[minimumIntegerDigits]]'], internal['[[minimumFractionDigits]]'], internal['[[maximumFractionDigits]]']); } // 4. If the value of the numberFormat.[[numberingSystem]] matches one of the values in the "Numbering System" column of Table 2 below, then if (numSys[nums]) { // 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. let digits = numSys[nums]; // a. Replace each digit in n with the value of digits[digit]. n = String(n).replace(/\d/g, (digit) => { return digits[digit]; }); } // 5. Else use an implementation dependent algorithm to map n to the appropriate representation of n in the given numbering system. else n = String(n); // ###TODO### let integer; let fraction; // 6. Let decimalSepIndex be Call(%StringProto_indexOf%, n, ".", 0). let decimalSepIndex = n.indexOf('.', 0); // 7. If decimalSepIndex > 0, then: if (decimalSepIndex > 0) { // a. Let integer be the substring of n from position 0, inclusive, to position decimalSepIndex, exclusive. integer = n.substring(0, decimalSepIndex); // a. Let fraction be the substring of n from position decimalSepIndex, exclusive, to the end of n. fraction = n.substring(decimalSepIndex + 1, decimalSepIndex.length); } // 8. Else: else { // a. Let integer be n. integer = n; // a. Let fraction be undefined. fraction = undefined; } // 9. If the value of the numberFormat.[[useGrouping]] is true, if (internal['[[useGrouping]]'] === true) { // a. Let groupSepSymbol be the ILND String representing the grouping separator. let groupSepSymbol = ild.group; // 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. let groups = []; // ----> implementation: // Primary group represents the group closest to the decimal let pgSize = data.patterns.primaryGroupSize || 3; // Secondary group is every other group let sgSize = data.patterns.secondaryGroupSize || pgSize; // Group only if necessary if (integer.length > pgSize) { // Index of the primary grouping separator let end = integer.length - pgSize; // Starting index for our loop let idx = end % sgSize; let start = integer.slice(0, idx); if (start.length) arrPush.call(groups, start); // Loop to separate into secondary grouping digits while (idx < end) { arrPush.call(groups, integer.slice(idx, idx + sgSize)); idx += sgSize; } // Add the primary grouping digits arrPush.call(groups, integer.slice(end)); } else { arrPush.call(groups, integer); } // a. Assert: The number of elements in groups List is greater than 0. if (groups.length === 0) throw new Error(); // a. Repeat, while groups List is not empty: while (groups.length) { // i. Remove the first element from groups and let integerGroup be the value of that element. let integerGroup = arrShift.call(groups); // ii. Add new part record { [[type]]: "integer", [[value]]: integerGroup } as a new element of the list result. arrPush.call(result, { '[[type]]': 'integer', '[[value]]': integerGroup }); // iii. If groups List is not empty, then: if (groups.length) { // 1. Add new part record { [[type]]: "group", [[value]]: groupSepSymbol } as a new element of the list result. arrPush.call(result, { '[[type]]': 'group', '[[value]]': groupSepSymbol }); } } } // 10. Else, else { // a. Add new part record { [[type]]: "integer", [[value]]: integer } as a new element of the list result. arrPush.call(result, { '[[type]]': 'integer', '[[value]]': integer }); } // 11. If fraction is not undefined, then: if (fraction !== undefined) { // a. Let decimalSepSymbol be the ILND String representing the decimal separator. let decimalSepSymbol = ild.decimal; // a. Add new part record { [[type]]: "decimal", [[value]]: decimalSepSymbol } as a new element of the list result. arrPush.call(result, { '[[type]]': 'decimal', '[[value]]': decimalSepSymbol }); // a. Add new part record { [[type]]: "fraction", [[value]]: fraction } as a new element of the list result. arrPush.call(result, { '[[type]]': 'fraction', '[[value]]': fraction }); } } } // a. Else if p is equal "plusSign", then: else if (p === "plusSign") { // i. Let plusSignSymbol be the ILND String representing the plus sign. let plusSignSymbol = ild.plusSign; // ii. Add new part record { [[type]]: "plusSign", [[value]]: plusSignSymbol } as a new element of the list result. arrPush.call(result, { '[[type]]': 'plusSign', '[[value]]': plusSignSymbol }); } // a. Else if p is equal "minusSign", then: else if (p === "minusSign") { // i. Let minusSignSymbol be the ILND String representing the minus sign. let minusSignSymbol = ild.minusSign; // ii. Add new part record { [[type]]: "minusSign", [[value]]: minusSignSymbol } as a new element of the list result. arrPush.call(result, { '[[type]]': 'minusSign', '[[value]]': minusSignSymbol }); } // a. Else if p is equal "percentSign" and numberFormat.[[style]] is "percent", then: else if (p === "percentSign" && internal['[[style]]'] === "percent") { // i. Let percentSignSymbol be the ILND String representing the percent sign. let percentSignSymbol = ild.percentSign; // ii. Add new part record { [[type]]: "percentSign", [[value]]: percentSignSymbol } as a new element of the list result. arrPush.call(result, { '[[type]]': 'literal', '[[value]]': percentSignSymbol }); } // a. Else if p is equal "currency" and numberFormat.[[style]] is "currency", then: else if (p === "currency" && internal['[[style]]'] === "currency") { // i. Let currency be the value of numberFormat.[[currency]]. let currency = internal['[[currency]]']; let cd; // ii. If numberFormat.[[currencyDisplay]] is "code", then if (internal['[[currencyDisplay]]'] === "code") { // 1. Let cd be currency. cd = currency; } // iii. Else if numberFormat.[[currencyDisplay]] is "symbol", then else if (internal['[[currencyDisplay]]'] === "symbol") { // 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. cd = data.currencies[currency] || currency; } // iv. Else if numberFormat.[[currencyDisplay]] is "name", then else if (internal['[[currencyDisplay]]'] === "name") { // 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. cd = currency; } // v. Add new part record { [[type]]: "currency", [[value]]: cd } as a new element of the list result. arrPush.call(result, { '[[type]]': 'currency', '[[value]]': cd }); } // a. Else, else { // i. Let literal be the substring of pattern from position beginIndex, inclusive, to position endIndex, inclusive. let literal = pattern.substring(beginIndex, endIndex); // ii. Add new part record { [[type]]: "literal", [[value]]: literal } as a new element of the list result. arrPush.call(result, { '[[type]]': 'literal', '[[value]]': literal }); } // a. Set nextIndex to endIndex + 1. nextIndex = endIndex + 1; // a. Set beginIndex to Call(%StringProto_indexOf%, pattern, "{", nextIndex) beginIndex = pattern.indexOf('{', nextIndex); } // 9. If nextIndex is less than length, then: if (nextIndex < length) { // a. Let literal be the substring of pattern from position nextIndex, inclusive, to position length, exclusive. let literal = pattern.substring(nextIndex, length); // a. Add new part record { [[type]]: "literal", [[value]]: literal } as a new element of the list result. arrPush.call(result, { '[[type]]': 'literal', '[[value]]': literal }); } // 10. Return result. return result; } /* * @spec[stasm/ecma402/number-format-to-parts/spec/numberformat.html] * @clause[sec-formatnumber] */ export function FormatNumber(numberFormat, x) { // 1. Let parts be ? PartitionNumberPattern(numberFormat, x). let parts = PartitionNumberPattern(numberFormat, x); // 2. Let result be an empty String. let result = ''; // 3. For each part in parts, do: for (let i = 0; parts.length > i; i++) { let part = parts[i]; // a. Set result to a String value produced by concatenating result and part.[[value]]. result += part['[[value]]']; } // 4. Return result. return result; } /** * When the ToRawPrecision abstract operation is called with arguments x (which * must be a finite non-negative number), minPrecision, and maxPrecision (both * must be integers between 1 and 21) the following steps are taken: */ function ToRawPrecision (x, minPrecision, maxPrecision) { // 1. Let p be maxPrecision. let p = maxPrecision; let m, e; // 2. If x = 0, then if (x === 0) { // a. Let m be the String consisting of p occurrences of the character "0". m = arrJoin.call(Array (p + 1), '0'); // b. Let e be 0. e = 0; } // 3. Else else { // a. Let e and n be integers such that 10ᵖ⁻¹ ≤ n < 10ᵖ and for which the // exact mathematical value of n × 10ᵉ⁻ᵖ⁺¹ – x is as close to zero as // possible. If there are two such sets of e and n, pick the e and n for // which n × 10ᵉ⁻ᵖ⁺¹ is larger. e = log10Floor(Math.abs(x)); // Easier to get to m from here let f = Math.round(Math.exp((Math.abs(e - p + 1)) * Math.LN10)); // b. Let m be the String consisting of the digits of the decimal // representation of n (in order, with no leading zeroes) m = String(Math.round(e - p + 1 < 0 ? x * f : x / f)); } // 4. If e ≥ p, then if (e >= p) // a. Return the concatenation of m and e-p+1 occurrences of the character "0". return m + arrJoin.call(Array(e-p+1 + 1), '0'); // 5. If e = p-1, then else if (e === p - 1) // a. Return m. return m; // 6. If e ≥ 0, then else if (e >= 0) // a. Let m be the concatenation of the first e+1 characters of m, the character // ".", and the remaining p–(e+1) characters of m. m = m.slice(0, e + 1) + '.' + m.slice(e + 1); // 7. If e < 0, then else if (e < 0) // a. Let m be the concatenation of the String "0.", –(e+1) occurrences of the // character "0", and the string m. m = '0.' + arrJoin.call(Array (-(e+1) + 1), '0') + m; // 8. If m contains the character ".", and maxPrecision > minPrecision, then if (m.indexOf(".") >= 0 && maxPrecision > minPrecision) { // a. Let cut be maxPrecision – minPrecision. let cut = maxPrecision - minPrecision; // b. Repeat while cut > 0 and the last character of m is "0": while (cut > 0 && m.charAt(m.length-1) === '0') { // i. Remove the last character from m. m = m.slice(0, -1); // ii. Decrease cut by 1. cut--; } // c. If the last character of m is ".", then if (m.charAt(m.length-1) === '.') // i. Remove the last character from m. m = m.slice(0, -1); } // 9. Return m. return m; } /** * @spec[tc39/ecma402/master/spec/numberformat.html] * @clause[sec-torawfixed] * When the ToRawFixed abstract operation is called with arguments x (which must * be a finite non-negative number), minInteger (which must be an integer between * 1 and 21), minFraction, and maxFraction (which must be integers between 0 and * 20) the following steps are taken: */ function ToRawFixed(x, minInteger, minFraction, maxFraction) { // 1. Let f be maxFraction. let f = maxFraction; // 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. let n = Math.pow(10, f) * x; // diverging... // 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). let m = (n === 0 ? "0" : n.toFixed(0)); // divering... { // this diversion is needed to take into consideration big numbers, e.g.: // 1.2344501e+37 -> 12344501000000000000000000000000000000 let idx; let exp = (idx = m.indexOf('e')) > -1 ? m.slice(idx + 1) : 0; if (exp) { m = m.slice(0, idx).replace('.', ''); m += arrJoin.call(Array(exp - (m.length - 1) + 1), '0'); } } let int; // 4. If f ≠ 0, then if (f !== 0) { // a. Let k be the number of characters in m. let k = m.length; // a. If k ≤ f, then if (k <= f) { // i. Let z be the String consisting of f+1–k occurrences of the character "0". let z = arrJoin.call(Array(f + 1 - k + 1), '0'); // ii. Let m be the concatenation of Strings z and m. m = z + m; // iii. Let k be f+1. k = f + 1; } // a. Let a be the first k–f characters of m, and let b be the remaining f characters of m. let a = m.substring(0, k - f), b = m.substring(k - f, m.length); // a. Let m be the concatenation of the three Strings a, ".", and b. m = a + "." + b; // a. Let int be the number of characters in a. int = a.length; } // 5. Else, let int be the number of characters in m. else int = m.length; // 6. Let cut be maxFraction – minFraction. let cut = maxFraction - minFraction; // 7. Repeat while cut > 0 and the last character of m is "0": while (cut > 0 && m.slice(-1) === "0") { // a. Remove the last character from m. m = m.slice(0, -1); // a. Decrease cut by 1. cut--; } // 8. If the last character of m is ".", then if (m.slice(-1) === ".") { // a. Remove the last character from m. m = m.slice(0, -1); } // 9. If int < minInteger, then if (int < minInteger) { // a. Let z be the String consisting of minInteger–int occurrences of the character "0". let z = arrJoin.call(Array(minInteger - int + 1), '0'); // a. Let m be the concatenation of Strings z and m. m = z + m; } // 10. Return m. return m; } // Sect 11.3.2 Table 2, Numbering systems // ====================================== let numSys = { arab: ['\u0660', '\u0661', '\u0662', '\u0663', '\u0664', '\u0665', '\u0666', '\u0667', '\u0668', '\u0669'], arabext: ['\u06F0', '\u06F1', '\u06F2', '\u06F3', '\u06F4', '\u06F5', '\u06F6', '\u06F7', '\u06F8', '\u06F9'], bali: ['\u1B50', '\u1B51', '\u1B52', '\u1B53', '\u1B54', '\u1B55', '\u1B56', '\u1B57', '\u1B58', '\u1B59'], beng: ['\u09E6', '\u09E7', '\u09E8', '\u09E9', '\u09EA', '\u09EB', '\u09EC', '\u09ED', '\u09EE', '\u09EF'], deva: ['\u0966', '\u0967', '\u0968', '\u0969', '\u096A', '\u096B', '\u096C', '\u096D', '\u096E', '\u096F'], fullwide: ['\uFF10', '\uFF11', '\uFF12', '\uFF13', '\uFF14', '\uFF15', '\uFF16', '\uFF17', '\uFF18', '\uFF19'], gujr: ['\u0AE6', '\u0AE7', '\u0AE8', '\u0AE9', '\u0AEA', '\u0AEB', '\u0AEC', '\u0AED', '\u0AEE', '\u0AEF'], guru: ['\u0A66', '\u0A67', '\u0A68', '\u0A69', '\u0A6A', '\u0A6B', '\u0A6C', '\u0A6D', '\u0A6E', '\u0A6F'], hanidec: ['\u3007', '\u4E00', '\u4E8C', '\u4E09', '\u56DB', '\u4E94', '\u516D', '\u4E03', '\u516B', '\u4E5D'], khmr: ['\u17E0', '\u17E1', '\u17E2', '\u17E3', '\u17E4', '\u17E5', '\u17E6', '\u17E7', '\u17E8', '\u17E9'], knda: ['\u0CE6', '\u0CE7', '\u0CE8', '\u0CE9', '\u0CEA', '\u0CEB', '\u0CEC', '\u0CED', '\u0CEE', '\u0CEF'], laoo: ['\u0ED0', '\u0ED1', '\u0ED2', '\u0ED3', '\u0ED4', '\u0ED5', '\u0ED6', '\u0ED7', '\u0ED8', '\u0ED9'], latn: ['\u0030', '\u0031', '\u0032', '\u0033', '\u0034', '\u0035', '\u0036', '\u0037', '\u0038', '\u0039'], limb: ['\u1946', '\u1947', '\u1948', '\u1949', '\u194A', '\u194B', '\u194C', '\u194D', '\u194E', '\u194F'], mlym: ['\u0D66', '\u0D67', '\u0D68', '\u0D69', '\u0D6A', '\u0D6B', '\u0D6C', '\u0D6D', '\u0D6E', '\u0D6F'], mong: ['\u1810', '\u1811', '\u1812', '\u1813', '\u1814', '\u1815', '\u1816', '\u1817', '\u1818', '\u1819'], mymr: ['\u1040', '\u1041', '\u1042', '\u1043', '\u1044', '\u1045', '\u1046', '\u1047', '\u1048', '\u1049'], orya: ['\u0B66', '\u0B67', '\u0B68', '\u0B69', '\u0B6A', '\u0B6B', '\u0B6C', '\u0B6D', '\u0B6E', '\u0B6F'], tamldec: ['\u0BE6', '\u0BE7', '\u0BE8', '\u0BE9', '\u0BEA', '\u0BEB', '\u0BEC', '\u0BED', '\u0BEE', '\u0BEF'], telu: ['\u0C66', '\u0C67', '\u0C68', '\u0C69', '\u0C6A', '\u0C6B', '\u0C6C', '\u0C6D', '\u0C6E', '\u0C6F'], thai: ['\u0E50', '\u0E51', '\u0E52', '\u0E53', '\u0E54', '\u0E55', '\u0E56', '\u0E57', '\u0E58', '\u0E59'], tibt: ['\u0F20', '\u0F21', '\u0F22', '\u0F23', '\u0F24', '\u0F25', '\u0F26', '\u0F27', '\u0F28', '\u0F29'], }; /** * This function provides access to the locale and formatting options computed * during initialization of the object. * * The function returns a new object whose properties and attributes are set as * if constructed by an object literal assigning to each of the following * properties the value of the corresponding internal property of this * NumberFormat object (see 11.4): locale, numberingSystem, style, currency, * currencyDisplay, minimumIntegerDigits, minimumFractionDigits, * maximumFractionDigits, minimumSignificantDigits, maximumSignificantDigits, and * useGrouping. Properties whose corresponding internal properties are not present * are not assigned. */ /* 11.3.3 */defineProperty(Intl.NumberFormat.prototype, 'resolvedOptions', { configurable: true, writable: true, value: function () { let prop, descs = new Record(), props = [ 'locale', 'numberingSystem', 'style', 'currency', 'currencyDisplay', 'minimumIntegerDigits', 'minimumFractionDigits', 'maximumFractionDigits', 'minimumSignificantDigits', 'maximumSignificantDigits', 'useGrouping', ], internal = this !== null && typeof this === 'object' && getInternalProperties(this); // Satisfy test 11.3_b if (!internal || !internal['[[initializedNumberFormat]]']) throw new TypeError('`this` value for resolvedOptions() is not an initialized Intl.NumberFormat object.'); for (let i = 0, max = props.length; i < max; i++) { if (hop.call(internal, prop = '[['+ props[i] +']]')) descs[props[i]] = { value: internal[prop], writable: true, configurable: true, enumerable: true }; } return objCreate({}, descs); }, });