dinero.js
Version:
An immutable library to create, calculate and format monetary values.
1,326 lines (1,239 loc) • 62.6 kB
JavaScript
define(function () { 'use strict';
/**
* Default values for all Dinero objects.
*
* You can override default values for all subsequent Dinero objects by changing them directly on the global `Dinero` object.
* Existing instances won't be affected.
*
* @property {Number} defaultAmount - The default amount for new Dinero objects (see {@link module:Dinero Dinero} for format).
* @property {String} defaultCurrency - The default currency for new Dinero objects (see {@link module:Dinero Dinero} for format).
* @property {Number} defaultPrecision - The default precision for new Dinero objects (see {@link module:Dinero Dinero} for format).
*
* @example
* // Will set currency to 'EUR' for all Dinero objects.
* Dinero.defaultCurrency = 'EUR'
*
* @type {Object}
*/
var Defaults = {
defaultAmount: 0,
defaultCurrency: 'USD',
defaultPrecision: 2
};
/**
* Global settings for all Dinero objects.
*
* You can override global values for all subsequent Dinero objects by changing them directly on the global `Dinero` object.
* Existing instances won't be affected.
*
* @property {String} globalLocale - The global locale for new Dinero objects (see {@link module:Dinero~setLocale setLocale} for format).
* @property {String} globalFormat - The global format for new Dinero objects (see {@link module:Dinero~toFormat toFormat} for format).
* @property {String} globalRoundingMode - The global rounding mode for new Dinero objects (see {@link module:Dinero~multiply multiply} or {@link module:Dinero~divide divide} for format).
* @property {String} globalFormatRoundingMode - The global rounding mode to format new Dinero objects (see {@link module:Dinero~toFormat toFormat} or {@link module:Dinero~toRoundedUnit toRoundedUnit} for format).
* @property {(String|Promise)} globalExchangeRatesApi.endpoint - The global exchange rate API endpoint for new Dinero objects, or the global promise that resolves to the exchanges rates (see {@link module:Dinero~convert convert} for format).
* @property {String} globalExchangeRatesApi.propertyPath - The global exchange rate API property path for new Dinero objects (see {@link module:Dinero~convert convert} for format).
* @property {Object} globalExchangeRatesApi.headers - The global exchange rate API headers for new Dinero objects (see {@link module:Dinero~convert convert} for format).
*
* @example
* // Will set locale to 'fr-FR' for all Dinero objects.
* Dinero.globalLocale = 'fr-FR'
* @example
* // Will set global exchange rate API parameters for all Dinero objects.
* Dinero.globalExchangeRatesApi = {
* endpoint: 'https://yourexchangerates.api/latest?base={{from}}',
* propertyPath: 'data.rates.{{to}}',
* headers: {
* 'user-key': 'xxxxxxxxx'
* }
* }
*
* @type {Object}
*/
var Globals = {
globalLocale: 'en-US',
globalFormat: '$0,0.00',
globalRoundingMode: 'HALF_EVEN',
globalFormatRoundingMode: 'HALF_AWAY_FROM_ZERO',
globalExchangeRatesApi: {
endpoint: undefined,
headers: undefined,
propertyPath: undefined
}
};
function _typeof(obj) {
"@babel/helpers - typeof";
if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") {
_typeof = function (obj) {
return typeof obj;
};
} else {
_typeof = function (obj) {
return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj;
};
}
return _typeof(obj);
}
function _toArray(arr) {
return _arrayWithHoles(arr) || _iterableToArray(arr) || _unsupportedIterableToArray(arr) || _nonIterableRest();
}
function _arrayWithHoles(arr) {
if (Array.isArray(arr)) return arr;
}
function _iterableToArray(iter) {
if (typeof Symbol !== "undefined" && Symbol.iterator in Object(iter)) return Array.from(iter);
}
function _unsupportedIterableToArray(o, minLen) {
if (!o) return;
if (typeof o === "string") return _arrayLikeToArray(o, minLen);
var n = Object.prototype.toString.call(o).slice(8, -1);
if (n === "Object" && o.constructor) n = o.constructor.name;
if (n === "Map" || n === "Set") return Array.from(n);
if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen);
}
function _arrayLikeToArray(arr, len) {
if (len == null || len > arr.length) len = arr.length;
for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i];
return arr2;
}
function _nonIterableRest() {
throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
}
/**
* Static methods for Dinero.
* @ignore
*
* @type {Object}
*/
var Static = {
/**
* Returns an array of Dinero objects, normalized to the same precision (the highest).
*
* @memberof module:Dinero
* @method
*
* @param {Dinero[]} objects - An array of Dinero objects
*
* @example
* // returns an array of Dinero objects
* // both with a precision of 3
* // and an amount of 1000
* Dinero.normalizePrecision([
* Dinero({ amount: 100, precision: 2 }),
* Dinero({ amount: 1000, precision: 3 })
* ])
*
* @return {Dinero[]}
*/
normalizePrecision: function normalizePrecision(objects) {
var highestPrecision = objects.reduce(function (a, b) {
return Math.max(a.getPrecision(), b.getPrecision());
});
return objects.map(function (object) {
return object.getPrecision() !== highestPrecision ? object.convertPrecision(highestPrecision) : object;
});
},
/**
* Returns the smallest Dinero object from an array of Dinero objects
*
* @memberof module:Dinero
* @method
*
* @param {Dinero[]} objects - An array of Dinero objects
*
* @example
* // returns the smallest Dinero object with amount of 500 from an array of Dinero objects with different precisions
* Dinero.minimum([
* Dinero({ amount: 500, precision: 3 }),
* Dinero({ amount: 100, precision: 2 })
* ])
* @example
* // returns the smallest Dinero object with amount of 50 from an array of Dinero objects
* Dinero.minimum([
* Dinero({ amount: 50 }),
* Dinero({ amount: 100 })
* ])
*
* @return {Dinero[]}
*/
minimum: function minimum(objects) {
var _objects = _toArray(objects),
firstObject = _objects[0],
tailObjects = _objects.slice(1);
var currentMinimum = firstObject;
tailObjects.forEach(function (obj) {
currentMinimum = currentMinimum.lessThan(obj) ? currentMinimum : obj;
});
return currentMinimum;
},
/**
* Returns the biggest Dinero object from an array of Dinero objects
*
* @memberof module:Dinero
* @method
*
* @param {Dinero[]} objects - An array of Dinero objects
*
* @example
* // returns the biggest Dinero object with amount of 20, from an array of Dinero objects with different precisions
* Dinero.maximum([
* Dinero({ amount: 20, precision: 2 }),
* Dinero({ amount: 150, precision: 3 })
* ])
* @example
* // returns the biggest Dinero object with amount of 100, from an array of Dinero objects
* Dinero.maximum([
* Dinero({ amount: 100 }),
* Dinero({ amount: 50 })
* ])
*
* @return {Dinero[]}
*/
maximum: function maximum(objects) {
var _objects2 = _toArray(objects),
firstObject = _objects2[0],
tailObjects = _objects2.slice(1);
var currentMaximum = firstObject;
tailObjects.forEach(function (obj) {
currentMaximum = currentMaximum.greaterThan(obj) ? currentMaximum : obj;
});
return currentMaximum;
}
};
/**
* Returns whether a value is numeric.
* @ignore
*
* @param {} value - The value to test.
*
* @return {Boolean}
*/
function isNumeric(value) {
return !isNaN(parseInt(value)) && isFinite(value);
}
/**
* Returns whether a value is a percentage.
* @ignore
*
* @param {} percentage - The percentage to test.
*
* @return {Boolean}
*/
function isPercentage(percentage) {
return isNumeric(percentage) && percentage <= 100 && percentage >= 0;
}
/**
* Returns whether an array of ratios is valid.
* @ignore
*
* @param {} ratios - The ratios to test.
*
* @return {Boolean}
*/
function areValidRatios(ratios) {
return ratios.length > 0 && ratios.every(function (ratio) {
return ratio >= 0;
}) && ratios.some(function (ratio) {
return ratio > 0;
});
}
/**
* Returns whether a value is even.
* @ignore
*
* @param {Number} value - The value to test.
*
* @return {Boolean}
*/
function isEven(value) {
return value % 2 === 0;
}
/**
* Returns whether a value is a float.
* @ignore
*
* @param {} value - The value to test.
*
* @return {Boolean}
*/
function isFloat(value) {
return isNumeric(value) && !Number.isInteger(value);
}
/**
* Returns how many fraction digits a number has.
* @ignore
*
* @param {Number} [number=0] - The number to test.
*
* @return {Number}
*/
function countFractionDigits() {
var number = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 0;
var stringRepresentation = number.toString();
if (stringRepresentation.indexOf('e-') > 0) {
// It's too small for a normal string representation, e.g. 1e-7 instead of 0.00000001
return parseInt(stringRepresentation.split('e-')[1]);
} else {
var fractionDigits = stringRepresentation.split('.')[1];
return fractionDigits ? fractionDigits.length : 0;
}
}
/**
* Returns whether a number is half.
* @ignore
*
* @param {Number} number - The number to test.
*
* @return {Number}
*/
function isHalf(number) {
return Math.abs(number) % 1 === 0.5;
}
/**
* Fetches a JSON resource.
* @ignore
*
* @param {String} url - The resource to fetch.
* @param {Object} [options.headers] - The headers to pass.
*
* @throws {Error} If `request.status` is lesser than 200 or greater or equal to 400.
* @throws {Error} If network fails.
*
* @return {JSON}
*/
function getJSON(url) {
var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
return new Promise(function (resolve, reject) {
var request = Object.assign(new XMLHttpRequest(), {
onreadystatechange: function onreadystatechange() {
if (request.readyState === 4) {
if (request.status >= 200 && request.status < 400) resolve(JSON.parse(request.responseText));else reject(new Error(request.statusText));
}
},
onerror: function onerror() {
reject(new Error('Network error'));
}
});
request.open('GET', url, true);
setXHRHeaders(request, options.headers);
request.send();
});
}
/**
* Returns an XHR object with attached headers.
* @ignore
*
* @param {XMLHttpRequest} xhr - The XHR request to set headers to.
* @param {Object} headers - The headers to set.
*
* @return {XMLHttpRequest}
*/
function setXHRHeaders(xhr) {
var headers = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
for (var header in headers) {
xhr.setRequestHeader(header, headers[header]);
}
return xhr;
}
/**
* Returns whether a value is undefined.
* @ignore
*
* @param {} value - The value to test.
*
* @return {Boolean}
*/
function isUndefined(value) {
return typeof value === 'undefined';
}
/**
* Returns an object flattened to one level deep.
* @ignore
*
* @param {Object} object - The object to flatten.
* @param {String} separator - The separator to use between flattened nodes.
*
* @return {Object}
*/
function flattenObject(object) {
var separator = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : '.';
var finalObject = {};
Object.entries(object).forEach(function (item) {
if (_typeof(item[1]) === 'object') {
var flatObject = flattenObject(item[1]);
Object.entries(flatObject).forEach(function (node) {
finalObject[item[0] + separator + node[0]] = node[1];
});
} else {
finalObject[item[0]] = item[1];
}
});
return finalObject;
}
/**
* Returns whether a value is thenable.
* @ignore
*
* @param {} value - The value to test.
*
* @return {Boolean}
*/
function isThenable(value) {
return Boolean(value) && (_typeof(value) === 'object' || typeof value === 'function') && typeof value.then === 'function';
}
function Calculator() {
var floatMultiply = function floatMultiply(a, b) {
var getFactor = function getFactor(number) {
return Math.pow(10, countFractionDigits(number));
};
var factor = Math.max(getFactor(a), getFactor(b));
return Math.round(a * factor) * Math.round(b * factor) / (factor * factor);
};
var roundingModes = {
HALF_ODD: function HALF_ODD(number) {
var rounded = Math.round(number);
return isHalf(number) ? isEven(rounded) ? rounded - 1 : rounded : rounded;
},
HALF_EVEN: function HALF_EVEN(number) {
var rounded = Math.round(number);
return isHalf(number) ? isEven(rounded) ? rounded : rounded - 1 : rounded;
},
HALF_UP: function HALF_UP(number) {
return Math.round(number);
},
HALF_DOWN: function HALF_DOWN(number) {
return isHalf(number) ? Math.floor(number) : Math.round(number);
},
HALF_TOWARDS_ZERO: function HALF_TOWARDS_ZERO(number) {
return isHalf(number) ? Math.sign(number) * Math.floor(Math.abs(number)) : Math.round(number);
},
HALF_AWAY_FROM_ZERO: function HALF_AWAY_FROM_ZERO(number) {
return isHalf(number) ? Math.sign(number) * Math.ceil(Math.abs(number)) : Math.round(number);
},
DOWN: function DOWN(number) {
return Math.floor(number);
}
};
return {
/**
* Returns the sum of two numbers.
* @ignore
*
* @param {Number} a - The first number to add.
* @param {Number} b - The second number to add.
*
* @return {Number}
*/
add: function add(a, b) {
return a + b;
},
/**
* Returns the difference of two numbers.
* @ignore
*
* @param {Number} a - The first number to subtract.
* @param {Number} b - The second number to subtract.
*
* @return {Number}
*/
subtract: function subtract(a, b) {
return a - b;
},
/**
* Returns the product of two numbers.
* @ignore
*
* @param {Number} a - The first number to multiply.
* @param {Number} b - The second number to multiply.
*
* @return {Number}
*/
multiply: function multiply(a, b) {
return isFloat(a) || isFloat(b) ? floatMultiply(a, b) : a * b;
},
/**
* Returns the quotient of two numbers.
* @ignore
*
* @param {Number} a - The first number to divide.
* @param {Number} b - The second number to divide.
*
* @return {Number}
*/
divide: function divide(a, b) {
return a / b;
},
/**
* Returns the remainder of two numbers.
* @ignore
*
* @param {Number} a - The first number to divide.
* @param {Number} b - The second number to divide.
*
* @return {Number}
*/
modulo: function modulo(a, b) {
return a % b;
},
/**
* Returns a rounded number based off a specific rounding mode.
* @ignore
*
* @param {Number} number - The number to round.
* @param {String} [roundingMode='HALF_EVEN'] - The rounding mode to use.
*
* @returns {Number}
*/
round: function round(number) {
var roundingMode = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'HALF_EVEN';
return roundingModes[roundingMode](number);
}
};
}
var calculator = Calculator();
function Format(format) {
var matches = /^(?:(\$|USD)?0(?:(,)0)?(\.)?(0+)?|0(?:(,)0)?(\.)?(0+)?\s?(dollar)?)$/gm.exec(format);
return {
/**
* Returns the matches.
* @ignore
*
* @return {Array}
*/
getMatches: function getMatches() {
return matches !== null ? matches.slice(1).filter(function (match) {
return !isUndefined(match);
}) : [];
},
/**
* Returns the amount of fraction digits to display.
* @ignore
*
* @return {Number}
*/
getMinimumFractionDigits: function getMinimumFractionDigits() {
var decimalPosition = function decimalPosition(match) {
return match === '.';
};
return !isUndefined(this.getMatches().find(decimalPosition)) ? this.getMatches()[calculator.add(this.getMatches().findIndex(decimalPosition), 1)].split('').length : 0;
},
/**
* Returns the currency display mode.
* @ignore
*
* @return {String}
*/
getCurrencyDisplay: function getCurrencyDisplay() {
var modes = {
USD: 'code',
dollar: 'name',
$: 'symbol'
};
return modes[this.getMatches().find(function (match) {
return match === 'USD' || match === 'dollar' || match === '$';
})];
},
/**
* Returns the formatting style.
* @ignore
*
* @return {String}
*/
getStyle: function getStyle() {
return !isUndefined(this.getCurrencyDisplay(this.getMatches())) ? 'currency' : 'decimal';
},
/**
* Returns whether grouping should be used or not.
* @ignore
*
* @return {Boolean}
*/
getUseGrouping: function getUseGrouping() {
return !isUndefined(this.getMatches().find(function (match) {
return match === ',';
}));
}
};
}
function CurrencyConverter(options) {
/* istanbul ignore next */
var mergeTags = function mergeTags() {
var string = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
var tags = arguments.length > 1 ? arguments[1] : undefined;
for (var tag in tags) {
string = string.replace("{{".concat(tag, "}}"), tags[tag]);
}
return string;
};
/* istanbul ignore next */
var getRatesFromRestApi = function getRatesFromRestApi(from, to) {
return getJSON(mergeTags(options.endpoint, {
from: from,
to: to
}), {
headers: options.headers
});
};
return {
/**
* Returns the exchange rate.
* @ignore
*
* @param {String} from - The base currency.
* @param {String} to - The destination currency.
*
* @return {Promise}
*/
getExchangeRate: function getExchangeRate(from, to) {
return (isThenable(options.endpoint) ? options.endpoint : getRatesFromRestApi(from, to)).then(function (data) {
return flattenObject(data)[mergeTags(options.propertyPath, {
from: from,
to: to
})];
});
}
};
}
/**
* Performs an assertion.
* @ignore
*
* @param {Boolean} condition - The expression to assert.
* @param {String} errorMessage - The message to throw if the assertion fails
* @param {ErrorConstructor} [ErrorType=Error] - The error to throw if the assertion fails.
*
* @throws {Error} If `condition` returns `false`.
*/
function assert(condition, errorMessage) {
var ErrorType = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : Error;
if (!condition) throw new ErrorType(errorMessage);
}
/**
* Asserts a value is a percentage.
* @ignore
*
* @param {} percentage - The percentage to test.
*
* @throws {RangeError} If `percentage` is out of range.
*/
function assertPercentage(percentage) {
assert(isPercentage(percentage), 'You must provide a numeric value between 0 and 100.', RangeError);
}
/**
* Asserts an array of ratios is valid.
* @ignore
*
* @param {} ratios - The ratios to test.
*
* @throws {TypeError} If `ratios` are invalid.
*/
function assertValidRatios(ratios) {
assert(areValidRatios(ratios), 'You must provide a non-empty array of numeric values greater than 0.', TypeError);
}
/**
* Asserts a value is an integer.
* @ignore
*
* @param {} number - The value to test.
*
* @throws {TypeError}
*/
function assertInteger(number) {
assert(Number.isInteger(number), 'You must provide an integer.', TypeError);
}
var calculator$1 = Calculator();
/**
* A Dinero object is an immutable data structure representing a specific monetary value.
* It comes with methods for creating, parsing, manipulating, testing, transforming and formatting them.
*
* A Dinero object has:
*
* * An `amount`, expressed in minor currency units, as an integer.
* * A `currency`, expressed as an {@link https://en.wikipedia.org/wiki/ISO_4217#Active_codes ISO 4217 currency code}.
* * A `precision`, expressed as an integer, to represent the number of decimal places in the `amount`.
* This is helpful when you want to represent fractional minor currency units (e.g.: $10.4545).
* You can also use it to represent a currency with a different [exponent](https://en.wikipedia.org/wiki/ISO_4217#Treatment_of_minor_currency_units_.28the_.22exponent.22.29) than `2` (e.g.: Iraqi dinar with 1000 fils in 1 dinar (exponent of `3`), Japanese yen with no sub-units (exponent of `0`)).
* * An optional `locale` property that affects how output strings are formatted.
*
* Here's an overview of the public API:
*
* * **Access:** {@link module:Dinero~getAmount getAmount}, {@link module:Dinero~getCurrency getCurrency}, {@link module:Dinero~getLocale getLocale} and {@link module:Dinero~getPrecision getPrecision}.
* * **Manipulation:** {@link module:Dinero~add add}, {@link module:Dinero~subtract subtract}, {@link module:Dinero~multiply multiply}, {@link module:Dinero~divide divide}, {@link module:Dinero~percentage percentage}, {@link module:Dinero~allocate allocate} and {@link module:Dinero~convert convert}.
* * **Testing:** {@link module:Dinero~equalsTo equalsTo}, {@link module:Dinero~lessThan lessThan}, {@link module:Dinero~lessThanOrEqual lessThanOrEqual}, {@link module:Dinero~greaterThan greaterThan}, {@link module:Dinero~greaterThanOrEqual greaterThanOrEqual}, {@link module:Dinero~isZero isZero}, {@link module:Dinero~isPositive isPositive}, {@link module:Dinero~isNegative isNegative}, {@link module:Dinero~hasSubUnits hasSubUnits}, {@link module:Dinero~hasSameCurrency hasSameCurrency} and {@link module:Dinero~hasSameAmount hasSameAmount}.
* * **Configuration:** {@link module:Dinero~setLocale setLocale}.
* * **Conversion & formatting:** {@link module:Dinero~toFormat toFormat}, {@link module:Dinero~toUnit toUnit}, {@link module:Dinero~toRoundedUnit toRoundedUnit}, {@link module:Dinero~toObject toObject}, {@link module:Dinero~toJSON toJSON}, {@link module:Dinero~convertPrecision convertPrecision} and {@link module:Dinero.normalizePrecision normalizePrecision}.
*
* Dinero.js uses `number`s under the hood, so it's constrained by the [double-precision floating-point format](https://en.wikipedia.org/wiki/Double-precision_floating-point_format). Using values over [`Number.MAX_SAFE_INTEGER`](https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Objets_globaux/Number/MAX_SAFE_INTEGER) or below [`Number.MIN_SAFE_INTEGER`](https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Objets_globaux/Number/MIN_SAFE_INTEGER) will yield unpredictable results.
* Same goes with performing calculations: once the internal `amount` value exceeds those limits, precision is no longer guaranteed.
*
* @module Dinero
* @param {Number} [options.amount=0] - The amount in minor currency units (as an integer).
* @param {String} [options.currency='USD'] - An ISO 4217 currency code.
* @param {String} [options.precision=2] - The number of decimal places to represent.
*
* @throws {TypeError} If `amount` or `precision` is invalid. Integers over [`Number.MAX_SAFE_INTEGER`](https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Objets_globaux/Number/MAX_SAFE_INTEGER) or below [`Number.MIN_SAFE_INTEGER`](https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Objets_globaux/Number/MIN_SAFE_INTEGER) are considered valid, even though they can lead to imprecise amounts.
*
* @return {Object}
*/
var Dinero = function Dinero(options) {
var _Object$assign = Object.assign({}, {
amount: Dinero.defaultAmount,
currency: Dinero.defaultCurrency,
precision: Dinero.defaultPrecision
}, options),
amount = _Object$assign.amount,
currency = _Object$assign.currency,
precision = _Object$assign.precision;
assertInteger(amount);
assertInteger(precision);
var globalLocale = Dinero.globalLocale,
globalFormat = Dinero.globalFormat,
globalRoundingMode = Dinero.globalRoundingMode,
globalFormatRoundingMode = Dinero.globalFormatRoundingMode;
var globalExchangeRatesApi = Object.assign({}, Dinero.globalExchangeRatesApi);
/**
* Uses ES5 function notation so `this` can be passed through call, apply and bind
* @ignore
*/
var create = function create(options) {
var obj = Object.assign({}, Object.assign({}, {
amount: amount,
currency: currency,
precision: precision
}, options), Object.assign({}, {
locale: this.locale
}, options));
return Object.assign(Dinero({
amount: obj.amount,
currency: obj.currency,
precision: obj.precision
}), {
locale: obj.locale
});
};
/**
* Uses ES5 function notation so `this` can be passed through call, apply and bind
* @ignore
*/
var assertSameCurrency = function assertSameCurrency(comparator) {
assert(this.hasSameCurrency(comparator), 'You must provide a Dinero instance with the same currency.', TypeError);
};
return {
/**
* Returns the amount.
*
* @example
* // returns 500
* Dinero({ amount: 500 }).getAmount()
*
* @return {Number}
*/
getAmount: function getAmount() {
return amount;
},
/**
* Returns the currency.
*
* @example
* // returns 'EUR'
* Dinero({ currency: 'EUR' }).getCurrency()
*
* @return {String}
*/
getCurrency: function getCurrency() {
return currency;
},
/**
* Returns the locale.
*
* @example
* // returns 'fr-FR'
* Dinero().setLocale('fr-FR').getLocale()
*
* @return {String}
*/
getLocale: function getLocale() {
return this.locale || globalLocale;
},
/**
* Returns a new Dinero object with an embedded locale.
*
* @param {String} newLocale - The new locale as an {@link http://tools.ietf.org/html/rfc5646 BCP 47 language tag}.
*
* @example
* // Returns a Dinero object with locale 'ja-JP'
* Dinero().setLocale('ja-JP')
*
* @return {Dinero}
*/
setLocale: function setLocale(newLocale) {
return create.call(this, {
locale: newLocale
});
},
/**
* Returns the precision.
*
* @example
* // returns 3
* Dinero({ precision: 3 }).getPrecision()
*
* @return {Number}
*/
getPrecision: function getPrecision() {
return precision;
},
/**
* Returns a new Dinero object with a new precision and a converted amount.
*
* By default, fractional minor currency units are rounded using the **half to even** rule ([banker's rounding](http://wiki.c2.com/?BankersRounding)).
* This can be necessary when you need to convert objects to a smaller precision.
*
* Rounding *can* lead to accuracy issues as you chain many times. Consider a minimal amount of subsequent conversions for safer results.
* You can also specify a different `roundingMode` to better fit your needs.
*
* @param {Number} newPrecision - The new precision.
* @param {String} [roundingMode='HALF_EVEN'] - The rounding mode to use: `'HALF_ODD'`, `'HALF_EVEN'`, `'HALF_UP'`, `'HALF_DOWN'`, `'HALF_TOWARDS_ZERO'`, `'HALF_AWAY_FROM_ZERO'` or `'DOWN'`.
*
* @example
* // Returns a Dinero object with precision 3 and amount 1000
* Dinero({ amount: 100, precision: 2 }).convertPrecision(3)
*
* @throws {TypeError} If `newPrecision` is invalid.
*
* @return {Dinero}
*/
convertPrecision: function convertPrecision(newPrecision) {
var roundingMode = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : globalFormatRoundingMode;
assertInteger(newPrecision);
return create.call(this, {
amount: calculator$1.round(calculator$1.multiply(this.getAmount(), Math.pow(10, calculator$1.subtract(newPrecision, this.getPrecision()))), roundingMode),
precision: newPrecision
});
},
/**
* Returns a new Dinero object that represents the sum of this and an other Dinero object.
*
* If Dinero objects have a different `precision`, they will be first converted to the highest.
*
* @param {Dinero} addend - The Dinero object to add.
*
* @example
* // returns a Dinero object with amount 600
* Dinero({ amount: 400 }).add(Dinero({ amount: 200 }))
* @example
* // returns a Dinero object with amount 144545 and precision 4
* Dinero({ amount: 400 }).add(Dinero({ amount: 104545, precision: 4 }))
*
* @throws {TypeError} If `addend` has a different currency.
*
* @return {Dinero}
*/
add: function add(addend) {
assertSameCurrency.call(this, addend);
var addends = Dinero.normalizePrecision([this, addend]);
return create.call(this, {
amount: calculator$1.add(addends[0].getAmount(), addends[1].getAmount()),
precision: addends[0].getPrecision()
});
},
/**
* Returns a new Dinero object that represents the difference of this and an other Dinero object.
*
* If Dinero objects have a different `precision`, they will be first converted to the highest.
*
* @param {Dinero} subtrahend - The Dinero object to subtract.
*
* @example
* // returns a Dinero object with amount 200
* Dinero({ amount: 400 }).subtract(Dinero({ amount: 200 }))
* @example
* // returns a Dinero object with amount 64545 and precision 4
* Dinero({ amount: 104545, precision: 4 }).subtract(Dinero({ amount: 400 }))
*
* @throws {TypeError} If `subtrahend` has a different currency.
*
* @return {Dinero}
*/
subtract: function subtract(subtrahend) {
assertSameCurrency.call(this, subtrahend);
var subtrahends = Dinero.normalizePrecision([this, subtrahend]);
return create.call(this, {
amount: calculator$1.subtract(subtrahends[0].getAmount(), subtrahends[1].getAmount()),
precision: subtrahends[0].getPrecision()
});
},
/**
* Returns a new Dinero object that represents the multiplied value by the given factor.
*
* By default, fractional minor currency units are rounded using the **half to even** rule ([banker's rounding](http://wiki.c2.com/?BankersRounding)).
*
* Rounding *can* lead to accuracy issues as you chain many times. Consider a minimal amount of subsequent calculations for safer results.
* You can also specify a different `roundingMode` to better fit your needs.
*
* @param {Number} multiplier - The factor to multiply by.
* @param {String} [roundingMode='HALF_EVEN'] - The rounding mode to use: `'HALF_ODD'`, `'HALF_EVEN'`, `'HALF_UP'`, `'HALF_DOWN'`, `'HALF_TOWARDS_ZERO'`, `'HALF_AWAY_FROM_ZERO'` or `'DOWN'`.
*
* @example
* // returns a Dinero object with amount 1600
* Dinero({ amount: 400 }).multiply(4)
* @example
* // returns a Dinero object with amount 800
* Dinero({ amount: 400 }).multiply(2.001)
* @example
* // returns a Dinero object with amount 801
* Dinero({ amount: 400 }).multiply(2.00125, 'HALF_UP')
*
* @return {Dinero}
*/
multiply: function multiply(multiplier) {
var roundingMode = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : globalRoundingMode;
return create.call(this, {
amount: calculator$1.round(calculator$1.multiply(this.getAmount(), multiplier), roundingMode)
});
},
/**
* Returns a new Dinero object that represents the divided value by the given factor.
*
* By default, fractional minor currency units are rounded using the **half to even** rule ([banker's rounding](http://wiki.c2.com/?BankersRounding)).
*
* Rounding *can* lead to accuracy issues as you chain many times. Consider a minimal amount of subsequent calculations for safer results.
* You can also specify a different `roundingMode` to better fit your needs.
*
* As rounding is applied, precision may be lost in the process. If you want to accurately split a Dinero object, use {@link module:Dinero~allocate allocate} instead.
*
* @param {Number} divisor - The factor to divide by.
* @param {String} [roundingMode='HALF_EVEN'] - The rounding mode to use: `'HALF_ODD'`, `'HALF_EVEN'`, `'HALF_UP'`, `'HALF_DOWN'`, `'HALF_TOWARDS_ZERO'`, `'HALF_AWAY_FROM_ZERO'` or `'DOWN'`.
*
* @example
* // returns a Dinero object with amount 100
* Dinero({ amount: 400 }).divide(4)
* @example
* // returns a Dinero object with amount 52
* Dinero({ amount: 105 }).divide(2)
* @example
* // returns a Dinero object with amount 53
* Dinero({ amount: 105 }).divide(2, 'HALF_UP')
*
* @return {Dinero}
*/
divide: function divide(divisor) {
var roundingMode = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : globalRoundingMode;
return create.call(this, {
amount: calculator$1.round(calculator$1.divide(this.getAmount(), divisor), roundingMode)
});
},
/**
* Returns a new Dinero object that represents a percentage of this.
*
* As rounding is applied, precision may be lost in the process. If you want to accurately split a Dinero object, use {@link module:Dinero~allocate allocate} instead.
*
* @param {Number} percentage - The percentage to extract (between 0 and 100).
*
* @example
* // returns a Dinero object with amount 5000
* Dinero({ amount: 10000 }).percentage(50)
*
* @throws {RangeError} If `percentage` is out of range.
*
* @return {Dinero}
*/
percentage: function percentage(_percentage) {
assertPercentage(_percentage);
return this.multiply(calculator$1.divide(_percentage, 100));
},
/**
* Allocates the amount of a Dinero object according to a list of ratios.
*
* Sometimes you need to split monetary values but percentages can't cut it without adding or losing pennies.
* A good example is invoicing: let's say you need to bill $1,000.03 and you want a 50% downpayment.
* If you use {@link module:Dinero~percentage percentage}, you'll get an accurate Dinero object but the amount won't be billable: you can't split a penny.
* If you round it, you'll bill a penny extra.
* With {@link module:Dinero~allocate allocate}, you can split a monetary amount then distribute the remainder as evenly as possible.
*
* You can use percentage style or ratio style for `ratios`: `[25, 75]` and `[1, 3]` will do the same thing.
*
* Since v1.8.0, you can use zero ratios (such as [0, 50, 50]). If there's a remainder to distribute, zero ratios are skipped and return a Dinero object with amount zero.
*
* @param {Number[]} ratios - The ratios to allocate the money to.
*
* @example
* // returns an array of two Dinero objects
* // the first one with an amount of 502
* // the second one with an amount of 501
* Dinero({ amount: 1003 }).allocate([50, 50])
* @example
* // returns an array of two Dinero objects
* // the first one with an amount of 25
* // the second one with an amount of 75
* Dinero({ amount: 100 }).allocate([1, 3])
* @example
* // since version 1.8.0
* // returns an array of three Dinero objects
* // the first one with an amount of 0
* // the second one with an amount of 502
* // the third one with an amount of 501
* Dinero({ amount: 1003 }).allocate([0, 50, 50])
*
* @throws {TypeError} If ratios are invalid.
*
* @return {Dinero[]}
*/
allocate: function allocate(ratios) {
var _this = this;
assertValidRatios(ratios);
var total = ratios.reduce(function (a, b) {
return calculator$1.add(a, b);
});
var remainder = this.getAmount();
var shares = ratios.map(function (ratio) {
var share = Math.floor(calculator$1.divide(calculator$1.multiply(_this.getAmount(), ratio), total));
remainder = calculator$1.subtract(remainder, share);
return create.call(_this, {
amount: share
});
});
var i = 0;
while (remainder > 0) {
if (ratios[i] > 0) {
shares[i] = shares[i].add(create.call(this, {
amount: 1
}));
remainder = calculator$1.subtract(remainder, 1);
}
i += 1;
}
return shares;
},
/**
* Returns a Promise containing a new Dinero object converted to another currency.
*
* You have two options to provide the exchange rates:
*
* 1. **Use an exchange rate REST API, and let Dinero handle the fetching and conversion.**
* This is a simple option if you have access to an exchange rate REST API and want Dinero to do the rest.
* 2. **Fetch the exchange rates on your own and provide them directly.**
* This is useful if you're fetching your rates from somewhere else (a file, a database), use a different protocol or query language than REST (SOAP, GraphQL) or want to fetch rates once and cache them instead of making new requests every time.
*
* **If you want to use a REST API**, you must provide a third-party endpoint yourself. Dinero doesn't come bundled with an exchange rates endpoint.
*
* Here are some exchange rate APIs you can use:
*
* * [Fixer](https://fixer.io)
* * [Open Exchange Rates](https://openexchangerates.org)
* * [Coinbase](https://api.coinbase.com/v2/exchange-rates)
* * More [foreign](https://github.com/toddmotto/public-apis#currency-exchange) and [crypto](https://github.com/toddmotto/public-apis#cryptocurrency) exchange rate APIs.
*
* **If you want to fetch your own rates and provide them directly**, you need to pass a promise that resolves to the exchanges rates.
*
* In both cases, you need to specify at least:
*
* * a **destination currency**: the currency in which you want to convert your Dinero object. You can specify it with `currency`.
* * an **endpoint**: the API URL to query exchange rates, with parameters, or a promise that resolves to the exchange rates. You can specify it with `options.endpoint`.
* * a **property path**: the path to access the wanted rate in your API's JSON response (or the custom promise's payload). For example, with a response of:
* ```json
* {
* "data": {
* "base": "USD",
* "destination": "EUR",
* "rate": "0.827728919"
* }
* }
* ```
* Then the property path is `'data.rate'`. You can specify it with `options.propertyPath`.
*
* The base currency (the one of your Dinero object) and the destination currency can be used as "merge tags" with the mustache syntax, respectively `{{from}}` and `{{to}}`.
* You can use these tags to refer to these values in `options.endpoint` and `options.propertyPath`.
*
* For example, if you need to specify the base currency as a query parameter, you can do the following:
*
* ```js
* {
* endpoint: 'https://yourexchangerates.api/latest?base={{from}}'
* }
* ```
*
* @param {String} currency - The destination currency, expressed as an {@link https://en.wikipedia.org/wiki/ISO_4217#Active_codes ISO 4217 currency code}.
* @param {(String|Promise)} options.endpoint - The API endpoint to retrieve exchange rates. You can substitute this with a promise that resolves to the exchanges rates if you already have them.
* @param {String} [options.propertyPath='rates.{{to}}'] - The property path to the rate.
* @param {Object} [options.headers] - The HTTP headers to provide, if needed.
* @param {String} [options.roundingMode='HALF_EVEN'] - The rounding mode to use: `'HALF_ODD'`, `'HALF_EVEN'`, `'HALF_UP'`, `'HALF_DOWN'`, `'HALF_TOWARDS_ZERO'`, `'HALF_AWAY_FROM_ZERO'` or `'DOWN'`.
*
* @example
* // your global API parameters
* Dinero.globalExchangeRatesApi = { ... }
*
* // returns a Promise containing a Dinero object with the destination currency
* // and the initial amount converted to the new currency.
* Dinero({ amount: 500 }).convert('EUR')
* @example
* // returns a Promise containing a Dinero object,
* // with specific API parameters and rounding mode for this specific instance.
* Dinero({ amount: 500 })
* .convert('XBT', {
* endpoint: 'https://yourexchangerates.api/latest?base={{from}}',
* propertyPath: 'data.rates.{{to}}',
* headers: {
* 'user-key': 'xxxxxxxxx'
* },
* roundingMode: 'HALF_UP'
* })
* @example
* // usage with exchange rates provided as a custom promise
* // using the default `propertyPath` format (so it doesn't have to be specified)
* const rates = {
* rates: {
* EUR: 0.81162
* }
* }
*
* Dinero({ amount: 500 })
* .convert('EUR', {
* endpoint: new Promise(resolve => resolve(rates))
* })
* @example
* // usage with Promise.prototype.then and Promise.prototype.catch
* Dinero({ amount: 500 })
* .convert('EUR')
* .then(dinero => {
* dinero.getCurrency() // returns 'EUR'
* })
* .catch(err => {
* // handle errors
* })
* @example
* // usage with async/await
* (async () => {
* const price = await Dinero({ amount: 500 }).convert('EUR')
* price.getCurrency() // returns 'EUR'
* })()
*
* @return {Promise}
*/
convert: function convert(currency) {
var _this2 = this;
var _ref = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {},
_ref$endpoint = _ref.endpoint,
endpoint = _ref$endpoint === void 0 ? globalExchangeRatesApi.endpoint : _ref$endpoint,
_ref$propertyPath = _ref.propertyPath,
propertyPath = _ref$propertyPath === void 0 ? globalExchangeRatesApi.propertyPath || 'rates.{{to}}' : _ref$propertyPath,
_ref$headers = _ref.headers,
headers = _ref$headers === void 0 ? globalExchangeRatesApi.headers : _ref$headers,
_ref$roundingMode = _ref.roundingMode,
roundingMode = _ref$roundingMode === void 0 ? globalRoundingMode : _ref$roundingMode;
var options = Object.assign({}, {
endpoint: endpoint,
propertyPath: propertyPath,
headers: headers,
roundingMode: roundingMode
});
return CurrencyConverter(options).getExchangeRate(this.getCurrency(), currency).then(function (rate) {
assert(!isUndefined(rate), "No rate was found for the destination currency \"".concat(currency, "\"."), TypeError);
return create.call(_this2, {
amount: calculator$1.round(calculator$1.multiply(_this2.getAmount(), parseFloat(rate)), options.roundingMode),
currency: currency
});
});
},
/**
* Checks whether the value represented by this object equals to the other.
*
* @param {Dinero} comparator - The Dinero object to compare to.
*
* @example
* // returns true
* Dinero({ amount: 500, currency: 'EUR' }).equalsTo(Dinero({ amount: 500, currency: 'EUR' }))
* @example
* // returns false
* Dinero({ amount: 500, currency: 'EUR' }).equalsTo(Dinero({ amount: 800, currency: 'EUR' }))
* @example
* // returns false
* Dinero({ amount: 500, currency: 'USD' }).equalsTo(Dinero({ amount: 500, currency: 'EUR' }))
* @example
* // returns false
* Dinero({ amount: 500, currency: 'USD' }).equalsTo(Dinero({ amount: 800, currency: 'EUR' }))
* @example
* // returns true
* Dinero({ amount: 1000, currency: 'EUR', precision: 2 }).equalsTo(Dinero({ amount: 10000, currency: 'EUR', precision: 3 }))
* @example
* // returns false
* Dinero({ amount: 10000, currency: 'EUR', precision: 2 }).equalsTo(Dinero({ amount: 10000, currency: 'EUR', precision: 3 }))
*
* @return {Boolean}
*/
equalsTo: function equalsTo(comparator) {
return this.hasSameAmount(comparator) && this.hasSameCurrency(comparator);
},
/**
* Checks whether the value represented by this object is less than the other.
*
* @param {Dinero} comparator - The Dinero object to compare to.
*
* @example
* // returns true
* Dinero({ amount: 500 }).lessThan(Dinero({ amount: 800 }))
* @example
* // returns false
* Dinero({ amount: 800 }).lessThan(Dinero({ amount: 500 }))
* @example
* // returns true
* Dinero({ amount: 5000, precision: 3 }).lessThan(Dinero({ amount: 800 }))
* @example
* // returns false
* Dinero({ amount: 800 }).lessThan(Dinero({ amount: 5000, precision: 3 }))
*
* @throws {TypeError} If `comparator` has a different currency.
*
* @return {Boolean}
*/
lessThan: function lessThan(comparator) {
assertSameCurrency.call(this, comparator);
var comparators = Dinero.normalizePrecision([this, comparator]);
return comparators[0].getAmount() < comparators[1].getAmount();
},
/**
* Checks whether the value represented by this object is less than or equal to the other.
*
* @param {Dinero} comparator - The Dinero object to compare to.
*
* @example
* // returns true
* Dinero({ amount: 500 }).lessThanOrEqual(Dinero({ amount: 800 }))
* @example
* // returns true
* Dinero({ amount: 500 }).lessThanOrEqual(Dinero({ amount: 500 }))
* @example
* // returns false
* Dinero({ amount: 500 }).lessThanOrEqual(Dinero({ amount: 300 }))
* @example
* // returns true
* Dinero({ amount: 5000, precision: 3 }).lessThanOrEqual(Dinero({ amount: 800 }))
* @example
* // returns true
* Dinero({ amount: 5000, precision: 3 }).lessThanOrEqual(Dinero({ amount: 500 }))
* @example
* // returns false
* Dinero({ amount: 800 }).lessThanOrEqual(Dinero({ amount: 5000, precision: 3 }))
*
* @throws {TypeError} If `comparator` has a different currency.
*
* @return {Boolean}
*/
lessThanOrEqual: function lessThanOrEqual(comparator) {
assertSameCurrency.call(this, comparator);
var comparators = Dinero.normalizePrecision([this, comparator]);
return comparators[0].getAmount() <= comparators[1].getAmount();
},
/**
* Checks whether the value represented by this object is greater than the other.
*
* @param {Dinero} comparator - The Dinero object to compare to.
*
* @example
* // returns false
* Dinero({ amount: 500 }).greaterThan(Dinero({ amount: 800 }))
* @example
* // returns true
* Dinero({ amount: 800 }).greaterThan(Dinero({ amount: 500 }))
* @example
* // returns true
* Dinero({