Skip to content

Commit 3290d07

Browse files
committed
NumberFormatter: More fixes for numberStyles
- Fix rawValue for Style.{currencyISOCode,currencyPlural,currencyAccounting} - Fix minimumIntegerDigits for .currencyPlural - Fix usesSignificantDigits, minimumIntegerDigits and groupingSize for .percent (cherry picked from commit 3fef3a8)
1 parent dd172c2 commit 3290d07

File tree

2 files changed

+230
-34
lines changed

2 files changed

+230
-34
lines changed

Foundation/NumberFormatter.swift

Lines changed: 40 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -24,16 +24,16 @@ internal let kCFNumberFormatterCurrencyAccountingStyle = CFNumberFormatterStyle.
2424

2525
extension NumberFormatter {
2626
public enum Style : UInt {
27-
case none
28-
case decimal
29-
case currency
30-
case percent
31-
case scientific
32-
case spellOut
33-
case ordinal
34-
case currencyISOCode
35-
case currencyPlural
36-
case currencyAccounting
27+
case none = 0
28+
case decimal = 1
29+
case currency = 2
30+
case percent = 3
31+
case scientific = 4
32+
case spellOut = 5
33+
case ordinal = 6
34+
case currencyISOCode = 8 // 7 is not used
35+
case currencyPlural = 9
36+
case currencyAccounting = 10
3737
}
3838

3939
public enum PadPosition : UInt {
@@ -187,14 +187,25 @@ open class NumberFormatter : Formatter {
187187
case .none, .ordinal, .spellOut:
188188
_usesSignificantDigits = false
189189

190-
case .currency, .currencyPlural, .currencyISOCode, .currencyAccounting:
190+
case .currency, .currencyISOCode, .currencyAccounting:
191191
_usesSignificantDigits = false
192192
_usesGroupingSeparator = true
193193
if _minimumIntegerDigits == nil {
194194
_minimumIntegerDigits = 1
195195
}
196+
if _groupingSize == 0 {
197+
_groupingSize = 3
198+
}
196199
_minimumFractionDigits = 2
197-
200+
201+
case .currencyPlural:
202+
_usesSignificantDigits = false
203+
_usesGroupingSeparator = true
204+
if _minimumIntegerDigits == nil {
205+
_minimumIntegerDigits = 0
206+
}
207+
_minimumFractionDigits = 2
208+
198209
case .decimal:
199210
_usesGroupingSeparator = true
200211
_maximumFractionDigits = 3
@@ -205,9 +216,24 @@ open class NumberFormatter : Formatter {
205216
_groupingSize = 3
206217
}
207218

208-
default:
209-
_usesSignificantDigits = true
219+
case .percent:
220+
_usesSignificantDigits = false
210221
_usesGroupingSeparator = true
222+
if _minimumIntegerDigits == nil {
223+
_minimumIntegerDigits = 1
224+
}
225+
if _groupingSize == 0 {
226+
_groupingSize = 3
227+
}
228+
_minimumFractionDigits = 0
229+
_maximumFractionDigits = 0
230+
231+
case .scientific:
232+
_usesSignificantDigits = false
233+
_usesGroupingSeparator = false
234+
if _minimumIntegerDigits == nil {
235+
_minimumIntegerDigits = 0
236+
}
211237
}
212238
_reset()
213239
_numberStyle = newValue

TestFoundation/TestNumberFormatter.swift

Lines changed: 190 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,15 @@ class TestNumberFormatter: XCTestCase {
2424
("test_plusSignSymbol", test_plusSignSymbol),
2525
("test_currencySymbol", test_currencySymbol),
2626
("test_exponentSymbol", test_exponentSymbol),
27-
("test_minimumIntegerDigits", test_minimumIntegerDigits),
27+
("test_decimalMinimumIntegerDigits", test_decimalMinimumIntegerDigits),
28+
("test_currencyMinimumIntegerDigits", test_currencyMinimumIntegerDigits),
29+
("test_percentMinimumIntegerDigits", test_percentMinimumIntegerDigits),
30+
("test_scientificMinimumIntegerDigits", test_scientificMinimumIntegerDigits),
31+
("test_spellOutMinimumIntegerDigits", test_spellOutMinimumIntegerDigits),
32+
("test_ordinalMinimumIntegerDigits", test_ordinalMinimumIntegerDigits),
33+
("test_currencyPluralMinimumIntegerDigits", test_currencyPluralMinimumIntegerDigits),
34+
("test_currencyISOCodeMinimumIntegerDigits", test_currencyISOCodeMinimumIntegerDigits),
35+
("test_currencyAccountingMinimumIntegerDigits", test_currencyAccountingMinimumIntegerDigits),
2836
("test_maximumIntegerDigits", test_maximumIntegerDigits),
2937
("test_minimumFractionDigits", test_minimumFractionDigits),
3038
("test_maximumFractionDigits", test_maximumFractionDigits),
@@ -202,7 +210,7 @@ class TestNumberFormatter: XCTestCase {
202210
XCTAssertEqual(formattedString, "4.2⬆️1")
203211
}
204212

205-
func test_minimumIntegerDigits() {
213+
func test_decimalMinimumIntegerDigits() {
206214
let numberFormatter1 = NumberFormatter()
207215
XCTAssertEqual(numberFormatter1.minimumIntegerDigits, 0)
208216
numberFormatter1.minimumIntegerDigits = 3
@@ -221,30 +229,192 @@ class TestNumberFormatter: XCTestCase {
221229
XCTAssertEqual(numberFormatter.minimumIntegerDigits, 3)
222230
formattedString = numberFormatter.string(from: 0.1)
223231
XCTAssertEqual(formattedString, "000.1")
232+
}
224233

225-
numberFormatter.numberStyle = .currency
226-
XCTAssertEqual(numberFormatter.minimumIntegerDigits, 3)
234+
func test_currencyMinimumIntegerDigits() {
235+
// If .minimumIntegerDigits is set to 0 before .numberStyle change, preserve the value
236+
let formatter = NumberFormatter()
237+
XCTAssertEqual(formatter.minimumIntegerDigits, 0)
238+
formatter.minimumIntegerDigits = 0
239+
formatter.numberStyle = .currency
240+
XCTAssertEqual(formatter.minimumIntegerDigits, 0)
241+
formatter.locale = Locale(identifier: "en_US")
242+
XCTAssertEqual(formatter.string(from: 0), "$.00")
243+
XCTAssertEqual(formatter.string(from: 1.23), "$1.23")
244+
XCTAssertEqual(formatter.string(from: 123.4), "$123.40")
245+
246+
// If .minimumIntegerDigits is not set before .numberStyle change, update the value
247+
let formatter2 = NumberFormatter()
248+
XCTAssertEqual(formatter2.minimumIntegerDigits, 0)
249+
formatter2.numberStyle = .currency
250+
XCTAssertEqual(formatter2.minimumIntegerDigits, 1)
251+
formatter2.locale = Locale(identifier: "en_US")
252+
XCTAssertEqual(formatter2.string(from: 0.001), "$0.00")
253+
XCTAssertEqual(formatter2.string(from: 1.234), "$1.23")
254+
XCTAssertEqual(formatter2.string(from: 123456.7), "$123,456.70")
255+
}
227256

257+
func test_percentMinimumIntegerDigits() {
228258
// If .minimumIntegerDigits is set to 0 before .numberStyle change, preserve the value
229-
let currencyFormatter = NumberFormatter()
230-
XCTAssertEqual(currencyFormatter.minimumIntegerDigits, 0)
231-
currencyFormatter.minimumIntegerDigits = 0
232-
currencyFormatter.numberStyle = .currency
233-
XCTAssertEqual(currencyFormatter.minimumIntegerDigits, 0)
234-
currencyFormatter.locale = Locale(identifier: "en_US")
235-
formattedString = currencyFormatter.string(from: NSNumber(value: 0))
236-
XCTAssertEqual(formattedString, "$.00")
259+
let formatter = NumberFormatter()
260+
XCTAssertEqual(formatter.minimumIntegerDigits, 0)
261+
formatter.minimumIntegerDigits = 0
262+
formatter.numberStyle = .percent
263+
XCTAssertEqual(formatter.minimumIntegerDigits, 0)
264+
formatter.locale = Locale(identifier: "en_US")
265+
XCTAssertEqual(formatter.string(from: 0), "0%")
266+
XCTAssertEqual(formatter.string(from: 1.234), "123%")
267+
XCTAssertEqual(formatter.string(from: 123.4), "12,340%")
237268

238269
// If .minimumIntegerDigits is not set before .numberStyle change, update the value
239-
let currencyFormatter2 = NumberFormatter()
240-
XCTAssertEqual(currencyFormatter2.minimumIntegerDigits, 0)
241-
currencyFormatter2.numberStyle = .currency
242-
XCTAssertEqual(currencyFormatter2.minimumIntegerDigits, 1)
243-
currencyFormatter2.locale = Locale(identifier: "en_US")
244-
formattedString = currencyFormatter2.string(from: NSNumber(value: 0))
245-
XCTAssertEqual(formattedString, "$0.00")
270+
let formatter2 = NumberFormatter()
271+
XCTAssertEqual(formatter2.minimumIntegerDigits, 0)
272+
formatter2.numberStyle = .percent
273+
XCTAssertEqual(formatter2.minimumIntegerDigits, 1)
274+
formatter2.locale = Locale(identifier: "en_US")
275+
XCTAssertEqual(formatter2.string(from: 0.01), "1%")
276+
XCTAssertEqual(formatter2.string(from: 1.234), "123%")
277+
XCTAssertEqual(formatter2.string(from: 123456.7), "12,345,670%")
246278
}
247-
279+
280+
func test_scientificMinimumIntegerDigits() {
281+
// If .minimumIntegerDigits is set to 0 before .numberStyle change, preserve the value
282+
let formatter = NumberFormatter()
283+
XCTAssertEqual(formatter.minimumIntegerDigits, 0)
284+
formatter.minimumIntegerDigits = 0
285+
formatter.numberStyle = .scientific
286+
XCTAssertEqual(formatter.minimumIntegerDigits, 0)
287+
formatter.locale = Locale(identifier: "en_US")
288+
XCTAssertEqual(formatter.string(from: 0), "0E0")
289+
XCTAssertEqual(formatter.string(from: 1.23), "1.23E0")
290+
XCTAssertEqual(formatter.string(from: 123.4), "1.234E2")
291+
292+
// If .minimumIntegerDigits is not set before .numberStyle change, update the value
293+
let formatter2 = NumberFormatter()
294+
XCTAssertEqual(formatter2.minimumIntegerDigits, 0)
295+
formatter2.numberStyle = .scientific
296+
XCTAssertEqual(formatter2.minimumIntegerDigits, 0)
297+
formatter2.locale = Locale(identifier: "en_US")
298+
XCTAssertEqual(formatter2.string(from: 0.01), "1E-2")
299+
XCTAssertEqual(formatter2.string(from: 1.234), "1.234E0")
300+
XCTAssertEqual(formatter2.string(from: 123456.7), "1.234567E5")
301+
}
302+
303+
func test_spellOutMinimumIntegerDigits() {
304+
// If .minimumIntegerDigits is set to 0 before .numberStyle change, preserve the value
305+
let formatter = NumberFormatter()
306+
XCTAssertEqual(formatter.minimumIntegerDigits, 0)
307+
formatter.minimumIntegerDigits = 0
308+
formatter.numberStyle = .spellOut
309+
XCTAssertEqual(formatter.minimumIntegerDigits, 0)
310+
formatter.locale = Locale(identifier: "en_US")
311+
XCTAssertEqual(formatter.string(from: 0), "zero")
312+
XCTAssertEqual(formatter.string(from: 1.23), "one point two three")
313+
XCTAssertEqual(formatter.string(from: 123.4), "one hundred twenty-three point four")
314+
315+
// If .minimumIntegerDigits is not set before .numberStyle change, update the value
316+
let formatter2 = NumberFormatter()
317+
XCTAssertEqual(formatter2.minimumIntegerDigits, 0)
318+
formatter2.numberStyle = .spellOut
319+
XCTAssertEqual(formatter2.minimumIntegerDigits, 0)
320+
formatter2.locale = Locale(identifier: "en_US")
321+
XCTAssertEqual(formatter2.string(from: 0.01), "zero point zero one")
322+
XCTAssertEqual(formatter2.string(from: 1.234), "one point two three four")
323+
XCTAssertEqual(formatter2.string(from: 123456.7), "one hundred twenty-three thousand four hundred fifty-six point seven")
324+
}
325+
326+
func test_ordinalMinimumIntegerDigits() {
327+
// If .minimumIntegerDigits is set to 0 before .numberStyle change, preserve the value
328+
let formatter = NumberFormatter()
329+
XCTAssertEqual(formatter.minimumIntegerDigits, 0)
330+
formatter.minimumIntegerDigits = 0
331+
formatter.numberStyle = .ordinal
332+
XCTAssertEqual(formatter.minimumIntegerDigits, 0)
333+
formatter.locale = Locale(identifier: "en_US")
334+
XCTAssertEqual(formatter.string(from: 0), "0th")
335+
XCTAssertEqual(formatter.string(from: 1.23), "1st")
336+
XCTAssertEqual(formatter.string(from: 123.4), "123rd")
337+
338+
// If .minimumIntegerDigits is not set before .numberStyle change, update the value
339+
let formatter2 = NumberFormatter()
340+
XCTAssertEqual(formatter2.minimumIntegerDigits, 0)
341+
formatter2.numberStyle = .ordinal
342+
XCTAssertEqual(formatter2.minimumIntegerDigits, 0)
343+
formatter2.locale = Locale(identifier: "en_US")
344+
XCTAssertEqual(formatter2.string(from: 0.01), "0th")
345+
XCTAssertEqual(formatter2.string(from: 4.234), "4th")
346+
XCTAssertEqual(formatter2.string(from: 42), "42nd")
347+
}
348+
349+
func test_currencyPluralMinimumIntegerDigits() {
350+
// If .minimumIntegerDigits is set to 0 before .numberStyle change, preserve the value
351+
let formatter = NumberFormatter()
352+
XCTAssertEqual(formatter.minimumIntegerDigits, 0)
353+
formatter.minimumIntegerDigits = 0
354+
formatter.numberStyle = .currencyPlural
355+
XCTAssertEqual(formatter.minimumIntegerDigits, 0)
356+
formatter.locale = Locale(identifier: "en_US")
357+
XCTAssertEqual(formatter.string(from: 0), "0.00 US dollars")
358+
XCTAssertEqual(formatter.string(from: 1.23), "1.23 US dollars")
359+
XCTAssertEqual(formatter.string(from: 123.4), "123.40 US dollars")
360+
361+
// If .minimumIntegerDigits is not set before .numberStyle change, update the value
362+
let formatter2 = NumberFormatter()
363+
XCTAssertEqual(formatter2.minimumIntegerDigits, 0)
364+
formatter2.numberStyle = .currencyPlural
365+
XCTAssertEqual(formatter2.minimumIntegerDigits, 0)
366+
formatter2.locale = Locale(identifier: "en_US")
367+
XCTAssertEqual(formatter2.string(from: 0.01), "0.01 US dollars")
368+
XCTAssertEqual(formatter2.string(from: 1.234), "1.23 US dollars")
369+
XCTAssertEqual(formatter2.string(from: 123456.7), "123,456.70 US dollars")
370+
}
371+
372+
func test_currencyISOCodeMinimumIntegerDigits() {
373+
// If .minimumIntegerDigits is set to 0 before .numberStyle change, preserve the value
374+
let formatter = NumberFormatter()
375+
XCTAssertEqual(formatter.minimumIntegerDigits, 0)
376+
formatter.minimumIntegerDigits = 0
377+
formatter.numberStyle = .currencyISOCode
378+
XCTAssertEqual(formatter.minimumIntegerDigits, 0)
379+
formatter.locale = Locale(identifier: "en_US")
380+
XCTAssertEqual(formatter.string(from: 0), "USD.00")
381+
XCTAssertEqual(formatter.string(from: 1.23), "USD1.23")
382+
XCTAssertEqual(formatter.string(from: 123.4), "USD123.40")
383+
384+
// If .minimumIntegerDigits is not set before .numberStyle change, update the value
385+
let formatter2 = NumberFormatter()
386+
XCTAssertEqual(formatter2.minimumIntegerDigits, 0)
387+
formatter2.numberStyle = .currencyISOCode
388+
XCTAssertEqual(formatter2.minimumIntegerDigits, 1)
389+
formatter2.locale = Locale(identifier: "en_US")
390+
XCTAssertEqual(formatter2.string(from: 0.01), "USD0.01")
391+
XCTAssertEqual(formatter2.string(from: 1.234), "USD1.23")
392+
XCTAssertEqual(formatter2.string(from: 123456.7), "USD123,456.70")
393+
}
394+
395+
func test_currencyAccountingMinimumIntegerDigits() {
396+
// If .minimumIntegerDigits is set to 0 before .numberStyle change, preserve the value
397+
let formatter = NumberFormatter()
398+
XCTAssertEqual(formatter.minimumIntegerDigits, 0)
399+
formatter.minimumIntegerDigits = 0
400+
formatter.numberStyle = .currencyAccounting
401+
XCTAssertEqual(formatter.minimumIntegerDigits, 0)
402+
formatter.locale = Locale(identifier: "en_US")
403+
XCTAssertEqual(formatter.string(from: 0), "$.00")
404+
XCTAssertEqual(formatter.string(from: 1.23), "$1.23")
405+
XCTAssertEqual(formatter.string(from: 123.4), "$123.40")
406+
407+
// If .minimumIntegerDigits is not set before .numberStyle change, update the value
408+
let formatter2 = NumberFormatter()
409+
XCTAssertEqual(formatter2.minimumIntegerDigits, 0)
410+
formatter2.numberStyle = .currencyAccounting
411+
XCTAssertEqual(formatter2.minimumIntegerDigits, 1)
412+
formatter2.locale = Locale(identifier: "en_US")
413+
XCTAssertEqual(formatter2.string(from: 0), "$0.00")
414+
XCTAssertEqual(formatter2.string(from: 1.23), "$1.23")
415+
XCTAssertEqual(formatter2.string(from: 123.4), "$123.40")
416+
}
417+
248418
func test_maximumIntegerDigits() {
249419
let numberFormatter = NumberFormatter()
250420
numberFormatter.maximumIntegerDigits = 3

0 commit comments

Comments
 (0)