///|
/// Currency codes whose minor unit has no fractional part.
///
/// ISO 4217 gives these currencies an exponent of `0`, so a statement that
/// writes `TRNAMT>1500` means 1500 units, not 15.00. Treating them as
/// two-decimal currencies is the classic way an importer is off by a factor of
/// a hundred.
fn has_no_minor_unit(code : String) -> Bool {
match code {
"BIF"
| "CLP"
| "DJF"
| "GNF"
| "ISK"
| "JPY"
| "KMF"
| "KRW"
| "PYG"
| "RWF"
| "UGX"
| "UYI"
| "VND"
| "VUV"
| "XAF"
| "XOF"
| "XPF"
| "XAG"
| "XAU"
| "XBA"
| "XBB"
| "XBC"
| "XBD"
| "XDR"
| "XPD"
| "XPT"
| "XSU"
| "XTS"
| "XUA"
| "XXX" => true
_ => false
}
}
///|
/// Currency codes whose minor unit has three fractional digits.
fn has_three_minor_digits(code : String) -> Bool {
match code {
"BHD" | "IQD" | "JOD" | "KWD" | "LYD" | "OMR" | "TND" => true
_ => false
}
}
///|
/// Currency codes whose minor unit has four fractional digits.
fn has_four_minor_digits(code : String) -> Bool {
match code {
"CLF" | "UYW" => true
_ => false
}
}
///|
/// Currency codes whose minor unit has the usual two fractional digits.
///
/// The list is the ISO 4217 set that a retail bank statement can carry; a code
/// outside it is reported as unknown rather than assumed to be two digits,
/// because guessing a scale is how a hundred becomes one.
fn has_two_minor_digits(code : String) -> Bool {
match code {
"AED"
| "AFN"
| "ALL"
| "AMD"
| "ANG"
| "AOA"
| "ARS"
| "AUD"
| "AWG"
| "AZN"
| "BAM"
| "BBD"
| "BDT"
| "BGN"
| "BMD"
| "BND"
| "BOB"
| "BRL"
| "BSD"
| "BTN"
| "BWP"
| "BYN"
| "BZD"
| "CAD"
| "CDF"
| "CHE"
| "CHF"
| "CHW"
| "CNY"
| "COP"
| "COU"
| "CRC"
| "CUP"
| "CVE"
| "CZK"
| "DKK"
| "DOP"
| "DZD"
| "EGP"
| "ERN"
| "ETB"
| "EUR"
| "FJD"
| "FKP"
| "GBP"
| "GEL"
| "GHS"
| "GIP"
| "GMD"
| "GTQ"
| "GYD"
| "HKD"
| "HNL"
| "HTG"
| "HUF"
| "IDR"
| "ILS"
| "INR"
| "IRR"
| "JMD"
| "KES"
| "KGS"
| "KHR"
| "KPW"
| "KYD"
| "KZT"
| "LAK"
| "LBP"
| "LKR"
| "LRD"
| "LSL"
| "MAD"
| "MDL"
| "MGA"
| "MKD"
| "MMK"
| "MNT"
| "MOP"
| "MRU"
| "MUR"
| "MVR"
| "MWK"
| "MXN"
| "MYR"
| "MZN"
| "NAD"
| "NGN"
| "NIO"
| "NOK"
| "NPR"
| "NZD"
| "PAB"
| "PEN"
| "PGK"
| "PHP"
| "PKR"
| "PLN"
| "QAR"
| "RON"
| "RSD"
| "RUB"
| "SAR"
| "SBD"
| "SCR"
| "SDG"
| "SEK"
| "SGD"
| "SHP"
| "SLE"
| "SOS"
| "SRD"
| "SSP"
| "STN"
| "SVC"
| "SYP"
| "SZL"
| "THB"
| "TJS"
| "TMT"
| "TOP"
| "TRY"
| "TTD"
| "TWD"
| "TZS"
| "UAH"
| "USD"
| "UYU"
| "UZS"
| "VES"
| "WST"
| "XCD"
| "YER"
| "ZAR"
| "ZMW"
| "ZWG"
| "ZWL" => true
_ => false
}
}
///|
/// How many fractional digits the currency's minor unit uses.
///
/// `Some(2)` for most currencies, `Some(0)` for the yen, won and the other
/// no-fraction currencies, `Some(3)` for the dinar family and `Some(4)` for the
/// Chilean and Uruguayan unit-of-account codes. `None` means the code is not one
/// this library knows, and the caller must choose a scale itself rather than
/// have one guessed.
///
/// The argument is matched without regard to case, because a bank may write
/// `CURDEF>usd`.
pub fn minor_unit_scale(currency : String) -> Int? {
let code = currency.to_upper()
if has_no_minor_unit(code) {
Some(0)
} else if has_three_minor_digits(code) {
Some(3)
} else if has_four_minor_digits(code) {
Some(4)
} else if has_two_minor_digits(code) {
Some(2)
} else {
None
}
}
///|
/// Whether the currency code is one this library knows.
pub fn is_known_currency(currency : String) -> Bool {
minor_unit_scale(currency) is Some(_)
}
///|
/// The scale an amount falls back to when the document names no currency.
///
/// Two digits: it is what most currencies use, and the importer says so instead
/// of pretending the choice was read from the document.
pub fn default_minor_unit_scale() -> Int {
2
}
///|
/// Convert an amount using the scale the currency implies.
///
/// This is the conversion a ledger import usually wants: the document states
/// both the amount and the currency, so the scale does not have to be passed in
/// by hand. `None` means the amount is malformed, needs more fractional digits
/// than the currency has, overflows `Int64`, or names an unknown currency.
pub fn amount_in_currency(amount : String, currency : String) -> Int64? {
match minor_unit_scale(currency) {
Some(scale) => amount_to_minor_units(amount, scale)
None => None
}
}
///|
/// Render an integer count of minor units using the currency's scale.
pub fn amount_from_currency(minor : Int64, currency : String) -> String? {
match minor_unit_scale(currency) {
Some(scale) => minor_units_to_amount(minor, scale)
None => None
}
}
///|
/// The currency code as it should be written in a report: upper-cased, or the
/// fallback when the document declared nothing.
pub fn currency_label(currency : String) -> String {
if currency == "" {
"UNKNOWN"
} else {
currency.to_upper()
}
}
///|
/// The minor-unit name a report can print next to an amount, or the empty
/// string for an unknown currency.
pub fn minor_unit_name(currency : String) -> String {
match minor_unit_scale(currency) {
Some(0) => "unit"
Some(3) => "mill"
Some(4) => "unit"
Some(_) => "cent"
None => ""
}
}