2024-09-19 19:12:23 +00:00
|
|
|
module util.money;
|
|
|
|
|
|
|
|
import std.traits : isSomeString, EnumMembers;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Basic information about a monetary currency, as defined by ISO 4217.
|
|
|
|
* https://en.wikipedia.org/wiki/ISO_4217
|
|
|
|
*/
|
|
|
|
struct Currency {
|
|
|
|
/// The common 3-character code for the currency, like "USD".
|
2024-09-27 15:35:08 +00:00
|
|
|
immutable char[3] code;
|
2024-09-19 19:12:23 +00:00
|
|
|
/// The number of digits after the decimal place that the currency supports.
|
2024-09-27 15:35:08 +00:00
|
|
|
immutable ubyte fractionalDigits;
|
2024-09-19 19:12:23 +00:00
|
|
|
/// The ISO 4217 numeric code for the currency.
|
2024-09-27 15:35:08 +00:00
|
|
|
immutable ushort numericCode;
|
2024-09-19 19:12:23 +00:00
|
|
|
|
|
|
|
static Currency ofCode(S)(S code) if (isSomeString!S) {
|
|
|
|
if (code.length != 3) {
|
|
|
|
throw new Exception("Invalid currency code: " ~ code);
|
|
|
|
}
|
|
|
|
static foreach (c; ALL_CURRENCIES) {
|
|
|
|
if (c.code == code) return c;
|
|
|
|
}
|
|
|
|
throw new Exception("Unknown currency code: " ~ code);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// An enumeration of all available currencies.
|
|
|
|
enum Currencies : Currency {
|
|
|
|
AUD = Currency("AUD", 2, 36),
|
|
|
|
USD = Currency("USD", 2, 840),
|
|
|
|
CAD = Currency("CAD", 2, 124),
|
|
|
|
GBP = Currency("GBP", 2, 826),
|
|
|
|
EUR = Currency("EUR", 2, 978),
|
|
|
|
CHF = Currency("CHF", 2, 756),
|
|
|
|
ZAR = Currency("ZAR", 2, 710),
|
|
|
|
JPY = Currency("JPY", 0, 392),
|
|
|
|
INR = Currency("INR", 2, 356)
|
|
|
|
}
|
|
|
|
|
|
|
|
immutable(Currency[]) ALL_CURRENCIES = cast(Currency[]) [EnumMembers!Currencies];
|
|
|
|
|
|
|
|
unittest {
|
|
|
|
assert(Currency.ofCode("USD") == Currencies.USD);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* A monetary value consisting of an integer value, and a currency. The value
|
|
|
|
* is interpreted as a multiple of the smallest denomination of the currency,
|
|
|
|
* so for example, with USD currency, a value of 123 indicates $1.23.
|
|
|
|
*/
|
|
|
|
struct MoneyValue {
|
2024-09-27 15:35:08 +00:00
|
|
|
immutable Currency currency;
|
|
|
|
immutable long value;
|
2024-09-19 19:12:23 +00:00
|
|
|
|
|
|
|
int opCmp(in MoneyValue other) const {
|
|
|
|
if (other.currency != this.currency) return 0;
|
|
|
|
if (this.value < other.value) return -1;
|
|
|
|
if (this.value > other.value) return 1;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
MoneyValue opBinary(string op)(in MoneyValue rhs) const {
|
|
|
|
if (rhs.currency != this.currency)
|
|
|
|
throw new Exception("Cannot perform binary operations on MoneyValues with different currencies.");
|
|
|
|
static if (op == "+") return MoneyValue(currency, this.value + rhs.value);
|
|
|
|
static if (op == "-") return MoneyValue(currency, this.value - rhs.value);
|
|
|
|
static assert(false, "Operator " ~ op ~ " is not supported.");
|
|
|
|
}
|
|
|
|
|
|
|
|
MoneyValue opBinary(string op)(int rhs) const {
|
|
|
|
static if (op == "+") return MoneyValue(currency, this.value + rhs);
|
|
|
|
static if (op == "-") return MoneyValue(currency, this.value - rhs);
|
|
|
|
static if (op == "*") return MoneyValue(currency, this.value * rhs);
|
|
|
|
static if (op == "/") return MoneyValue(currency, this.value / rhs);
|
|
|
|
static assert(false, "Operator " ~ op ~ " is not supported.");
|
|
|
|
}
|
|
|
|
|
|
|
|
MoneyValue opUnary(string op)() const {
|
|
|
|
static if (op == "-") return MoneyValue(currency, -this.value);
|
|
|
|
static assert(false, "Operator " ~ op ~ " is not supported.");
|
|
|
|
}
|
|
|
|
}
|