Is there a way to format currency using a compact version, for example instead of $1,500,000 show $1.5M (and similar to thousands, billions and trillions)?
For instance, numbers can be set in a compact form using number.notation:
let price = Decimal(1_500_000.59)
price.formatted(.number.notation(.compactName))
// "1.5M"
I don't see ready APIs that can do this for you unfortunately. You should be cautious about using your own logic to combine these parsers if your app needs to be highly localized. Note that if you dump the formatted currencies for different Locales, you will sometimes have leading symbols like "$" and other times letters. Here's a few samples:
Locale: af_NA
currencySymbol: $
numberFormatted: 1,5 m
currencyFormatted: $1,500,000.59
Locale: af_ZA
currencySymbol: R
numberFormatted: 1,5 m
currencyFormatted: R 1,500,000.59
Locale: agq_CM
currencySymbol: FCFA
numberFormatted: 1,5M
currencyFormatted: FCFA 1,500,001
I wouldn't be sure that mixing their compact number notation with the leading currency symbol would appear proper to native speakers. For instance,
$1,5 m
R 1,5 m
FCFA 1,5M
So this is not answer - just a note this problem may be more complicated than it seems.
I wouldn't be sure that mixing their compact number notation with the
leading currency symbol
It’s worse than that. Different locales use different orders for their currency symbols, even for the same currency symbol. For example:
let n = 1234.56
var fs = FloatingPointFormatStyle<Double>.Currency(code: "EUR")
fs.locale = Locale(identifier: "en_IE")
print(n.formatted(fs))
// €1,234.56
fs.locale = Locale(identifier: "fr_FR")
print(n.formatted(fs))
// 1 234,56 €
Note that this output contains two different things that look like normal spaces but aren’t (-:
enobat, I think you might be able to make this work by using the currency formatter to render to an attributed string, using the attributes to find the numeric component of the output, and replacing that with the number from your number formatter. That’ll probably be wrong somewhere — one thing I’ve learnt about internationalise is that there are no universal truths — but I think it’ll work in most cases.
Here’s the first part of that process:
let n = 1234.56
var fs = FloatingPointFormatStyle<Double>.Currency(code: "EUR")
fs.locale = Locale(identifier: "en_IE")
let fsa = fs.attributed
let s = n.formatted(fsa)
print(String(s.characters))
// €1,234.56
guard
let start = s.runs[\.numberPart].first(where: { $0.0 == .integer})?.1.lowerBound,
let end = s.runs[\.numberPart].first(where: { $0.0 == .fraction})?.1.upperBound
else {
fatalError()
}
let numberRange = s[start..<end]
print(String(numberRange.characters))
// 1,234.56
There's currently no way to do it. I think @eskimo 's answer is the most correct. It wouldn't work, however, if there are more than one number parts in the string, such as "123$.456". I don't think there is currently any locale that does that, but just something to watch out.
I went ahead and created an issue for swift-foundation.
Hi enobat, here's how I'd do it. I'm using the copyright symbol to signify constant or unit of measurement, and k# to signify multiplication by powers of thousand. We could also use m# for million, g# for billion, t# for trillion, and so on. But it's probably more sensible to restrict ourselves to k# for thousand.