UNPKG

dinero.js

Version:

An immutable library to create, calculate and format monetary values.

961 lines (946 loc) 38.2 kB
import { Defaults, Globals } from './services/settings' import Static from './services/static' import Format from './services/format' import Calculator from './services/calculator' import CurrencyConverter from './services/currency-converter' import { assert, assertPercentage, assertValidRatios, assertInteger } from './services/assert' import { isUndefined } from './services/helpers' const calculator = 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} */ const Dinero = options => { const { amount, currency, precision } = Object.assign( {}, { amount: Dinero.defaultAmount, currency: Dinero.defaultCurrency, precision: Dinero.defaultPrecision }, options ) assertInteger(amount) assertInteger(precision) const { globalLocale, globalFormat, globalRoundingMode, globalFormatRoundingMode } = Dinero const globalExchangeRatesApi = Object.assign( {}, Dinero.globalExchangeRatesApi ) /** * Uses ES5 function notation so `this` can be passed through call, apply and bind * @ignore */ const create = function(options) { const obj = Object.assign( {}, Object.assign({}, { amount, currency, 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 */ const assertSameCurrency = function(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() { return amount }, /** * Returns the currency. * * @example * // returns 'EUR' * Dinero({ currency: 'EUR' }).getCurrency() * * @return {String} */ getCurrency() { return currency }, /** * Returns the locale. * * @example * // returns 'fr-FR' * Dinero().setLocale('fr-FR').getLocale() * * @return {String} */ 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(newLocale) { return create.call(this, { locale: newLocale }) }, /** * Returns the precision. * * @example * // returns 3 * Dinero({ precision: 3 }).getPrecision() * * @return {Number} */ 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(newPrecision, roundingMode = globalFormatRoundingMode) { assertInteger(newPrecision) return create.call(this, { amount: calculator.round( calculator.multiply( this.getAmount(), Math.pow(10, calculator.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(addend) { assertSameCurrency.call(this, addend) const addends = Dinero.normalizePrecision([this, addend]) return create.call(this, { amount: calculator.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(subtrahend) { assertSameCurrency.call(this, subtrahend) const subtrahends = Dinero.normalizePrecision([this, subtrahend]) return create.call(this, { amount: calculator.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(multiplier, roundingMode = globalRoundingMode) { return create.call(this, { amount: calculator.round( calculator.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(divisor, roundingMode = globalRoundingMode) { return create.call(this, { amount: calculator.round( calculator.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(percentage) { assertPercentage(percentage) return this.multiply(calculator.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(ratios) { assertValidRatios(ratios) const total = ratios.reduce((a, b) => calculator.add(a, b)) let remainder = this.getAmount() const shares = ratios.map(ratio => { const share = Math.floor( calculator.divide(calculator.multiply(this.getAmount(), ratio), total) ) remainder = calculator.subtract(remainder, share) return create.call(this, { amount: share }) }) let i = 0 while (remainder > 0) { if (ratios[i] > 0) { shares[i] = shares[i].add(create.call(this, { amount: 1 })) remainder = calculator.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( currency, { endpoint = globalExchangeRatesApi.endpoint, propertyPath = globalExchangeRatesApi.propertyPath || 'rates.{{to}}', headers = globalExchangeRatesApi.headers, roundingMode = globalRoundingMode } = {} ) { const options = Object.assign( {}, { endpoint, propertyPath, headers, roundingMode } ) return CurrencyConverter(options) .getExchangeRate(this.getCurrency(), currency) .then(rate => { assert( !isUndefined(rate), `No rate was found for the destination currency "${currency}".`, TypeError ) return create.call(this, { amount: calculator.round( calculator.multiply(this.getAmount(), parseFloat(rate)), options.roundingMode ), 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(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(comparator) { assertSameCurrency.call(this, comparator) const 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(comparator) { assertSameCurrency.call(this, comparator) const 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({ amount: 800 }).greaterThan(Dinero({ amount: 5000, precision: 3 })) * @example * // returns false * Dinero({ amount: 5000, precision: 3 }).greaterThan(Dinero({ amount: 800 })) * * @throws {TypeError} If `comparator` has a different currency. * * @return {Boolean} */ greaterThan(comparator) { assertSameCurrency.call(this, comparator) const comparators = Dinero.normalizePrecision([this, comparator]) return comparators[0].getAmount() > comparators[1].getAmount() }, /** * Checks whether the value represented by this object is greater than or equal to the other. * * @param {Dinero} comparator - The Dinero object to compare to. * * @example * // returns true * Dinero({ amount: 500 }).greaterThanOrEqual(Dinero({ amount: 300 })) * @example * // returns true * Dinero({ amount: 500 }).greaterThanOrEqual(Dinero({ amount: 500 })) * @example * // returns false * Dinero({ amount: 500 }).greaterThanOrEqual(Dinero({ amount: 800 })) * @example * // returns true * Dinero({ amount: 800 }).greaterThanOrEqual(Dinero({ amount: 5000, precision: 3 })) * @example * // returns true * Dinero({ amount: 500 }).greaterThanOrEqual(Dinero({ amount: 5000, precision: 3 })) * @example * // returns false * Dinero({ amount: 5000, precision: 3 }).greaterThanOrEqual(Dinero({ amount: 800 })) * * @throws {TypeError} If `comparator` has a different currency. * * @return {Boolean} */ greaterThanOrEqual(comparator) { assertSameCurrency.call(this, comparator) const comparators = Dinero.normalizePrecision([this, comparator]) return comparators[0].getAmount() >= comparators[1].getAmount() }, /** * Checks if the value represented by this object is zero. * * @example * // returns true * Dinero({ amount: 0 }).isZero() * @example * // returns false * Dinero({ amount: 100 }).isZero() * * @return {Boolean} */ isZero() { return this.getAmount() === 0 }, /** * Checks if the value represented by this object is positive. * * @example * // returns false * Dinero({ amount: -10 }).isPositive() * @example * // returns true * Dinero({ amount: 10 }).isPositive() * @example * // returns true * Dinero({ amount: 0 }).isPositive() * * @return {Boolean} */ isPositive() { return this.getAmount() >= 0 }, /** * Checks if the value represented by this object is negative. * * @example * // returns true * Dinero({ amount: -10 }).isNegative() * @example * // returns false * Dinero({ amount: 10 }).isNegative() * @example * // returns false * Dinero({ amount: 0 }).isNegative() * * @return {Boolean} */ isNegative() { return this.getAmount() < 0 }, /** * Checks if this has minor currency units. * Deprecates {@link module:Dinero~hasCents hasCents}. * * @example * // returns false * Dinero({ amount: 1100 }).hasSubUnits() * @example * // returns true * Dinero({ amount: 1150 }).hasSubUnits() * * @return {Boolean} */ hasSubUnits() { return calculator.modulo(this.getAmount(), Math.pow(10, precision)) !== 0 }, /** * Checks if this has minor currency units. * * @deprecated since version 1.4.0, will be removed in 2.0.0 * Use {@link module:Dinero~hasSubUnits hasSubUnits} instead. * * @example * // returns false * Dinero({ amount: 1100 }).hasCents() * @example * // returns true * Dinero({ amount: 1150 }).hasCents() * * @return {Boolean} */ hasCents() { return calculator.modulo(this.getAmount(), Math.pow(10, precision)) !== 0 }, /** * Checks whether the currency represented by this object equals to the other. * * @param {Dinero} comparator - The Dinero object to compare to. * * @example * // returns true * Dinero({ amount: 2000, currency: 'EUR' }).hasSameCurrency(Dinero({ amount: 1000, currency: 'EUR' })) * @example * // returns false * Dinero({ amount: 1000, currency: 'EUR' }).hasSameCurrency(Dinero({ amount: 1000, currency: 'USD' })) * * @return {Boolean} */ hasSameCurrency(comparator) { return this.getCurrency() === comparator.getCurrency() }, /** * Checks whether the amount represented by this object equals to the other. * * @param {Dinero} comparator - The Dinero object to compare to. * * @example * // returns true * Dinero({ amount: 1000, currency: 'EUR' }).hasSameAmount(Dinero({ amount: 1000 })) * @example * // returns false * Dinero({ amount: 2000, currency: 'EUR' }).hasSameAmount(Dinero({ amount: 1000, currency: 'EUR' })) * @example * // returns true * Dinero({ amount: 1000, currency: 'EUR', precision: 2 }).hasSameAmount(Dinero({ amount: 10000, precision: 3 })) * @example * // returns false * Dinero({ amount: 10000, currency: 'EUR', precision: 2 }).hasSameAmount(Dinero({ amount: 10000, precision: 3 })) * * @return {Boolean} */ hasSameAmount(comparator) { const comparators = Dinero.normalizePrecision([this, comparator]) return comparators[0].getAmount() === comparators[1].getAmount() }, /** * Returns this object formatted as a string. * * The format is a mask which defines how the output string will be formatted. * It defines whether to display a currency, in what format, how many fraction digits to display and whether to use grouping separators. * The output is formatted according to the applying locale. * * Object | Format | String * :--------------------------- | :---------------- | :--- * `Dinero({ amount: 500050 })` | `'$0,0.00'` | $5,000.50 * `Dinero({ amount: 500050 })` | `'$0,0'` | $5,001 * `Dinero({ amount: 500050 })` | `'$0'` | $5001 * `Dinero({ amount: 500050 })` | `'$0.0'` | $5000.5 * `Dinero({ amount: 500050 })` | `'USD0,0.0'` | USD5,000.5 * `Dinero({ amount: 500050 })` | `'0,0.0 dollar'` | 5,000.5 dollars * * Don't try to substitute the `$` sign or the `USD` code with your target currency, nor adapt the format string to the exact format you want. * The format is a mask which defines a pattern and returns a valid, localized currency string. * If you want to display the object in a custom way, either use {@link module:Dinero~getAmount getAmount}, {@link module:Dinero~toUnit toUnit} or {@link module:Dinero~toRoundedUnit toRoundedUnit} and manipulate the output string as you wish. * * {@link module:Dinero~toFormat toFormat} wraps around `Number.prototype.toLocaleString`. For that reason, **format will vary depending on how it's implemented in the end user's environment**. * * You can also use `toLocaleString` directly: * `Dinero().toRoundedUnit(digits, roundingMode).toLocaleString(locale, options)`. * * By default, amounts are rounded using the **half away from zero** rule ([commercial rounding](https://en.wikipedia.org/wiki/Rounding#Round_half_away_from_zero)). * You can also specify a different `roundingMode` to better fit your needs. * * @param {String} [format='$0,0.00'] - The format mask to format to. * @param {String} [roundingMode='HALF_AWAY_FROM_ZERO'] - The rounding mode to use: `'HALF_ODD'`, `'HALF_EVEN'`, `'HALF_UP'`, `'HALF_DOWN'`, `'HALF_TOWARDS_ZERO'`, `'HALF_AWAY_FROM_ZERO'` or `'DOWN'`. * * @example * // returns $2,000 * Dinero({ amount: 200000 }).toFormat('$0,0') * @example * // returns €50.5 * Dinero({ amount: 5050, currency: 'EUR' }).toFormat('$0,0.0') * @example * // returns 100 euros * Dinero({ amount: 10000, currency: 'EUR' }).setLocale('fr-FR').toFormat('0,0 dollar') * @example * // returns 2000 * Dinero({ amount: 200000, currency: 'EUR' }).toFormat() * @example * // returns $10 * Dinero({ amount: 1050 }).toFormat('$0', 'HALF_EVEN') * * @return {String} */ toFormat(format = globalFormat, roundingMode = globalFormatRoundingMode) { const formatter = Format(format) return this.toRoundedUnit( formatter.getMinimumFractionDigits(), roundingMode ).toLocaleString(this.getLocale(), { currencyDisplay: formatter.getCurrencyDisplay(), useGrouping: formatter.getUseGrouping(), minimumFractionDigits: formatter.getMinimumFractionDigits(), style: formatter.getStyle(), currency: this.getCurrency() }) }, /** * Returns the amount represented by this object in units. * * @example * // returns 10.5 * Dinero({ amount: 1050 }).toUnit() * @example * // returns 10.545 * Dinero({ amount: 10545, precision: 3 }).toUnit() * * @return {Number} */ toUnit() { return calculator.divide(this.getAmount(), Math.pow(10, precision)) }, /** * Returns the amount represented by this object in rounded units. * * By default, the method uses the **half away from zero** rule ([commercial rounding](https://en.wikipedia.org/wiki/Rounding#Round_half_away_from_zero)). * You can also specify a different `roundingMode` to better fit your needs. * * @example * // returns 10.6 * Dinero({ amount: 1055 }).toRoundedUnit(1) * @example * // returns 10 * Dinero({ amount: 1050 }).toRoundedUnit(0, 'HALF_EVEN') * * @param {Number} digits - The number of fraction digits to round to. * @param {String} [roundingMode='HALF_AWAY_FROM_ZERO'] - The rounding mode to use: `'HALF_ODD'`, `'HALF_EVEN'`, `'HALF_UP'`, `'HALF_DOWN'`, `'HALF_TOWARDS_ZERO'`, `'HALF_AWAY_FROM_ZERO'` or `'DOWN'`. * * @return {Number} */ toRoundedUnit(digits, roundingMode = globalFormatRoundingMode) { const factor = Math.pow(10, digits) return calculator.divide( calculator.round( calculator.multiply(this.toUnit(), factor), roundingMode ), factor ) }, /** * Returns the object's data as an object literal. * * @example * // returns { amount: 500, currency: 'EUR', precision: 2 } * Dinero({ amount: 500, currency: 'EUR', precision: 2 }).toObject() * * @return {Object} */ toObject() { return { amount, currency, precision } }, /** * Returns the object's data as an object literal. * * Alias of {@link module:Dinero~toObject toObject}. * It is defined so that calling `JSON.stringify` on a Dinero object will automatically extract the relevant data. * * @example * // returns '{"amount":500,"currency":"EUR","precision":2}' * JSON.stringify(Dinero({ amount: 500, currency: 'EUR', precision: 2 })) * * @return {Object} */ toJSON() { return this.toObject() } } } export default Object.assign(Dinero, Defaults, Globals, Static)