0

I am getting a string value that represents a price. I want to convert it to another string with specific precision and format it according to a current device Locale.

Decimal(string: "123,4567", locale: NSLocale.current)?.formatted(.number.precision(.fractionLength(precision)))

This code works for the german language and the output is "123,45". But if I switch to English the output is "123.00". The problem is with the dot instead of the comma. Any ideas how to fix it and show the correct number "123.45"?

Artiom
  • 513
  • 1
  • 4
  • 15
  • well, looks like it is a better solution to use NSFormatter.numberStyle = .currency for this use-case – Artiom Mar 03 '22 at 20:06

2 Answers2

1

The locale used for the input string must match the format of the string, so if it is a German format then use a German locale

let locale = Locale(identifier: "de_DE")
let string = Decimal(string: "123,4567", locale: locale)?
    .formatted(.number.precision(.fractionLength(precision)))

This uses Locale.current (Swedish in this example) for the output

123,46

Since this is a price here is a currency example

let string = Decimal(string: "123,4567", locale: .current)?
    .formatted(.currency(code: "EUR")
        .precision(.fractionLength(2))
        .rounded(rule: .down)
        .locale(.current))

€123,45

Joakim Danielson
  • 43,251
  • 5
  • 22
  • 52
  • Thanks, I see. So first of all I need to get a proper string-price to be able to format it to a Local I need? If it is "123,4567" I basically can not format it to $ US format 1,123.45? – Artiom Mar 03 '22 at 20:36
  • @ArtiomLemiasheuski Yes you can (although your example doesn't match). You do it in two steps as I have shown in the answer. See the last example where I use a locale for the output, change from `.current` to the one you want. I used `.current` in the example because that is the locale the user has selected – Joakim Danielson Mar 03 '22 at 20:43
  • I also noticed that prices such as 0.2460 are represented as 0,0000. Do you have an idea how to fix it? Is NSNumberFormatter is a better way to work with the prices? – Artiom Mar 03 '22 at 20:48
  • My problem general problem is that I have to use the regional location of the device for formatting. I am getting the price as a string in the format "1298,99" and need to represent it in Germany to 1.298,99 € and in the USA to $1,298.99. So I can not hard code any value. – Artiom Mar 03 '22 at 20:52
  • Thanks for your help. I just need to use locale.currencyCode :) – Artiom Mar 03 '22 at 22:17
  • 1
    Ok, maybe I misunderstood you a little then. I thought the original string was always a specific currency but it is the users currency. – Joakim Danielson Mar 04 '22 at 06:32
1

If all you need is to format your decimal number to be displayed using a specific currency what you need is Decimal.FormatStyle.Currency. It will automatically discard the excess of precision length:

let valueString = "1234,5678"
let decimalLocale: Locale = .init(identifier: "de_DE")
let currencyLocale: Locale = .init(identifier: "en_US")  // or current (used US locale just for demonstration purposes)
if let decimal = Decimal(string: valueString, locale: decimalLocale),
    let currencyCode = currencyLocale.currencyCode {
    let currencyStyle: Decimal.FormatStyle.Currency = .init(code: currencyCode, locale: currencyLocale)
    let currencyFormatted = decimal.formatted(
        currencyStyle.rounded(rule: .towardZero)
    )
    print(currencyFormatted)  // "$1,234.56\n"
}

edit/update:

I usually don't like manipulating strings but if your source doesn't have a specific format where sometimes de decimal separator is a comma and sometimes a period you need to replace your string comma with a period before converting it to Decimal:

let valueString = "1234,5678"
let currencyLocale: Locale = .init(identifier: "en_US")  // or current (used US locale just for demonstration purposes)
if let decimal = Decimal(string: valueString.replacingOccurrences(of: ",", with: ".")),
    let currencyCode = currencyLocale.currencyCode {
    let currencyStyle: Decimal.FormatStyle.Currency = .init(code: currencyCode, locale: currencyLocale)
    let currencyFormatted = decimal.formatted(
        currencyStyle.rounded(rule: .towardZero)
    )
    print(currencyFormatted)  // "$1,234.56\n"
}
Leo Dabus
  • 229,809
  • 59
  • 489
  • 571
  • Thank you! It is almost what I need. The one problem is if the number is separated by a dot, instead of a comma ("1234,5678") the result in my case for en_US is $1,234.00. And same for en_DE, if it is separated by a dot("1234.5678") -> 1.234,00 € – Artiom Mar 03 '22 at 21:50
  • Can you show where is your value coming from? Is it coming from a text field? Check this post [How to input currency format on a text field (from right to left) using Swift?](https://stackoverflow.com/a/29783546/2303865) – Leo Dabus Mar 03 '22 at 21:52
  • sure, it is coming from a json file -> "avg_price": "0.0055" – Artiom Mar 03 '22 at 21:54
  • ok so you have no control over its format? Does your json contain the locale information? – Leo Dabus Mar 03 '22 at 21:54
  • unfortunately not :( – Artiom Mar 03 '22 at 21:55
  • and also no locale information in the json file. Only the price in a format of "123.45" – Artiom Mar 03 '22 at 22:02
  • @ArtiomLemiasheuski check my last edit – Leo Dabus Mar 03 '22 at 22:05