-
Notifications
You must be signed in to change notification settings - Fork 207
Fix parsing foreign currency strings #1074
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
|
@swift-ci please test |
|
@swift-ci please test |
|
|
||
| let frenchPrice = "57 379 €" | ||
| _verifyMatching(frenchPrice, formatStyle: frenchStyle, expectedUpperBound: frenchPrice.endIndex, expectedValue: 57379) | ||
| _verifyMatching(frenchPrice, formatStyle: floatStyle.locale(frFR), expectedUpperBound: frenchPrice.endIndex, expectedValue: 57379) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The tests incorrectly assumed that a currency style with "USD" code but "fr_FR" locale can correctly match a currency string with EUR symbol ("57 379,00 €"). This isn't correct, and is exactly what this bug is about: we only respect the locale regional currency, but ignore the currency code.
theMomax
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Generally looks good! I think we just need to make sure the serialization format is backwards compatible, then this should be good to go!
Tests/FoundationInternationalizationTests/Formatting/NumberParseStrategyTests.swift
Outdated
Show resolved
Hide resolved
Sources/FoundationInternationalization/Formatting/Number/NumberFormatStyleConfiguration.swift
Outdated
Show resolved
Hide resolved
Currently parsing a currency string would fail if the currency code does not match `FormatStyle`'s locale region. For example,
```swift
let style = Decimal.FormatStyle.Currency(code: "GBP", locale: .init(identifier: "en_US")).presentation(.isoCode)
```
This formats 3.14 into "GBP\u{0xa0}3.14", but parsing such string fails.
Fix this by always set the ICU formatter's currency code.
Resolves rdar://138179737
73a6149 to
10091ea
Compare
itingliu
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like this approach better than the previous one. We already have the currency code stored in the FormatStyle as a top-level property. There's no need to store it again in the nested configuration.
Also this should address @theMomax your concern about changing Codable representation. This should not change the serialized output anymore since we're not touching any Codable type in public types.
|
@swift-ci please test |
|
@swift-ci please test |
theMomax
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This new approach is definitely better because I think people serializing FormatStyles is much more common than serializing ParseStrategys. But I think we should still make it backwards compatible in any scenario. Could we make currencyCode an optional property on CurrencyFormatStyleConfiguration.Collection? That way you could still avoid writing out a custom codable conformance.
Tests/FoundationInternationalizationTests/Formatting/NumberFormatStyleTests.swift
Outdated
Show resolved
Hide resolved
Tests/FoundationInternationalizationTests/Formatting/NumberFormatStyleTests.swift
Outdated
Show resolved
Hide resolved
Sources/FoundationInternationalization/Formatting/Number/ICULegacyNumberFormatter.swift
Outdated
Show resolved
Hide resolved
| enum NumberFormatType : Hashable, Codable { | ||
| case number(NumberFormatStyleConfiguration.Collection) | ||
| case percent(NumberFormatStyleConfiguration.Collection) | ||
| case currency(CurrencyFormatStyleConfiguration.Collection) | ||
| case currency(CurrencyFormatStyleConfiguration.Collection, /*currency code*/ String) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This type is part of the Codable format of a number of public types conforming to ParseStrategy, e.g. IntegerParseStrategy. So while this solution means the Codable conformance for Decimal.FormatStyle.Currency is backwards compatible, your decoding test would still fail for IntegerParseStrategy.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, you meant this is a var in IntegerParseStrategy and FloatingPointParseStrategy and friends ... OK we need tests for that too. Thanks
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- Add codable test for
ParseStrategy
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Another attempt: In 9a1e617 I removed the stored property numberFormatType from IntegerParseStrategy and FloatingPointParseStrategy. That means the we no longer preserve numberFormatType in serialization, and numberFormatType in previous Codable representation will be ignored from now on.
This is ok because the information is already provided by the other variable, formatStyle. In other words, numberFormatType has always been redundant. I also liked this because the previous serialization output feels like we're leaking internal information.
There is indeed a slight chance of regression -- if you manually craft an archive where numberFormatType represents different configurations from that of formatStyle, previously we'd favor what's represented by numberFormatType, but now we'd favor formatStyle. I think this risk is low enough though. Besides, I don't think we ever guarantee compatibility for manually crafted archives.
I'm flipping back and forth. A custom |
…eStrategy` and `FloatingPointParseStrategy` These properties are redundant as the information is already available through the public variable `formatStyle`.
|
@swift-ci please test |
theMomax
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That sounds like a good solution, thank you!
|
@swift-ci please test |
- Remove an unused variable. - Calling `JSONDecoder`'s `func decode<T: Decodable>(_ type: T.Type, from data: Data) throws -> T` with `Decimal.FormatStyle.Currency` as the type isn't correct. But for some reason, the compiler allows it incorrectly. Fix it. - Clean up whitespaces while we're here.
- Remove an unused variable. - Calling `JSONDecoder`'s `func decode<T: Decodable>(_ type: T.Type, from data: Data) throws -> T` with `Decimal.FormatStyle.Currency` as the type isn't correct. But for some reason, the compiler allows it incorrectly. Fix it. - Clean up whitespaces while we're here.
Currently parsing a currency string would fail if the currency code does not match
FormatStyle's locale region.For example,
This formats 3.14 into
"GBP\u{0xa0}3.14", but parsing such string fails.Fix this by always set the ICU formatter's currency code.
Resolves rdar://138179737