Skip to content

Commit 0464812

Browse files
authored
Merge pull request #1530 from spevans/pr_sr_7481
2 parents 6c54c84 + 3fef3a8 commit 0464812

File tree

2 files changed

+246
-22
lines changed

2 files changed

+246
-22
lines changed

Foundation/NumberFormatter.swift

Lines changed: 51 additions & 19 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 {
@@ -140,7 +140,7 @@ open class NumberFormatter : Formatter {
140140
_setFormatterAttribute(formatter, attributeName: kCFNumberFormatterPlusSign, value: _plusSign?._cfObject)
141141
_setFormatterAttribute(formatter, attributeName: kCFNumberFormatterCurrencySymbol, value: _currencySymbol?._cfObject)
142142
_setFormatterAttribute(formatter, attributeName: kCFNumberFormatterExponentSymbol, value: _exponentSymbol?._cfObject)
143-
_setFormatterAttribute(formatter, attributeName: kCFNumberFormatterMinIntegerDigits, value: _minimumIntegerDigits._bridgeToObjectiveC()._cfObject)
143+
_setFormatterAttribute(formatter, attributeName: kCFNumberFormatterMinIntegerDigits, value: minimumIntegerDigits._bridgeToObjectiveC()._cfObject)
144144
_setFormatterAttribute(formatter, attributeName: kCFNumberFormatterMaxIntegerDigits, value: _maximumIntegerDigits._bridgeToObjectiveC()._cfObject)
145145
_setFormatterAttribute(formatter, attributeName: kCFNumberFormatterMinFractionDigits, value: _minimumFractionDigits._bridgeToObjectiveC()._cfObject)
146146
if _minimumFractionDigits <= 0 {
@@ -187,24 +187,53 @@ 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
193+
if _minimumIntegerDigits == nil {
194+
_minimumIntegerDigits = 1
195+
}
196+
if _groupingSize == 0 {
197+
_groupingSize = 3
198+
}
193199
_minimumFractionDigits = 2
194-
200+
201+
case .currencyPlural:
202+
_usesSignificantDigits = false
203+
_usesGroupingSeparator = true
204+
if _minimumIntegerDigits == nil {
205+
_minimumIntegerDigits = 0
206+
}
207+
_minimumFractionDigits = 2
208+
195209
case .decimal:
196210
_usesGroupingSeparator = true
197211
_maximumFractionDigits = 3
198-
if _minimumIntegerDigits == 0 {
212+
if _minimumIntegerDigits == nil {
199213
_minimumIntegerDigits = 1
200214
}
201215
if _groupingSize == 0 {
202216
_groupingSize = 3
203217
}
204218

205-
default:
206-
_usesSignificantDigits = true
219+
case .percent:
220+
_usesSignificantDigits = false
207221
_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+
}
208237
}
209238
_reset()
210239
_numberStyle = newValue
@@ -680,11 +709,14 @@ open class NumberFormatter : Formatter {
680709
_roundingIncrement = newValue
681710
}
682711
}
683-
684-
internal var _minimumIntegerDigits: Int = 0
712+
713+
// Use an optional for _minimumIntegerDigits to track if the value is
714+
// set BEFORE the .numberStyle is changed. This allows preserving a setting
715+
// of 0.
716+
internal var _minimumIntegerDigits: Int?
685717
open var minimumIntegerDigits: Int {
686718
get {
687-
return _minimumIntegerDigits
719+
return _minimumIntegerDigits ?? 0
688720
}
689721
set {
690722
_reset()

TestFoundation/TestNumberFormatter.swift

Lines changed: 195 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,15 @@ class TestNumberFormatter: XCTestCase {
3232
("test_plusSignSymbol", test_plusSignSymbol),
3333
("test_currencySymbol", test_currencySymbol),
3434
("test_exponentSymbol", test_exponentSymbol),
35-
("test_minimumIntegerDigits", test_minimumIntegerDigits),
35+
("test_decimalMinimumIntegerDigits", test_decimalMinimumIntegerDigits),
36+
("test_currencyMinimumIntegerDigits", test_currencyMinimumIntegerDigits),
37+
("test_percentMinimumIntegerDigits", test_percentMinimumIntegerDigits),
38+
("test_scientificMinimumIntegerDigits", test_scientificMinimumIntegerDigits),
39+
("test_spellOutMinimumIntegerDigits", test_spellOutMinimumIntegerDigits),
40+
("test_ordinalMinimumIntegerDigits", test_ordinalMinimumIntegerDigits),
41+
("test_currencyPluralMinimumIntegerDigits", test_currencyPluralMinimumIntegerDigits),
42+
("test_currencyISOCodeMinimumIntegerDigits", test_currencyISOCodeMinimumIntegerDigits),
43+
("test_currencyAccountingMinimumIntegerDigits", test_currencyAccountingMinimumIntegerDigits),
3644
("test_maximumIntegerDigits", test_maximumIntegerDigits),
3745
("test_minimumFractionDigits", test_minimumFractionDigits),
3846
("test_maximumFractionDigits", test_maximumFractionDigits),
@@ -210,7 +218,7 @@ class TestNumberFormatter: XCTestCase {
210218
XCTAssertEqual(formattedString, "4.2⬆️1")
211219
}
212220

213-
func test_minimumIntegerDigits() {
221+
func test_decimalMinimumIntegerDigits() {
214222
let numberFormatter1 = NumberFormatter()
215223
XCTAssertEqual(numberFormatter1.minimumIntegerDigits, 0)
216224
numberFormatter1.minimumIntegerDigits = 3
@@ -230,7 +238,191 @@ class TestNumberFormatter: XCTestCase {
230238
formattedString = numberFormatter.string(from: 0.1)
231239
XCTAssertEqual(formattedString, "000.1")
232240
}
233-
241+
242+
func test_currencyMinimumIntegerDigits() {
243+
// If .minimumIntegerDigits is set to 0 before .numberStyle change, preserve the value
244+
let formatter = NumberFormatter()
245+
XCTAssertEqual(formatter.minimumIntegerDigits, 0)
246+
formatter.minimumIntegerDigits = 0
247+
formatter.numberStyle = .currency
248+
XCTAssertEqual(formatter.minimumIntegerDigits, 0)
249+
formatter.locale = Locale(identifier: "en_US")
250+
XCTAssertEqual(formatter.string(from: 0), "$.00")
251+
XCTAssertEqual(formatter.string(from: 1.23), "$1.23")
252+
XCTAssertEqual(formatter.string(from: 123.4), "$123.40")
253+
254+
// If .minimumIntegerDigits is not set before .numberStyle change, update the value
255+
let formatter2 = NumberFormatter()
256+
XCTAssertEqual(formatter2.minimumIntegerDigits, 0)
257+
formatter2.numberStyle = .currency
258+
XCTAssertEqual(formatter2.minimumIntegerDigits, 1)
259+
formatter2.locale = Locale(identifier: "en_US")
260+
XCTAssertEqual(formatter2.string(from: 0.001), "$0.00")
261+
XCTAssertEqual(formatter2.string(from: 1.234), "$1.23")
262+
XCTAssertEqual(formatter2.string(from: 123456.7), "$123,456.70")
263+
}
264+
265+
func test_percentMinimumIntegerDigits() {
266+
// If .minimumIntegerDigits is set to 0 before .numberStyle change, preserve the value
267+
let formatter = NumberFormatter()
268+
XCTAssertEqual(formatter.minimumIntegerDigits, 0)
269+
formatter.minimumIntegerDigits = 0
270+
formatter.numberStyle = .percent
271+
XCTAssertEqual(formatter.minimumIntegerDigits, 0)
272+
formatter.locale = Locale(identifier: "en_US")
273+
XCTAssertEqual(formatter.string(from: 0), "0%")
274+
XCTAssertEqual(formatter.string(from: 1.234), "123%")
275+
XCTAssertEqual(formatter.string(from: 123.4), "12,340%")
276+
277+
// If .minimumIntegerDigits is not set before .numberStyle change, update the value
278+
let formatter2 = NumberFormatter()
279+
XCTAssertEqual(formatter2.minimumIntegerDigits, 0)
280+
formatter2.numberStyle = .percent
281+
XCTAssertEqual(formatter2.minimumIntegerDigits, 1)
282+
formatter2.locale = Locale(identifier: "en_US")
283+
XCTAssertEqual(formatter2.string(from: 0.01), "1%")
284+
XCTAssertEqual(formatter2.string(from: 1.234), "123%")
285+
XCTAssertEqual(formatter2.string(from: 123456.7), "12,345,670%")
286+
}
287+
288+
func test_scientificMinimumIntegerDigits() {
289+
// If .minimumIntegerDigits is set to 0 before .numberStyle change, preserve the value
290+
let formatter = NumberFormatter()
291+
XCTAssertEqual(formatter.minimumIntegerDigits, 0)
292+
formatter.minimumIntegerDigits = 0
293+
formatter.numberStyle = .scientific
294+
XCTAssertEqual(formatter.minimumIntegerDigits, 0)
295+
formatter.locale = Locale(identifier: "en_US")
296+
XCTAssertEqual(formatter.string(from: 0), "0E0")
297+
XCTAssertEqual(formatter.string(from: 1.23), "1.23E0")
298+
XCTAssertEqual(formatter.string(from: 123.4), "1.234E2")
299+
300+
// If .minimumIntegerDigits is not set before .numberStyle change, update the value
301+
let formatter2 = NumberFormatter()
302+
XCTAssertEqual(formatter2.minimumIntegerDigits, 0)
303+
formatter2.numberStyle = .scientific
304+
XCTAssertEqual(formatter2.minimumIntegerDigits, 0)
305+
formatter2.locale = Locale(identifier: "en_US")
306+
XCTAssertEqual(formatter2.string(from: 0.01), "1E-2")
307+
XCTAssertEqual(formatter2.string(from: 1.234), "1.234E0")
308+
XCTAssertEqual(formatter2.string(from: 123456.7), "1.234567E5")
309+
}
310+
311+
func test_spellOutMinimumIntegerDigits() {
312+
// If .minimumIntegerDigits is set to 0 before .numberStyle change, preserve the value
313+
let formatter = NumberFormatter()
314+
XCTAssertEqual(formatter.minimumIntegerDigits, 0)
315+
formatter.minimumIntegerDigits = 0
316+
formatter.numberStyle = .spellOut
317+
XCTAssertEqual(formatter.minimumIntegerDigits, 0)
318+
formatter.locale = Locale(identifier: "en_US")
319+
XCTAssertEqual(formatter.string(from: 0), "zero")
320+
XCTAssertEqual(formatter.string(from: 1.23), "one point two three")
321+
XCTAssertEqual(formatter.string(from: 123.4), "one hundred twenty-three point four")
322+
323+
// If .minimumIntegerDigits is not set before .numberStyle change, update the value
324+
let formatter2 = NumberFormatter()
325+
XCTAssertEqual(formatter2.minimumIntegerDigits, 0)
326+
formatter2.numberStyle = .spellOut
327+
XCTAssertEqual(formatter2.minimumIntegerDigits, 0)
328+
formatter2.locale = Locale(identifier: "en_US")
329+
XCTAssertEqual(formatter2.string(from: 0.01), "zero point zero one")
330+
XCTAssertEqual(formatter2.string(from: 1.234), "one point two three four")
331+
XCTAssertEqual(formatter2.string(from: 123456.7), "one hundred twenty-three thousand four hundred fifty-six point seven")
332+
}
333+
334+
func test_ordinalMinimumIntegerDigits() {
335+
// If .minimumIntegerDigits is set to 0 before .numberStyle change, preserve the value
336+
let formatter = NumberFormatter()
337+
XCTAssertEqual(formatter.minimumIntegerDigits, 0)
338+
formatter.minimumIntegerDigits = 0
339+
formatter.numberStyle = .ordinal
340+
XCTAssertEqual(formatter.minimumIntegerDigits, 0)
341+
formatter.locale = Locale(identifier: "en_US")
342+
XCTAssertEqual(formatter.string(from: 0), "0th")
343+
XCTAssertEqual(formatter.string(from: 1.23), "1st")
344+
XCTAssertEqual(formatter.string(from: 123.4), "123rd")
345+
346+
// If .minimumIntegerDigits is not set before .numberStyle change, update the value
347+
let formatter2 = NumberFormatter()
348+
XCTAssertEqual(formatter2.minimumIntegerDigits, 0)
349+
formatter2.numberStyle = .ordinal
350+
XCTAssertEqual(formatter2.minimumIntegerDigits, 0)
351+
formatter2.locale = Locale(identifier: "en_US")
352+
XCTAssertEqual(formatter2.string(from: 0.01), "0th")
353+
XCTAssertEqual(formatter2.string(from: 4.234), "4th")
354+
XCTAssertEqual(formatter2.string(from: 42), "42nd")
355+
}
356+
357+
func test_currencyPluralMinimumIntegerDigits() {
358+
// If .minimumIntegerDigits is set to 0 before .numberStyle change, preserve the value
359+
let formatter = NumberFormatter()
360+
XCTAssertEqual(formatter.minimumIntegerDigits, 0)
361+
formatter.minimumIntegerDigits = 0
362+
formatter.numberStyle = .currencyPlural
363+
XCTAssertEqual(formatter.minimumIntegerDigits, 0)
364+
formatter.locale = Locale(identifier: "en_US")
365+
XCTAssertEqual(formatter.string(from: 0), "0.00 US dollars")
366+
XCTAssertEqual(formatter.string(from: 1.23), "1.23 US dollars")
367+
XCTAssertEqual(formatter.string(from: 123.4), "123.40 US dollars")
368+
369+
// If .minimumIntegerDigits is not set before .numberStyle change, update the value
370+
let formatter2 = NumberFormatter()
371+
XCTAssertEqual(formatter2.minimumIntegerDigits, 0)
372+
formatter2.numberStyle = .currencyPlural
373+
XCTAssertEqual(formatter2.minimumIntegerDigits, 0)
374+
formatter2.locale = Locale(identifier: "en_US")
375+
XCTAssertEqual(formatter2.string(from: 0.01), "0.01 US dollars")
376+
XCTAssertEqual(formatter2.string(from: 1.234), "1.23 US dollars")
377+
XCTAssertEqual(formatter2.string(from: 123456.7), "123,456.70 US dollars")
378+
}
379+
380+
func test_currencyISOCodeMinimumIntegerDigits() {
381+
// If .minimumIntegerDigits is set to 0 before .numberStyle change, preserve the value
382+
let formatter = NumberFormatter()
383+
XCTAssertEqual(formatter.minimumIntegerDigits, 0)
384+
formatter.minimumIntegerDigits = 0
385+
formatter.numberStyle = .currencyISOCode
386+
XCTAssertEqual(formatter.minimumIntegerDigits, 0)
387+
formatter.locale = Locale(identifier: "en_US")
388+
XCTAssertEqual(formatter.string(from: 0), "USD.00")
389+
XCTAssertEqual(formatter.string(from: 1.23), "USD1.23")
390+
XCTAssertEqual(formatter.string(from: 123.4), "USD123.40")
391+
392+
// If .minimumIntegerDigits is not set before .numberStyle change, update the value
393+
let formatter2 = NumberFormatter()
394+
XCTAssertEqual(formatter2.minimumIntegerDigits, 0)
395+
formatter2.numberStyle = .currencyISOCode
396+
XCTAssertEqual(formatter2.minimumIntegerDigits, 1)
397+
formatter2.locale = Locale(identifier: "en_US")
398+
XCTAssertEqual(formatter2.string(from: 0.01), "USD0.01")
399+
XCTAssertEqual(formatter2.string(from: 1.234), "USD1.23")
400+
XCTAssertEqual(formatter2.string(from: 123456.7), "USD123,456.70")
401+
}
402+
403+
func test_currencyAccountingMinimumIntegerDigits() {
404+
// If .minimumIntegerDigits is set to 0 before .numberStyle change, preserve the value
405+
let formatter = NumberFormatter()
406+
XCTAssertEqual(formatter.minimumIntegerDigits, 0)
407+
formatter.minimumIntegerDigits = 0
408+
formatter.numberStyle = .currencyAccounting
409+
XCTAssertEqual(formatter.minimumIntegerDigits, 0)
410+
formatter.locale = Locale(identifier: "en_US")
411+
XCTAssertEqual(formatter.string(from: 0), "$.00")
412+
XCTAssertEqual(formatter.string(from: 1.23), "$1.23")
413+
XCTAssertEqual(formatter.string(from: 123.4), "$123.40")
414+
415+
// If .minimumIntegerDigits is not set before .numberStyle change, update the value
416+
let formatter2 = NumberFormatter()
417+
XCTAssertEqual(formatter2.minimumIntegerDigits, 0)
418+
formatter2.numberStyle = .currencyAccounting
419+
XCTAssertEqual(formatter2.minimumIntegerDigits, 1)
420+
formatter2.locale = Locale(identifier: "en_US")
421+
XCTAssertEqual(formatter2.string(from: 0), "$0.00")
422+
XCTAssertEqual(formatter2.string(from: 1.23), "$1.23")
423+
XCTAssertEqual(formatter2.string(from: 123.4), "$123.40")
424+
}
425+
234426
func test_maximumIntegerDigits() {
235427
let numberFormatter = NumberFormatter()
236428
numberFormatter.maximumIntegerDigits = 3

0 commit comments

Comments
 (0)