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).
* @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 5000
* Dinero({ amount: 10000 }).percentage(50)
* @example
* // returns a Dinero object with amount 29
* Dinero({ amount: 57 }).percentage(50, "HALF_ODD")
*
* @throws {RangeError} If `percentage` is out of range.
*
* @return {Dinero}
*/
percentage(percentage, roundingMode = globalRoundingMode) {
assertPercentage(percentage)
return this.multiply(calculator.divide(percentage, 100), roundingMode)
},
/**
* 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)
