(function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : typeof define === 'function' && define.amd ? define(['exports'], factory) : (factory((global.accounting = global.accounting || {}))); }(this, function (exports) { 'use strict'; function __commonjs(fn, module) { return module = { exports: {} }, fn(module, module.exports), module.exports; } /** * The library's settings configuration object. * * Contains default parameters for currency and number formatting */ var settings = { symbol: '$', // default currency symbol is '$' format: '%s%v', // controls output: %s = symbol, %v = value (can be object, see docs) decimal: '.', // decimal point separator thousand: ',', // thousands separator precision: 2, // decimal places grouping: 3, // digit grouping (not implemented yet) stripZeros: false, // strip insignificant zeros from decimal part fallback: 0 // value returned on unformat() failure }; /** * Takes a string/array of strings, removes all formatting/cruft and returns the raw float value * Alias: `accounting.parse(string)` * * Decimal must be included in the regular expression to match floats (defaults to * accounting.settings.decimal), so if the number uses a non-standard decimal * separator, provide it as the second argument. * * Also matches bracketed negatives (eg. '$ (1.99)' => -1.99) * * Doesn't throw any errors (`NaN`s become 0) but this may change in future * * ```js * accounting.unformat("£ 12,345,678.90 GBP"); // 12345678.9 * ``` * * @method unformat * @for accounting * @param {String|Array} value The string or array of strings containing the number/s to parse. * @param {Number} decimal Number of decimal digits of the resultant number * @return {Float} The parsed number */ function unformat(value) { var decimal = arguments.length <= 1 || arguments[1] === undefined ? settings.decimal : arguments[1]; var fallback = arguments.length <= 2 || arguments[2] === undefined ? settings.fallback : arguments[2]; // Recursively unformat arrays: if (Array.isArray(value)) { return value.map(function (val) { return unformat(val, decimal, fallback); }); } // Return the value as-is if it's already a number: if (typeof value === 'number') return value; // Build regex to strip out everything except digits, decimal point and minus sign: var regex = new RegExp('[^0-9-(-)-' + decimal + ']', ['g']); var unformattedValueString = ('' + value).replace(regex, '') // strip out any cruft .replace(decimal, '.') // make sure decimal point is standard .replace(/\(([-]*\d*[^)]?\d+)\)/g, '-$1') // replace bracketed values with negatives .replace(/\((.*)\)/, ''); // remove any brackets that do not have numeric value /** * Handling -ve number and bracket, eg. * (-100) = 100, -(100) = 100, --100 = 100 */ var negative = (unformattedValueString.match(/-/g) || 2).length % 2, absUnformatted = parseFloat(unformattedValueString.replace(/-/g, '')), unformatted = absUnformatted * (negative ? -1 : 1); // This will fail silently which may cause trouble, let's wait and see: return !isNaN(unformatted) ? unformatted : fallback; } /** * Check and normalise the value of precision (must be positive integer) */ function _checkPrecision(val, base) { val = Math.round(Math.abs(val)); return isNaN(val) ? base : val; } /** * Implementation of toFixed() that treats floats more like decimals * * Fixes binary rounding issues (eg. (0.615).toFixed(2) === '0.61') that present * problems for accounting- and finance-related software. * * ```js * (0.615).toFixed(2); // "0.61" (native toFixed has rounding issues) * accounting.toFixed(0.615, 2); // "0.62" * ``` * * @method toFixed * @for accounting * @param {Float} value The float to be treated as a decimal number. * @param {Number} [precision=2] The number of decimal digits to keep. * @return {String} The given number transformed into a string with the given precission */ function toFixed(value, precision) { precision = _checkPrecision(precision, settings.precision); var power = Math.pow(10, precision); // Multiply up by precision, round accurately, then divide and use native toFixed(): return (Math.round((value + 1e-8) * power) / power).toFixed(precision); } var index = __commonjs(function (module) { /* eslint-disable no-unused-vars */ 'use strict'; var hasOwnProperty = Object.prototype.hasOwnProperty; var propIsEnumerable = Object.prototype.propertyIsEnumerable; function toObject(val) { if (val === null || val === undefined) { throw new TypeError('Object.assign cannot be called with null or undefined'); } return Object(val); } module.exports = Object.assign || function (target, source) { var from; var to = toObject(target); var symbols; for (var s = 1; s < arguments.length; s++) { from = Object(arguments[s]); for (var key in from) { if (hasOwnProperty.call(from, key)) { to[key] = from[key]; } } if (Object.getOwnPropertySymbols) { symbols = Object.getOwnPropertySymbols(from); for (var i = 0; i < symbols.length; i++) { if (propIsEnumerable.call(from, symbols[i])) { to[symbols[i]] = from[symbols[i]]; } } } } return to; }; }); var objectAssign = (index && typeof index === 'object' && 'default' in index ? index['default'] : index); function _stripInsignificantZeros(str, decimal) { var parts = str.split(decimal); var integerPart = parts[0]; var decimalPart = parts[1].replace(/0+$/, ''); if (decimalPart.length > 0) { return integerPart + decimal + decimalPart; } return integerPart; } /** * Format a number, with comma-separated thousands and custom precision/decimal places * Alias: `accounting.format()` * * Localise by overriding the precision and thousand / decimal separators * * ```js * accounting.formatNumber(5318008); // 5,318,008 * accounting.formatNumber(9876543.21, { precision: 3, thousand: " " }); // 9 876 543.210 * ``` * * @method formatNumber * @for accounting * @param {Number} number The number to be formatted. * @param {Object} [opts={}] Object containing all the options of the method. * @return {String} The given number properly formatted. */ function formatNumber(number) { var opts = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1]; // Resursively format arrays: if (Array.isArray(number)) { return number.map(function (val) { return formatNumber(val, opts); }); } // Build options object from second param (if object) or all params, extending defaults: opts = objectAssign({}, settings, opts); // Do some calc: var negative = number < 0 ? '-' : ''; var base = parseInt(toFixed(Math.abs(number), opts.precision), 10) + ''; var mod = base.length > 3 ? base.length % 3 : 0; // Format the number: var formatted = negative + (mod ? base.substr(0, mod) + opts.thousand : '') + base.substr(mod).replace(/(\d{3})(?=\d)/g, '$1' + opts.thousand) + (opts.precision > 0 ? opts.decimal + toFixed(Math.abs(number), opts.precision).split('.')[1] : ''); return opts.stripZeros ? _stripInsignificantZeros(formatted, opts.decimal) : formatted; } var index$1 = __commonjs(function (module) { 'use strict'; var strValue = String.prototype.valueOf; var tryStringObject = function tryStringObject(value) { try { strValue.call(value); return true; } catch (e) { return false; } }; var toStr = Object.prototype.toString; var strClass = '[object String]'; var hasToStringTag = typeof Symbol === 'function' && typeof Symbol.toStringTag === 'symbol'; module.exports = function isString(value) { if (typeof value === 'string') { return true; } if (typeof value !== 'object') { return false; } return hasToStringTag ? tryStringObject(value) : toStr.call(value) === strClass; }; }); var isString = (index$1 && typeof index$1 === 'object' && 'default' in index$1 ? index$1['default'] : index$1); /** * Parses a format string or object and returns format obj for use in rendering * * `format` is either a string with the default (positive) format, or object * containing `pos` (required), `neg` and `zero` values * * Either string or format.pos must contain "%v" (value) to be valid * * @method _checkCurrencyFormat * @for accounting * @param {String} [format="%s%v"] String with the format to apply, where %s is the currency symbol and %v is the value. * @return {Object} object represnting format (with pos, neg and zero attributes) */ function _checkCurrencyFormat(format) { // Format should be a string, in which case `value` ('%v') must be present: if (isString(format) && format.match('%v')) { // Create and return positive, negative and zero formats: return { pos: format, neg: format.replace('-', '').replace('%v', '-%v'), zero: format }; } // Otherwise, assume format was fine: return format; } /** * Format a number into currency * * Usage: accounting.formatMoney(number, symbol, precision, thousandsSep, decimalSep, format) * defaults: (0, '$', 2, ',', '.', '%s%v') * * Localise by overriding the symbol, precision, thousand / decimal separators and format * * ```js * // Default usage: * accounting.formatMoney(12345678); // $12,345,678.00 * * // European formatting (custom symbol and separators), can also use options object as second parameter: * accounting.formatMoney(4999.99, { symbol: "€", precision: 2, thousand: ".", decimal: "," }); // €4.999,99 * * // Negative values can be formatted nicely: * accounting.formatMoney(-500000, { symbol: "£ ", precision: 0 }); // £ -500,000 * * // Simple `format` string allows control of symbol position (%v = value, %s = symbol): * accounting.formatMoney(5318008, { symbol: "GBP", format: "%v %s" }); // 5,318,008.00 GBP * ``` * * @method formatMoney * @for accounting * @param {Number} number Number to be formatted. * @param {Object} [opts={}] Object containing all the options of the method. * @return {String} The given number properly formatted as money. */ function formatMoney(number) { var opts = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1]; // Resursively format arrays: if (Array.isArray(number)) { return number.map(function (val) { return formatMoney(val, opts); }); } // Build options object from second param (if object) or all params, extending defaults: opts = objectAssign({}, settings, opts); // Check format (returns object with pos, neg and zero): var formats = _checkCurrencyFormat(opts.format); // Choose which format to use for this value: var useFormat = undefined; if (number > 0) { useFormat = formats.pos; } else if (number < 0) { useFormat = formats.neg; } else { useFormat = formats.zero; } // Return with currency symbol added: return useFormat.replace('%s', opts.symbol).replace('%v', formatNumber(Math.abs(number), opts)); } /** * Format a list of numbers into an accounting column, padding with whitespace * to line up currency symbols, thousand separators and decimals places * * List should be an array of numbers * * Returns array of accouting-formatted number strings of same length * * NB: `white-space:pre` CSS rule is required on the list container to prevent * browsers from collapsing the whitespace in the output strings. * * ```js * accounting.formatColumn([123.5, 3456.49, 777888.99, 12345678, -5432], { symbol: "$ " }); * ``` * * @method formatColumn * @for accounting * @param {Array} list An array of numbers to format * @param {Object} [opts={}] Object containing all the options of the method. * @param {Object|String} [symbol="$"] String with the currency symbol. For conveniency if can be an object containing all the options of the method. * @param {Integer} [precision=2] Number of decimal digits * @param {String} [thousand=','] String with the thousands separator. * @param {String} [decimal="."] String with the decimal separator. * @param {String} [format="%s%v"] String with the format to apply, where %s is the currency symbol and %v is the value. * @return {Array} array of accouting-formatted number strings of same length */ function formatColumn(list) { var opts = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1]; if (!list) return []; // Build options object from second param (if object) or all params, extending defaults: opts = objectAssign({}, settings, opts); // Check format (returns object with pos, neg and zero), only need pos for now: var formats = _checkCurrencyFormat(opts.format); // Whether to pad at start of string or after currency symbol: var padAfterSymbol = formats.pos.indexOf('%s') < formats.pos.indexOf('%v'); // Store value for the length of the longest string in the column: var maxLength = 0; // Format the list according to options, store the length of the longest string: var formatted = list.map(function (val) { if (Array.isArray(val)) { // Recursively format columns if list is a multi-dimensional array: return formatColumn(val, opts); } // Clean up the value val = unformat(val, opts.decimal); // Choose which format to use for this value (pos, neg or zero): var useFormat = undefined; if (val > 0) { useFormat = formats.pos; } else if (val < 0) { useFormat = formats.neg; } else { useFormat = formats.zero; } // Format this value, push into formatted list and save the length: var fVal = useFormat.replace('%s', opts.symbol).replace('%v', formatNumber(Math.abs(val), opts)); if (fVal.length > maxLength) { maxLength = fVal.length; } return fVal; }); // Pad each number in the list and send back the column of numbers: return formatted.map(function (val) { // Only if this is a string (not a nested array, which would have already been padded): if (isString(val) && val.length < maxLength) { // Depending on symbol position, pad after symbol or at index 0: return padAfterSymbol ? val.replace(opts.symbol, opts.symbol + new Array(maxLength - val.length + 1).join(' ')) : new Array(maxLength - val.length + 1).join(' ') + val; } return val; }); } exports.settings = settings; exports.unformat = unformat; exports.toFixed = toFixed; exports.formatMoney = formatMoney; exports.formatNumber = formatNumber; exports.formatColumn = formatColumn; exports.format = formatMoney; exports.parse = unformat; })); //# sourceMappingURL=accounting.umd.js.map