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". immutable char[3] code; /// The number of digits after the decimal place that the currency supports. immutable ubyte fractionalDigits; /// The ISO 4217 numeric code for the currency. immutable ushort numericCode; 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 { immutable Currency currency; immutable long value; 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."); } }