Exclude withdrawn currencies from the picker, keep precious metals
Currency.getAvailableCurrencies() (JVM/Android) and NSLocale.ISOCurrencyCodes (iOS/macOS) return every ISO 4217 code the platform has ever known about — withdrawn currencies like the Deutsche Mark and French Franc, and test/ placeholder codes (XTS, XXX), included. Measured on the JVM: 232 total codes, only 156 still assigned to a country. Filters the list down to currencies actually assigned to an ISO country today, which drops the withdrawn ones without needing a maintained exclusion list. Precious metals (XAU, XAG, XPD, XPT) are added back in since they're still actively traded despite not being tied to any country. Web is left unfiltered — there's no reliable country-to-currency API there. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QpjMKWGoiT5aJBzhxvXkwp
This commit is contained in:
@@ -5,9 +5,27 @@ import java.util.Locale
|
||||
|
||||
actual fun systemCurrencies(): List<CurrencyInfo> =
|
||||
Currency.getAvailableCurrencies()
|
||||
.filter { it.currencyCode in currentlyUsedCurrencyCodes() }
|
||||
.map { CurrencyInfo(it.currencyCode, it.getDisplayName(Locale.getDefault())) }
|
||||
.sortedBy { it.code }
|
||||
|
||||
/**
|
||||
* `Currency.getAvailableCurrencies()` includes every ISO 4217 code the JDK has ever known about —
|
||||
* historical currencies (Deutsche Mark, French Franc, ...) and test/placeholder codes (XTS, XXX)
|
||||
* included. The currency currently assigned to each ISO country is a reliable proxy for "still in
|
||||
* use" without needing a maintained exclusion list; precious metals are added back in since
|
||||
* they're actively traded but aren't tied to any country.
|
||||
*/
|
||||
@Suppress("DEPRECATION") // Locale(language, country) still works fine; Locale.of() needs newer Android API levels.
|
||||
private fun currentlyUsedCurrencyCodes(): Set<String> =
|
||||
Locale.getISOCountries().mapNotNullTo(mutableSetOf()) { country ->
|
||||
try {
|
||||
Currency.getInstance(Locale("", country))?.currencyCode
|
||||
} catch (e: IllegalArgumentException) {
|
||||
null
|
||||
}
|
||||
} + PRECIOUS_METAL_CURRENCY_CODES
|
||||
|
||||
actual fun localeCurrencyCode(): String? = try {
|
||||
Currency.getInstance(Locale.getDefault())?.currencyCode
|
||||
} catch (e: IllegalArgumentException) {
|
||||
|
||||
@@ -5,11 +5,27 @@ import platform.Foundation.*
|
||||
actual fun systemCurrencies(): List<CurrencyInfo> {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
val codes = NSLocale.Companion.ISOCurrencyCodes as List<String>
|
||||
val usedCodes = currentlyUsedCurrencyCodes()
|
||||
return codes
|
||||
.filter { it in usedCodes }
|
||||
.map { code -> CurrencyInfo(code, NSLocale.currentLocale.localizedStringForCurrencyCode(code) ?: code) }
|
||||
.sortedBy { it.code }
|
||||
}
|
||||
|
||||
/**
|
||||
* `NSLocale.ISOCurrencyCodes` includes withdrawn ISO 4217 codes (Deutsche Mark, French Franc,
|
||||
* ...) alongside currently-circulating ones. The currency each ISO country is assigned today is
|
||||
* a reliable proxy for "still in use" without needing a maintained exclusion list; precious
|
||||
* metals are added back in since they're actively traded but aren't tied to any country.
|
||||
*/
|
||||
private fun currentlyUsedCurrencyCodes(): Set<String> {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
val countryCodes = NSLocale.Companion.ISOCountryCodes as List<String>
|
||||
return countryCodes.mapNotNullTo(mutableSetOf()) { country ->
|
||||
NSLocale(localeIdentifier = "_$country").currencyCode
|
||||
} + PRECIOUS_METAL_CURRENCY_CODES
|
||||
}
|
||||
|
||||
actual fun localeCurrencyCode(): String? = NSLocale.currentLocale.currencyCode
|
||||
|
||||
actual fun currencyDecimalDigits(code: String): Int {
|
||||
|
||||
@@ -2,6 +2,14 @@ package xyz.tyiu.satsprice
|
||||
|
||||
data class CurrencyInfo(val code: String, val displayName: String)
|
||||
|
||||
/**
|
||||
* ISO 4217 codes for the precious metals actively traded today. These aren't tied to any
|
||||
* country, so a currently-used-currency filter derived from country/locale data (as the
|
||||
* Android/JVM/Apple [systemCurrencies] implementations do, to drop long-withdrawn currencies
|
||||
* like the Deutsche Mark) would otherwise exclude them too.
|
||||
*/
|
||||
val PRECIOUS_METAL_CURRENCY_CODES: Set<String> = setOf("XAU", "XAG", "XPD", "XPT")
|
||||
|
||||
/** All ISO 4217 currencies the current platform knows about, with localized display names. */
|
||||
expect fun systemCurrencies(): List<CurrencyInfo>
|
||||
|
||||
|
||||
@@ -5,9 +5,27 @@ import java.util.Locale
|
||||
|
||||
actual fun systemCurrencies(): List<CurrencyInfo> =
|
||||
Currency.getAvailableCurrencies()
|
||||
.filter { it.currencyCode in currentlyUsedCurrencyCodes() }
|
||||
.map { CurrencyInfo(it.currencyCode, it.getDisplayName(Locale.getDefault())) }
|
||||
.sortedBy { it.code }
|
||||
|
||||
/**
|
||||
* `Currency.getAvailableCurrencies()` includes every ISO 4217 code the JDK has ever known about —
|
||||
* historical currencies (Deutsche Mark, French Franc, ...) and test/placeholder codes (XTS, XXX)
|
||||
* included. The currency currently assigned to each ISO country is a reliable proxy for "still in
|
||||
* use" without needing a maintained exclusion list; precious metals are added back in since
|
||||
* they're actively traded but aren't tied to any country.
|
||||
*/
|
||||
@Suppress("DEPRECATION") // Locale(language, country) still works fine; Locale.of() needs newer Android API levels.
|
||||
private fun currentlyUsedCurrencyCodes(): Set<String> =
|
||||
Locale.getISOCountries().mapNotNullTo(mutableSetOf()) { country ->
|
||||
try {
|
||||
Currency.getInstance(Locale("", country))?.currencyCode
|
||||
} catch (e: IllegalArgumentException) {
|
||||
null
|
||||
}
|
||||
} + PRECIOUS_METAL_CURRENCY_CODES
|
||||
|
||||
actual fun localeCurrencyCode(): String? = try {
|
||||
Currency.getInstance(Locale.getDefault())?.currencyCode
|
||||
} catch (e: IllegalArgumentException) {
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
package xyz.tyiu.satsprice
|
||||
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertFalse
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
/**
|
||||
* `java.util.Currency.getAvailableCurrencies()` includes every ISO 4217 code the JDK has ever
|
||||
* known about — withdrawn currencies and test/placeholder codes included — so [systemCurrencies]
|
||||
* filters those out, while keeping precious metals, which are still actively traded.
|
||||
*/
|
||||
class SystemCurrenciesJvmTest {
|
||||
|
||||
@Test
|
||||
fun systemCurrencies_excludesWithdrawnAndNonCountryCodes() {
|
||||
val codes = systemCurrencies().map { it.code }.toSet()
|
||||
|
||||
assertFalse("DEM" in codes, "Deutsche Mark was withdrawn when Germany adopted the Euro")
|
||||
assertFalse("FRF" in codes, "French Franc was withdrawn when France adopted the Euro")
|
||||
assertFalse("XTS" in codes, "XTS is reserved for testing, not a real currency")
|
||||
assertFalse("XXX" in codes, "XXX represents 'no currency', not a real currency")
|
||||
|
||||
assertTrue("USD" in codes)
|
||||
assertTrue("EUR" in codes)
|
||||
assertTrue("ALL" in codes)
|
||||
assertTrue("XAU" in codes, "Gold is still actively traded even though it isn't tied to a country")
|
||||
assertTrue("XAG" in codes, "Silver is still actively traded even though it isn't tied to a country")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user