numbro
Version:
Format and manipulate numbers.
1,544 lines (1,351 loc) • 177 kB
JavaScript
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
typeof define === 'function' && define.amd ? define(factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.numbro = factory());
})(this, (function () { 'use strict';
var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};
function getDefaultExportFromCjs (x) {
return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x;
}
/*!
* Copyright (c) 2017 Benjamin Van Ryseghem<benjamin@vanryseghem.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
var enUS;
var hasRequiredEnUS;
function requireEnUS () {
if (hasRequiredEnUS) return enUS;
hasRequiredEnUS = 1;
enUS = {
languageTag: "en-US",
delimiters: {
thousands: ",",
decimal: "."
},
abbreviations: {
thousand: "k",
million: "m",
billion: "b",
trillion: "t"
},
spaceSeparated: false,
ordinal: function(number) {
let b = number % 10;
return (~~(number % 100 / 10) === 1) ? "th" : (b === 1) ? "st" : (b === 2) ? "nd" : (b === 3) ? "rd" : "th";
},
bytes: {
binarySuffixes: ["B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB"],
decimalSuffixes: ["B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"]
},
currency: {
symbol: "$",
position: "prefix",
code: "USD"
},
currencyFormat: {
thousandSeparated: true,
totalLength: 4,
spaceSeparated: true,
spaceSeparatedCurrency: true
},
formats: {
fourDigits: {
totalLength: 4,
spaceSeparated: true
},
fullWithTwoDecimals: {
output: "currency",
thousandSeparated: true,
mantissa: 2
},
fullWithTwoDecimalsNoCurrency: {
thousandSeparated: true,
mantissa: 2
},
fullWithNoDecimals: {
output: "currency",
thousandSeparated: true,
mantissa: 0
}
}
};
return enUS;
}
/*!
* Copyright (c) 2017 Benjamin Van Ryseghem<benjamin@vanryseghem.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
var unformatting;
var hasRequiredUnformatting;
function requireUnformatting () {
if (hasRequiredUnformatting) return unformatting;
hasRequiredUnformatting = 1;
const allSuffixes = [
{key: "ZiB", factor: Math.pow(1024, 7)},
{key: "ZB", factor: Math.pow(1000, 7)},
{key: "YiB", factor: Math.pow(1024, 8)},
{key: "YB", factor: Math.pow(1000, 8)},
{key: "TiB", factor: Math.pow(1024, 4)},
{key: "TB", factor: Math.pow(1000, 4)},
{key: "PiB", factor: Math.pow(1024, 5)},
{key: "PB", factor: Math.pow(1000, 5)},
{key: "MiB", factor: Math.pow(1024, 2)},
{key: "MB", factor: Math.pow(1000, 2)},
{key: "KiB", factor: Math.pow(1024, 1)},
{key: "KB", factor: Math.pow(1000, 1)},
{key: "GiB", factor: Math.pow(1024, 3)},
{key: "GB", factor: Math.pow(1000, 3)},
{key: "EiB", factor: Math.pow(1024, 6)},
{key: "EB", factor: Math.pow(1000, 6)},
{key: "B", factor: 1}
];
/**
* Generate a RegExp where S get all RegExp specific characters escaped.
*
* @param {string} s - string representing a RegExp
* @return {string}
*/
function escapeRegExp(s) {
return s.replace(/[-/\\^$*+?.()|[\]{}]/g, "\\$&");
}
/**
* Recursively compute the unformatted value.
*
* @param {string} inputString - string to unformat
* @param {*} delimiters - Delimiters used to generate the inputString
* @param {string} [currencySymbol] - symbol used for currency while generating the inputString
* @param {function} ordinal - function used to generate an ordinal out of a number
* @param {string} zeroFormat - string representing zero
* @param {*} abbreviations - abbreviations used while generating the inputString
* @param {NumbroFormat} format - format used while generating the inputString
* @return {number|undefined}
*/
function computeUnformattedValue(inputString, delimiters, currencySymbol, ordinal, zeroFormat, abbreviations, format) {
if (!isNaN(+inputString)) {
return +inputString;
}
let stripped = "";
// Negative
let newInput = inputString.replace(/(^[^(]*)\((.*)\)([^)]*$)/, "$1$2$3");
if (newInput !== inputString) {
return -1 * computeUnformattedValue(newInput, delimiters, currencySymbol, ordinal, zeroFormat, abbreviations);
}
// Byte
for (let i = 0; i < allSuffixes.length; i++) {
let suffix = allSuffixes[i];
stripped = inputString.replace(RegExp(`([0-9 ])(${suffix.key})$`), "$1");
if (stripped !== inputString) {
return computeUnformattedValue(stripped, delimiters, currencySymbol, ordinal, zeroFormat, abbreviations) * suffix.factor;
}
}
// Percent
stripped = inputString.replace("%", "");
if (stripped !== inputString) {
return computeUnformattedValue(stripped, delimiters, currencySymbol, ordinal, zeroFormat, abbreviations) / 100;
}
// Ordinal
let possibleOrdinalValue = parseFloat(inputString);
if (isNaN(possibleOrdinalValue)) {
return undefined;
}
let ordinalString = ordinal(possibleOrdinalValue);
if (ordinalString && ordinalString !== ".") { // if ordinal is "." it will be caught next round in the +inputString
stripped = inputString.replace(new RegExp(`${escapeRegExp(ordinalString)}$`), "");
if (stripped !== inputString) {
return computeUnformattedValue(stripped, delimiters, currencySymbol, ordinal, zeroFormat, abbreviations);
}
}
// Average
let inversedAbbreviations = {};
Object.keys(abbreviations).forEach((key) => {
inversedAbbreviations[abbreviations[key]] = key;
});
let abbreviationValues = Object.keys(inversedAbbreviations).sort().reverse();
let numberOfAbbreviations = abbreviationValues.length;
for (let i = 0; i < numberOfAbbreviations; i++) {
let value = abbreviationValues[i];
let key = inversedAbbreviations[value];
stripped = inputString.replace(value, "");
if (stripped !== inputString) {
let factor = undefined;
switch (key) { // eslint-disable-line default-case
case "thousand":
factor = Math.pow(10, 3);
break;
case "million":
factor = Math.pow(10, 6);
break;
case "billion":
factor = Math.pow(10, 9);
break;
case "trillion":
factor = Math.pow(10, 12);
break;
}
return computeUnformattedValue(stripped, delimiters, currencySymbol, ordinal, zeroFormat, abbreviations) * factor;
}
}
return undefined;
}
/**
* Removes in one pass all formatting symbols.
*
* @param {string} inputString - string to unformat
* @param {*} delimiters - Delimiters used to generate the inputString
* @param {string} [currencySymbol] - symbol used for currency while generating the inputString
* @return {string}
*/
function removeFormattingSymbols(inputString, delimiters, currencySymbol) {
// Currency
let stripped = inputString.replace(currencySymbol, "");
// Thousand separators
stripped = stripped.replace(new RegExp(`([0-9])${escapeRegExp(delimiters.thousands)}([0-9])`, "g"), "$1$2");
// Decimal
stripped = stripped.replace(delimiters.decimal, ".");
return stripped;
}
/**
* Unformat a numbro-generated string to retrieve the original value.
*
* @param {string} inputString - string to unformat
* @param {*} delimiters - Delimiters used to generate the inputString
* @param {string} [currencySymbol] - symbol used for currency while generating the inputString
* @param {function} ordinal - function used to generate an ordinal out of a number
* @param {string} zeroFormat - string representing zero
* @param {*} abbreviations - abbreviations used while generating the inputString
* @param {NumbroFormat} format - format used while generating the inputString
* @return {number|undefined}
*/
function unformatValue(inputString, delimiters, currencySymbol = "", ordinal, zeroFormat, abbreviations, format) {
if (inputString === "") {
return undefined;
}
// Zero Format
if (inputString === zeroFormat) {
return 0;
}
let value = removeFormattingSymbols(inputString, delimiters, currencySymbol);
return computeUnformattedValue(value, delimiters, currencySymbol, ordinal, zeroFormat, abbreviations);
}
/**
* Check if the INPUTSTRING represents a time.
*
* @param {string} inputString - string to check
* @param {*} delimiters - Delimiters used while generating the inputString
* @return {boolean}
*/
function matchesTime(inputString, delimiters) {
let separators = inputString.indexOf(":") && delimiters.thousands !== ":";
if (!separators) {
return false;
}
let segments = inputString.split(":");
if (segments.length !== 3) {
return false;
}
let hours = +segments[0];
let minutes = +segments[1];
let seconds = +segments[2];
return !isNaN(hours) && !isNaN(minutes) && !isNaN(seconds);
}
/**
* Unformat a numbro-generated string representing a time to retrieve the original value.
*
* @param {string} inputString - string to unformat
* @return {number}
*/
function unformatTime(inputString) {
let segments = inputString.split(":");
let hours = +segments[0];
let minutes = +segments[1];
let seconds = +segments[2];
return seconds + 60 * minutes + 3600 * hours;
}
/**
* Unformat a numbro-generated string to retrieve the original value.
*
* @param {string} inputString - string to unformat
* @param {NumbroFormat} format - format used while generating the inputString
* @return {number}
*/
function unformat(inputString, format) {
// Avoid circular references
const globalState = requireGlobalState();
let delimiters = globalState.currentDelimiters();
let currencySymbol = globalState.currentCurrency().symbol;
let ordinal = globalState.currentOrdinal();
let zeroFormat = globalState.getZeroFormat();
let abbreviations = globalState.currentAbbreviations();
let value = undefined;
if (typeof inputString === "string") {
if (matchesTime(inputString, delimiters)) {
value = unformatTime(inputString);
} else {
value = unformatValue(inputString, delimiters, currencySymbol, ordinal, zeroFormat, abbreviations);
}
} else if (typeof inputString === "number") {
value = inputString;
} else {
return undefined;
}
if (value === undefined) {
return undefined;
}
return value;
}
unformatting = {
unformat
};
return unformatting;
}
/*!
* Copyright (c) 2017 Benjamin Van Ryseghem<benjamin@vanryseghem.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
var validating$1;
var hasRequiredValidating;
function requireValidating () {
if (hasRequiredValidating) return validating$1;
hasRequiredValidating = 1;
let unformatter = requireUnformatting();
// Simplified regexp supporting only `language`, `script`, and `region`
const bcp47RegExp = /^[a-z]{2,3}(-[a-zA-Z]{4})?(-([A-Z]{2}|[0-9]{3}))?$/;
const validOutputValues = [
"currency",
"percent",
"byte",
"time",
"ordinal",
"number"
];
const validForceAverageValues = [
"trillion",
"billion",
"million",
"thousand"
];
const validCurrencyPosition = [
"prefix",
"infix",
"postfix"
];
const validNegativeValues = [
"sign",
"parenthesis"
];
const validMandatoryAbbreviations = {
type: "object",
children: {
thousand: {
type: "string",
mandatory: true
},
million: {
type: "string",
mandatory: true
},
billion: {
type: "string",
mandatory: true
},
trillion: {
type: "string",
mandatory: true
}
},
mandatory: true
};
const validAbbreviations = {
type: "object",
children: {
thousand: "string",
million: "string",
billion: "string",
trillion: "string"
}
};
const validBaseValues = [
"decimal",
"binary",
"general"
];
const validFormat = {
output: {
type: "string",
validValues: validOutputValues
},
base: {
type: "string",
validValues: validBaseValues,
restriction: (number, format) => format.output === "byte",
message: "`base` must be provided only when the output is `byte`",
mandatory: (format) => format.output === "byte"
},
characteristic: {
type: "number",
restriction: (number) => number >= 0,
message: "value must be positive"
},
prefix: "string",
postfix: "string",
forceAverage: {
type: "string",
validValues: validForceAverageValues
},
average: "boolean",
lowPrecision: {
type: "boolean",
restriction: (number, format) => format.average === true,
message: "`lowPrecision` must be provided only when the option `average` is set"
},
currencyPosition: {
type: "string",
validValues: validCurrencyPosition
},
currencySymbol: "string",
totalLength: {
type: "number",
restrictions: [
{
restriction: (number) => number >= 0,
message: "value must be positive"
},
{
restriction: (number, format) => !format.exponential,
message: "`totalLength` is incompatible with `exponential`"
}
]
},
mantissa: {
type: "number",
restriction: (number) => number >= 0,
message: "value must be positive"
},
optionalMantissa: "boolean",
trimMantissa: "boolean",
roundingFunction: "function",
optionalCharacteristic: "boolean",
thousandSeparated: "boolean",
spaceSeparated: "boolean",
spaceSeparatedCurrency: "boolean",
spaceSeparatedAbbreviation: "boolean",
abbreviations: validAbbreviations,
negative: {
type: "string",
validValues: validNegativeValues
},
forceSign: "boolean",
exponential: {
type: "boolean"
},
prefixSymbol: {
type: "boolean",
restriction: (number, format) => format.output === "percent",
message: "`prefixSymbol` can be provided only when the output is `percent`"
}
};
const validLanguage = {
languageTag: {
type: "string",
mandatory: true,
restriction: (tag) => {
return tag.match(bcp47RegExp);
},
message: "the language tag must follow the BCP 47 specification (see https://tools.ieft.org/html/bcp47)"
},
delimiters: {
type: "object",
children: {
thousands: "string",
decimal: "string",
thousandsSize: "number"
},
mandatory: true
},
abbreviations: validMandatoryAbbreviations,
spaceSeparated: "boolean",
spaceSeparatedCurrency: "boolean",
ordinal: {
type: "function",
mandatory: true
},
bytes: {
type: "object",
children: {
binarySuffixes: "object",
decimalSuffixes: "object"
}
},
currency: {
type: "object",
children: {
symbol: "string",
position: "string",
code: "string"
},
mandatory: true
},
defaults: "format",
ordinalFormat: "format",
byteFormat: "format",
percentageFormat: "format",
currencyFormat: "format",
timeDefaults: "format",
formats: {
type: "object",
children: {
fourDigits: {
type: "format",
mandatory: true
},
fullWithTwoDecimals: {
type: "format",
mandatory: true
},
fullWithTwoDecimalsNoCurrency: {
type: "format",
mandatory: true
},
fullWithNoDecimals: {
type: "format",
mandatory: true
}
}
}
};
/**
* Check the validity of the provided input and format.
* The check is NOT lazy.
*
* @param {string|number|Numbro} input - input to check
* @param {NumbroFormat} format - format to check
* @return {boolean} True when everything is correct
*/
function validate(input, format) {
let validInput = validateInput(input);
let isFormatValid = validateFormat(format);
return validInput && isFormatValid;
}
/**
* Check the validity of the numbro input.
*
* @param {string|number|Numbro} input - input to check
* @return {boolean} True when everything is correct
*/
function validateInput(input) {
let value = unformatter.unformat(input);
return value !== undefined;
}
/**
* Check the validity of the provided format TOVALIDATE against SPEC.
*
* @param {NumbroFormat} toValidate - format to check
* @param {*} spec - specification against which to check
* @param {string} prefix - prefix use for error messages
* @param {boolean} skipMandatoryCheck - `true` when the check for mandatory key must be skipped
* @return {boolean} True when everything is correct
*/
function validateSpec(toValidate, spec, prefix, skipMandatoryCheck = false) {
let results = Object.keys(toValidate).map((key) => {
if (!spec[key]) {
console.error(`${prefix} Invalid key: ${key}`); // eslint-disable-line no-console
return false;
}
let value = toValidate[key];
let data = spec[key];
if (typeof data === "string") {
data = {type: data};
}
if (data.type === "format") { // all formats are partial (a.k.a will be merged with some default values) thus no need to check mandatory values
let valid = validateSpec(value, validFormat, `[Validate ${key}]`, true);
if (!valid) {
return false;
}
} else if (typeof value !== data.type) {
console.error(`${prefix} ${key} type mismatched: "${data.type}" expected, "${typeof value}" provided`); // eslint-disable-line no-console
return false;
}
if (data.restrictions && data.restrictions.length) {
let length = data.restrictions.length;
for (let i = 0; i < length; i++) {
let {restriction, message} = data.restrictions[i];
if (!restriction(value, toValidate)) {
console.error(`${prefix} ${key} invalid value: ${message}`); // eslint-disable-line no-console
return false;
}
}
}
if (data.restriction && !data.restriction(value, toValidate)) {
console.error(`${prefix} ${key} invalid value: ${data.message}`); // eslint-disable-line no-console
return false;
}
if (data.validValues && data.validValues.indexOf(value) === -1) {
console.error(`${prefix} ${key} invalid value: must be among ${JSON.stringify(data.validValues)}, "${value}" provided`); // eslint-disable-line no-console
return false;
}
if (data.children) {
let valid = validateSpec(value, data.children, `[Validate ${key}]`);
if (!valid) {
return false;
}
}
return true;
});
if (!skipMandatoryCheck) {
results.push(...Object.keys(spec).map((key) => {
let data = spec[key];
if (typeof data === "string") {
data = {type: data};
}
if (data.mandatory) {
let mandatory = data.mandatory;
if (typeof mandatory === "function") {
mandatory = mandatory(toValidate);
}
if (mandatory && toValidate[key] === undefined) {
console.error(`${prefix} Missing mandatory key "${key}"`); // eslint-disable-line no-console
return false;
}
}
return true;
}));
}
return results.reduce((acc, current) => {
return acc && current;
}, true);
}
/**
* Check the provided FORMAT.
*
* @param {NumbroFormat} format - format to check
* @return {boolean}
*/
function validateFormat(format) {
return validateSpec(format, validFormat, "[Validate format]");
}
/**
* Check the provided LANGUAGE.
*
* @param {NumbroLanguage} language - language to check
* @return {boolean}
*/
function validateLanguage(language) {
return validateSpec(language, validLanguage, "[Validate language]");
}
validating$1 = {
validate,
validateFormat,
validateInput,
validateLanguage
};
return validating$1;
}
/*!
* Copyright (c) 2017 Benjamin Van Ryseghem<benjamin@vanryseghem.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
/**
* Parse the format STRING looking for a prefix. Append it to RESULT when found.
*
* @param {string} string - format
* @param {NumbroFormat} result - Result accumulator
* @return {string} - format
*/
function parsePrefix(string, result) {
let match = string.match(/^{([^}]*)}/);
if (match) {
result.prefix = match[1];
return string.slice(match[0].length);
}
return string;
}
/**
* Parse the format STRING looking for a postfix. Append it to RESULT when found.
*
* @param {string} string - format
* @param {NumbroFormat} result - Result accumulator
* @return {string} - format
*/
function parsePostfix(string, result) {
let match = string.match(/{([^}]*)}$/);
if (match) {
result.postfix = match[1];
return string.slice(0, -match[0].length);
}
return string;
}
/**
* Parse the format STRING looking for the output value. Append it to RESULT when found.
*
* @param {string} string - format
* @param {NumbroFormat} result - Result accumulator
*/
function parseOutput(string, result) {
if (string.indexOf("$") !== -1) {
result.output = "currency";
return;
}
if (string.indexOf("%") !== -1) {
result.output = "percent";
return;
}
if (string.indexOf("bd") !== -1) {
result.output = "byte";
result.base = "general";
return;
}
if (string.indexOf("b") !== -1) {
result.output = "byte";
result.base = "binary";
return;
}
if (string.indexOf("d") !== -1) {
result.output = "byte";
result.base = "decimal";
return;
}
if (string.indexOf(":") !== -1) {
result.output = "time";
return;
}
if (string.indexOf("o") !== -1) {
result.output = "ordinal";
}
}
/**
* Parse the format STRING looking for the thousand separated value. Append it to RESULT when found.
*
* @param {string} string - format
* @param {NumbroFormat} result - Result accumulator
* @return {string} - format
*/
function parseThousandSeparated(string, result) {
if (string.indexOf(",") !== -1) {
result.thousandSeparated = true;
}
}
/**
* Parse the format STRING looking for the space separated value. Append it to RESULT when found.
*
* @param {string} string - format
* @param {NumbroFormat} result - Result accumulator
* @return {string} - format
*/
function parseSpaceSeparated(string, result) {
if (string.indexOf(" ") !== -1) {
result.spaceSeparated = true;
result.spaceSeparatedCurrency = true;
if (result.average || result.forceAverage) {
result.spaceSeparatedAbbreviation = true;
}
}
}
/**
* Parse the format STRING looking for the total length. Append it to RESULT when found.
*
* @param {string} string - format
* @param {NumbroFormat} result - Result accumulator
* @return {string} - format
*/
function parseTotalLength(string, result) {
let match = string.match(/[1-9]+[0-9]*/);
if (match) {
result.totalLength = +match[0];
}
}
/**
* Parse the format STRING looking for the characteristic length. Append it to RESULT when found.
*
* @param {string} string - format
* @param {NumbroFormat} result - Result accumulator
* @return {string} - format
*/
function parseCharacteristic(string, result) {
let characteristic = string.split(".")[0];
let match = characteristic.match(/0+/);
if (match) {
result.characteristic = match[0].length;
}
}
/**
* Parse the format STRING looking for the mantissa length. Append it to RESULT when found.
*
* @param {string} string - format
* @param {NumbroFormat} result - Result accumulator
* @return {string} - format
*/
function parseMantissa(string, result) {
let mantissa = string.split(".")[1];
if (mantissa) {
let match = mantissa.match(/0+/);
if (match) {
result.mantissa = match[0].length;
}
}
}
/**
* Parse the format STRING looking for a trimmed mantissa. Append it to RESULT when found.
*
* @param {string} string - format
* @param {NumbroFormat} result - Result accumulator
*/
function parseTrimMantissa(string, result) {
const mantissa = string.split(".")[1];
if (mantissa) {
result.trimMantissa = mantissa.indexOf("[") !== -1;
}
}
/**
* Parse the format STRING looking for the average value. Append it to RESULT when found.
*
* @param {string} string - format
* @param {NumbroFormat} result - Result accumulator
* @return {string} - format
*/
function parseAverage(string, result) {
if (string.indexOf("a") !== -1) {
result.average = true;
}
}
/**
* Parse the format STRING looking for a forced average precision. Append it to RESULT when found.
*
* @param {string} string - format
* @param {NumbroFormat} result - Result accumulator
* @return {string} - format
*/
function parseForceAverage(string, result) {
if (string.indexOf("K") !== -1) {
result.forceAverage = "thousand";
} else if (string.indexOf("M") !== -1) {
result.forceAverage = "million";
} else if (string.indexOf("B") !== -1) {
result.forceAverage = "billion";
} else if (string.indexOf("T") !== -1) {
result.forceAverage = "trillion";
}
}
/**
* Parse the format STRING finding if the mantissa is optional. Append it to RESULT when found.
*
* @param {string} string - format
* @param {NumbroFormat} result - Result accumulator
* @return {string} - format
*/
function parseOptionalMantissa(string, result) {
if (string.match(/\[\.]/)) {
result.optionalMantissa = true;
} else if (string.match(/\./)) {
result.optionalMantissa = false;
}
}
/**
* Parse the format STRING finding if the characteristic is optional. Append it to RESULT when found.
*
* @param {string} string - format
* @param {NumbroFormat} result - Result accumulator
* @return {string} - format
*/
function parseOptionalCharacteristic(string, result) {
if (string.indexOf(".") !== -1) {
let characteristic = string.split(".")[0];
result.optionalCharacteristic = characteristic.indexOf("0") === -1;
}
}
/**
* Parse the format STRING looking for the negative format. Append it to RESULT when found.
*
* @param {string} string - format
* @param {NumbroFormat} result - Result accumulator
* @return {string} - format
*/
function parseNegative(string, result) {
if (string.match(/^\+?\([^)]*\)$/)) {
result.negative = "parenthesis";
}
if (string.match(/^\+?-/)) {
result.negative = "sign";
}
}
/**
* Parse the format STRING finding if the sign is mandatory. Append it to RESULT when found.
*
* @param {string} string - format
* @param {NumbroFormat} result - Result accumulator
*/
function parseForceSign(string, result) {
if (string.match(/^\+/)) {
result.forceSign = true;
}
}
/**
* Parse the format STRING and accumulating the values ie RESULT.
*
* @param {string} string - format
* @param {NumbroFormat} result - Result accumulator
* @return {NumbroFormat} - format
*/
function parseFormat(string, result = {}) {
if (typeof string !== "string") {
return string;
}
string = parsePrefix(string, result);
string = parsePostfix(string, result);
parseOutput(string, result);
parseTotalLength(string, result);
parseCharacteristic(string, result);
parseOptionalCharacteristic(string, result);
parseAverage(string, result);
parseForceAverage(string, result);
parseMantissa(string, result);
parseOptionalMantissa(string, result);
parseTrimMantissa(string, result);
parseThousandSeparated(string, result);
parseSpaceSeparated(string, result);
parseNegative(string, result);
parseForceSign(string, result);
return result;
}
var parsing$2 = {
parseFormat
};
/*!
* Copyright (c) 2017 Benjamin Van Ryseghem<benjamin@vanryseghem.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
var globalState$2;
var hasRequiredGlobalState;
function requireGlobalState () {
if (hasRequiredGlobalState) return globalState$2;
hasRequiredGlobalState = 1;
const enUS = requireEnUS();
const validating = requireValidating();
const parsing = parsing$2;
let state = {};
let currentLanguageTag = undefined;
let languages = {};
let zeroFormat = null;
let globalDefaults = {};
function chooseLanguage(tag) { currentLanguageTag = tag; }
function currentLanguageData() { return languages[currentLanguageTag]; }
/**
* Return all the register languages
*
* @return {{}}
*/
state.languages = () => Object.assign({}, languages);
//
// Current language accessors
//
/**
* Return the current language tag
*
* @return {string}
*/
state.currentLanguage = () => currentLanguageTag;
/**
* Return the current language bytes data
*
* @return {{}}
*/
state.currentBytes = () => currentLanguageData().bytes || {};
/**
* Return the current language currency data
*
* @return {{}}
*/
state.currentCurrency = () => currentLanguageData().currency;
/**
* Return the current language abbreviations data
*
* @return {{}}
*/
state.currentAbbreviations = () => currentLanguageData().abbreviations;
/**
* Return the current language delimiters data
*
* @return {{}}
*/
state.currentDelimiters = () => currentLanguageData().delimiters;
/**
* Return the current language ordinal function
*
* @return {function}
*/
state.currentOrdinal = () => currentLanguageData().ordinal;
//
// Defaults
//
/**
* Return the current formatting defaults.
* First use the current language default, then fallback to the globally defined defaults.
*
* @return {{}}
*/
state.currentDefaults = () => Object.assign({}, currentLanguageData().defaults, globalDefaults);
/**
* Return the ordinal default-format.
* First use the current language ordinal default, then fallback to the regular defaults.
*
* @return {{}}
*/
state.currentOrdinalDefaultFormat = () => Object.assign({}, state.currentDefaults(), currentLanguageData().ordinalFormat);
/**
* Return the byte default-format.
* First use the current language byte default, then fallback to the regular defaults.
*
* @return {{}}
*/
state.currentByteDefaultFormat = () => Object.assign({}, state.currentDefaults(), currentLanguageData().byteFormat);
/**
* Return the percentage default-format.
* First use the current language percentage default, then fallback to the regular defaults.
*
* @return {{}}
*/
state.currentPercentageDefaultFormat = () => Object.assign({}, state.currentDefaults(), currentLanguageData().percentageFormat);
/**
* Return the currency default-format.
* First use the current language currency default, then fallback to the regular defaults.
*
* @return {{}}
*/
state.currentCurrencyDefaultFormat = () => Object.assign({}, state.currentDefaults(), currentLanguageData().currencyFormat);
/**
* Return the time default-format.
* First use the current language currency default, then fallback to the regular defaults.
*
* @return {{}}
*/
state.currentTimeDefaultFormat = () => Object.assign({}, state.currentDefaults(), currentLanguageData().timeFormat);
/**
* Set the global formatting defaults.
*
* @param {{}|string} format - formatting options to use as defaults
*/
state.setDefaults = (format) => {
format = parsing.parseFormat(format);
if (validating.validateFormat(format)) {
globalDefaults = format;
}
};
//
// Zero format
//
/**
* Return the format string for 0.
*
* @return {string}
*/
state.getZeroFormat = () => zeroFormat;
/**
* Set a STRING to output when the value is 0.
*
* @param {{}|string} string - string to set
*/
state.setZeroFormat = (string) => zeroFormat = typeof(string) === "string" ? string : null;
/**
* Return true if a format for 0 has been set already.
*
* @return {boolean}
*/
state.hasZeroFormat = () => zeroFormat !== null;
//
// Getters/Setters
//
/**
* Return the language data for the provided TAG.
* Return the current language data if no tag is provided.
*
* Throw an error if the tag doesn't match any registered language.
*
* @param {string} [tag] - language tag of a registered language
* @return {{}}
*/
state.languageData = (tag) => {
if (tag) {
if (languages[tag]) {
return languages[tag];
}
throw new Error(`Unknown tag "${tag}"`);
}
return currentLanguageData();
};
/**
* Register the provided DATA as a language if and only if the data is valid.
* If the data is not valid, an error is thrown.
*
* When USELANGUAGE is true, the registered language is then used.
*
* @param {{}} data - language data to register
* @param {boolean} [useLanguage] - `true` if the provided data should become the current language
*/
state.registerLanguage = (data, useLanguage = false) => {
if (!validating.validateLanguage(data)) {
throw new Error("Invalid language data");
}
languages[data.languageTag] = data;
if (useLanguage) {
chooseLanguage(data.languageTag);
}
};
/**
* Set the current language according to TAG.
* If TAG doesn't match a registered language, another language matching
* the "language" part of the tag (according to BCP47: https://tools.ietf.org/rfc/bcp/bcp47.txt).
* If none, the FALLBACKTAG is used. If the FALLBACKTAG doesn't match a register language,
* `en-US` is finally used.
*
* @param tag
* @param fallbackTag
*/
state.setLanguage = (tag, fallbackTag = enUS.languageTag) => {
if (!languages[tag]) {
let suffix = tag.split("-")[0];
let matchingLanguageTag = Object.keys(languages).find(each => {
return each.split("-")[0] === suffix;
});
if (!languages[matchingLanguageTag]) {
chooseLanguage(fallbackTag);
return;
}
chooseLanguage(matchingLanguageTag);
return;
}
chooseLanguage(tag);
};
state.registerLanguage(enUS);
currentLanguageTag = enUS.languageTag;
globalState$2 = state;
return globalState$2;
}
function commonjsRequire(path) {
throw new Error('Could not dynamically require "' + path + '". Please configure the dynamicRequireTargets or/and ignoreDynamicRequires option of @rollup/plugin-commonjs appropriately for this require call to work.');
}
/*!
* Copyright (c) 2017 Benjamin Van Ryseghem<benjamin@vanryseghem.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
/**
* Load languages matching TAGS. Silently pass over the failing load.
*
* We assume here that we are in a node environment, so we don't check for it.
* @param {[String]} tags - list of tags to load
* @param {Numbro} numbro - the numbro singleton
*/
function loadLanguagesInNode(tags, numbro) {
tags.forEach((tag) => {
let data = undefined;
try {
data = commonjsRequire(`../languages/${tag}`);
} catch (e) {
console.error(`Unable to load "${tag}". No matching language file found.`); // eslint-disable-line no-console
}
if (data) {
numbro.registerLanguage(data);
}
});
}
var loading = (numbro) => ({
loadLanguagesInNode: (tags) => loadLanguagesInNode(tags, numbro)
});
var bignumber = {exports: {}};
(function (module) {
(function (globalObject) {
/*
* bignumber.js v9.1.2
* A JavaScript library for arbitrary-precision arithmetic.
* https://github.com/MikeMcl/bignumber.js
* Copyright (c) 2022 Michael Mclaughlin <M8ch88l@gmail.com>
* MIT Licensed.
*
* BigNumber.prototype methods | BigNumber methods
* |
* absoluteValue abs | clone
* comparedTo | config set
* decimalPlaces dp | DECIMAL_PLACES
* dividedBy div | ROUNDING_MODE
* dividedToIntegerBy idiv | EXPONENTIAL_AT
* exponentiatedBy pow | RANGE
* integerValue | CRYPTO
* isEqualTo eq | MODULO_MODE
* isFinite | POW_PRECISION
* isGreaterThan gt | FORMAT
* isGreaterThanOrEqualTo gte | ALPHABET
* isInteger | isBigNumber
* isLessThan lt | maximum max
* isLessThanOrEqualTo lte | minimum min
* isNaN | random
* isNegative | sum
* isPositive |
* isZero |
* minus |
* modulo mod |
* multipliedBy times |
* negated |
* plus |
* precision sd |
* shiftedBy |
* squareRoot sqrt |
* toExponential |
* toFixed |
* toFormat |
* toFraction |
* toJSON |
* toNumber |
* toPrecision |
* toString |
* valueOf |
*
*/
var BigNumber,
isNumeric = /^-?(?:\d+(?:\.\d*)?|\.\d+)(?:e[+-]?\d+)?$/i,
mathceil = Math.ceil,
mathfloor = Math.floor,
bignumberError = '[BigNumber Error] ',
tooManyDigits = bignumberError + 'Number primitive has more than 15 significant digits: ',
BASE = 1e14,
LOG_BASE = 14,
MAX_SAFE_INTEGER = 0x1fffffffffffff, // 2^53 - 1
// MAX_INT32 = 0x7fffffff, // 2^31 - 1
POWS_TEN = [1, 10, 100, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9, 1e10, 1e11, 1e12, 1e13],
SQRT_BASE = 1e7,
// EDITABLE
// The limit on the value of DECIMAL_PLACES, TO_EXP_NEG, TO_EXP_POS, MIN_EXP, MAX_EXP, and
// the arguments to toExponential, toFixed, toFormat, and toPrecision.
MAX = 1E9; // 0 to MAX_INT32
/*
* Create and return a BigNumber constructor.
*/
function clone(configObject) {
var div, convertBase, parseNumeric,
P = BigNumber.prototype = { constructor: BigNumber, toString: null, valueOf: null },
ONE = new BigNumber(1),
//----------------------------- EDITABLE CONFIG DEFAULTS -------------------------------
// The default values below must be integers within the inclusive ranges stated.
// The values can also be changed at run-time using BigNumber.set.
// The maximum number of decimal places for operations involving division.
DECIMAL_PLACES = 20, // 0 to MAX
// The rounding mode used when rounding to the above decimal places, and when using
// toExponential, toFixed, toFormat and toPrecision, and round (default value).
// UP 0 Away from zero.
// DOWN 1 Towards zero.
// CEIL 2 Towards +Infinity.
// FLOOR 3 Towards -Infinity.
// HALF_UP 4 Towards nearest neighbour. If equidistant, up.
// HALF_DOWN 5 Towards nearest neighbour. If equidistant, down.
// HALF_EVEN 6 Towards nearest neighbour. If equidistant, towards even neighbour.
// HALF_CEIL 7 Towards nearest neighbour. If equidistant, towards +Infinity.
// HALF_FLOOR 8 Towards nearest neighbour. If equidistant, towards -Infinity.
ROUNDING_MODE = 4, // 0 to 8
// EXPONENTIAL_AT : [TO_EXP_NEG , TO_EXP_POS]
// The exponent value at and beneath which toString returns exponential notation.
// Number type: -7
TO_EXP_NEG = -7, // 0 to -MAX
// The exponent value at and above which toString returns exponential notation.
// Number type: 21
TO_EXP_POS = 21, // 0 to MAX
// RANGE : [MIN_EXP, MAX_EXP]
// The mi