3

Solved Thanks to Martin R this has been solved by having to put the locale first. So:

import Foundation
let formatter = MeasurementFormatter()
formatter.locale = Locale(identifier: "en_GB") // Set locale first
formatter.numberFormatter.minimumFractionDigits = 4 // Now set numberFormatter properties
formatter.numberFormatter.maximumFractionDigits = 4
let distanceInKm = 16.093 // ~10 mi
var measurement: Measurement<UnitLength> = Measurement(value: distanceInKm, unit: .kilometers)
let string = formatter.string(from: measurement) // 9.998 mi
assert(string == "9.9998 mi") // Passes

=================================

After several hours of confusion, I have found that setting the locale on a MeasurementFormatter instance voids any numberFormatter settings also set on it.

For example: 1. Run this:

import Foundation
let formatter = MeasurementFormatter()
formatter.numberFormatter.minimumFractionDigits = 4
formatter.numberFormatter.maximumFractionDigits = 4
let distanceInKm = 16.093 // ~10 mi
var measurement: Measurement<UnitLength> = Measurement(value: distanceInKm, unit: .kilometers)
let string = formatter.string(from: measurement) // 9.998 mi
assert(string == "9.9998 mi") // Passes
  1. Now add a locale to the MeasurementFormatter instance and create a string from the same distance
formatter.locale = Locale(identifier: "en_GB") // Set the locale
let string2 = formatter.string(from: measurement) // 10 mi
assert(string2 == "9.9998 mi") // FAILS: No longer formatted to 4 fractional digits

I would expect in the second case for the assertion still to work, getting a string formatted to 4 decimal places as set by the numberFormatter property. Instead it seems to be ignored.

I have tried setting the numberFormatter properties again after setting a locale, but that doesn't change anything, e.g.,

formatter.locale = Locale(identifier: "en_GB") // Set the locale
formatter.numberFormatter.minimumFractionDigits = 4 // set numberFormatter properties again
formatter.numberFormatter.maximumFractionDigits = 4
let string3 = formatter.string(from: measurement) // still 10 mi
assert(string3 == "9.9998 mi") // FAILS: Still not formatted as per numberFormatter options

I couldn't find this being reported on SO, and thought I'd put it here so that other people can find it rather than wasting time. I have opened a radar with apple (and noticed someone else did a similar one for units of time in 2017!!!). I'm still hoping I am missing something, and/or if anyone has any suggestions on how to still format the number using a locale (in my actual source I'm actually working with metric and imperial distances based on user's locale), that would be much appreciated.

  • It seems that you have to assign `formatter.locale` first, before setting any other properties. – Martin R Mar 15 '19 at 10:14
  • I tried that (see last code block) and no change? – Benjamin Hall Mar 15 '19 at 10:16
  • 1
    The only reliable way seems to be: Create a new formatter, assign the locale, then the other properties. – A (remotely) related behavior is “documented” here https://developer.apple.com/library/archive/qa/qa1480/_index.html for NSDateFormatter: *"you should **first** set the locale of the date formatter"* – Martin R Mar 15 '19 at 10:18
  • If you set the locale straight after initializing it and only afterwards set the `numberFormatter` properties, it works just fine. `let formatter = MeasurementFormatter() ; formatter.locale = Locale(identifier: "en_GB") // Set the locale ; formatter.numberFormatter.minimumFractionDigits = 4; formatter.numberFormatter.maximumFractionDigits = 4` produces the expected results. – Dávid Pásztor Mar 15 '19 at 10:19
  • Your last code block actually works if you assign a *different* locale ... – Martin R Mar 15 '19 at 10:25

1 Answers1

4

The only reliable way to use MeasurementFormatter with a custom locale seems to be:

  • Create a new formatter,
  • assign formatter.locale first,
  • then assign the other properties, such as the desired number of fraction digits,
  • use the new formatter.

A similar behavior is mentioned in Technical Q&A QA1480 for (NS)DateFormatter:

... you should first set the locale of the date formatter ...

Example:

let formatter = MeasurementFormatter()
formatter.locale = Locale(identifier: "de_DE")
formatter.numberFormatter.minimumFractionDigits = 4
formatter.numberFormatter.maximumFractionDigits = 4

let distanceInKm = 16.093
let measurement = Measurement(value: distanceInKm, unit: UnitLength.kilometers)
let string = formatter.string(from: measurement)

print(string) // 16,0930 km
Martin R
  • 529,903
  • 94
  • 1,240
  • 1,382