Amount: add ruble and Yen, fix toString.

This commit is contained in:
Mike Hearn 2017-03-10 17:16:09 +01:00
parent 7cac55ca4a
commit 23fcbd284c
3 changed files with 37 additions and 13 deletions

View File

@ -27,7 +27,9 @@ fun commodity(code: String) = Commodity.getInstance(code)!!
@JvmField val GBP = currency("GBP") @JvmField val GBP = currency("GBP")
@JvmField val EUR = currency("EUR") @JvmField val EUR = currency("EUR")
@JvmField val CHF = currency("CHF") @JvmField val CHF = currency("CHF")
@JvmField val FCOJ = commodity("FCOJ") @JvmField val JPY = currency("JPY")
@JvmField val RUB = currency("RUB")
@JvmField val FCOJ = commodity("FCOJ") // Frozen concentrated orange juice, yum!
fun DOLLARS(amount: Int): Amount<Currency> = Amount(amount.toLong() * 100, USD) fun DOLLARS(amount: Int): Amount<Currency> = Amount(amount.toLong() * 100, USD)
fun DOLLARS(amount: Double): Amount<Currency> = Amount((amount * 100).toLong(), USD) fun DOLLARS(amount: Double): Amount<Currency> = Amount((amount * 100).toLong(), USD)

View File

@ -39,7 +39,7 @@ import java.util.*
data class Amount<T>(val quantity: Long, val token: T) : Comparable<Amount<T>> { data class Amount<T>(val quantity: Long, val token: T) : Comparable<Amount<T>> {
companion object { companion object {
/** /**
* Build an amount from a decimal representation. For example, with an input of "12.34" GBP, * Build a currency amount from a decimal representation. For example, with an input of "12.34" GBP,
* returns an amount with a quantity of "1234". * returns an amount with a quantity of "1234".
* *
* @see Amount<Currency>.toDecimal * @see Amount<Currency>.toDecimal
@ -52,7 +52,9 @@ data class Amount<T>(val quantity: Long, val token: T) : Comparable<Amount<T>> {
private val currencySymbols: Map<String, Currency> = mapOf( private val currencySymbols: Map<String, Currency> = mapOf(
"$" to USD, "$" to USD,
"£" to GBP, "£" to GBP,
"" to EUR "" to EUR,
"¥" to JPY,
"" to RUB
) )
private val currencyCodes: Map<String, Currency> by lazy { Currency.getAvailableCurrencies().map { it.currencyCode to it }.toMap() } private val currencyCodes: Map<String, Currency> by lazy { Currency.getAvailableCurrencies().map { it.currencyCode to it }.toMap() }
@ -68,10 +70,16 @@ data class Amount<T>(val quantity: Long, val token: T) : Comparable<Amount<T>> {
* - 5000 * - 5000
* *
* Note this method does NOT respect internationalisation rules: it ignores commas and uses . as the * Note this method does NOT respect internationalisation rules: it ignores commas and uses . as the
* decimal point separator, always. It also ignores the users locale: $ is special cased to be USD, * decimal point separator, always. It also ignores the users locale:
* £ is special cased to GBP and is special cased to Euro. Thus an input of $12 expecting some other *
* countries dollar will not work. Do your own parsing if you need correct handling of currency amounts * - $ is always USD,
* with locale-sensitive handling. * - £ is always GBP
* - is always the Euro
* - ¥ is always Japanese Yen.
* - is always the Russian ruble.
*
* Thus an input of $12 expecting some other countries dollar will not work. Do your own parsing if
* you need correct handling of currency amounts with locale-sensitive handling.
* *
* @throws IllegalArgumentException if the input string was not understood. * @throws IllegalArgumentException if the input string was not understood.
*/ */
@ -117,17 +125,17 @@ data class Amount<T>(val quantity: Long, val token: T) : Comparable<Amount<T>> {
constructor(quantity: BigInteger, token: T) : this(quantity.toLong(), token) constructor(quantity: BigInteger, token: T) : this(quantity.toLong(), token)
operator fun plus(other: Amount<T>): Amount<T> { operator fun plus(other: Amount<T>): Amount<T> {
checkCurrency(other) checkToken(other)
return Amount(Math.addExact(quantity, other.quantity), token) return Amount(Math.addExact(quantity, other.quantity), token)
} }
operator fun minus(other: Amount<T>): Amount<T> { operator fun minus(other: Amount<T>): Amount<T> {
checkCurrency(other) checkToken(other)
return Amount(Math.subtractExact(quantity, other.quantity), token) return Amount(Math.subtractExact(quantity, other.quantity), token)
} }
private fun checkCurrency(other: Amount<T>) { private fun checkToken(other: Amount<T>) {
require(other.token == token) { "Currency mismatch: ${other.token} vs $token" } require(other.token == token) { "Token mismatch: ${other.token} vs $token" }
} }
operator fun div(other: Long): Amount<T> = Amount(quantity / other, token) operator fun div(other: Long): Amount<T> = Amount(quantity / other, token)
@ -135,10 +143,16 @@ data class Amount<T>(val quantity: Long, val token: T) : Comparable<Amount<T>> {
operator fun div(other: Int): Amount<T> = Amount(quantity / other, token) operator fun div(other: Int): Amount<T> = Amount(quantity / other, token)
operator fun times(other: Int): Amount<T> = Amount(Math.multiplyExact(quantity, other.toLong()), token) operator fun times(other: Int): Amount<T> = Amount(Math.multiplyExact(quantity, other.toLong()), token)
override fun toString(): String = (BigDecimal(quantity).divide(BigDecimal(100))).setScale(2).toPlainString() + " " + token override fun toString(): String {
val bd = if (token is Currency)
BigDecimal(quantity).movePointLeft(token.defaultFractionDigits)
else
BigDecimal(quantity)
return bd.toPlainString() + " " + token
}
override fun compareTo(other: Amount<T>): Int { override fun compareTo(other: Amount<T>): Int {
checkCurrency(other) checkToken(other)
return quantity.compareTo(other.quantity) return quantity.compareTo(other.quantity)
} }
} }

View File

@ -29,6 +29,14 @@ class AmountTests {
assertEquals(Amount(1234L, GBP), Amount.parseCurrency("£12.34")) assertEquals(Amount(1234L, GBP), Amount.parseCurrency("£12.34"))
assertEquals(Amount(1200L, GBP), Amount.parseCurrency("£12")) assertEquals(Amount(1200L, GBP), Amount.parseCurrency("£12"))
assertEquals(Amount(1000L, USD), Amount.parseCurrency("$10")) assertEquals(Amount(1000L, USD), Amount.parseCurrency("$10"))
assertEquals(Amount(5000L, JPY), Amount.parseCurrency("¥5000"))
assertEquals(Amount(500000L, RUB), Amount.parseCurrency("₽5000"))
assertEquals(Amount(1500000000L, CHF), Amount.parseCurrency("15,000,000 CHF")) assertEquals(Amount(1500000000L, CHF), Amount.parseCurrency("15,000,000 CHF"))
} }
@Test
fun rendering() {
assertEquals("5000 JPY", Amount.parseCurrency("¥5000").toString())
assertEquals("50.12 USD", Amount.parseCurrency("$50.12").toString())
}
} }