ICU-20912 Make C/J Currency consistent on lowercase/uppercase currency equality

- Adds additional tests for Currency equality behavior
This commit is contained in:
Shane F. Carr 2019-12-07 11:47:39 -08:00
parent cfef59f0b8
commit b186f2cff6
6 changed files with 71 additions and 12 deletions

View File

@ -52,6 +52,8 @@
U_CAPI UBool U_EXPORT2 U_CAPI UBool U_EXPORT2
uprv_isASCIILetter(char c); uprv_isASCIILetter(char c);
// NOTE: For u_asciiToUpper that takes a UChar, see ustr_imp.h
U_CAPI char U_EXPORT2 U_CAPI char U_EXPORT2
uprv_toupper(char c); uprv_toupper(char c);

View File

@ -46,6 +46,18 @@ ustr_hashCharsN(const char *str, int32_t length);
U_CAPI int32_t U_EXPORT2 U_CAPI int32_t U_EXPORT2
ustr_hashICharsN(const char *str, int32_t length); ustr_hashICharsN(const char *str, int32_t length);
/**
* Convert an ASCII-range lowercase character to uppercase.
*
* @param c A UChar.
* @return If UChar is a lowercase ASCII character, returns the uppercase version.
* Otherwise, returns the input character.
*/
U_CAPI UChar U_EXPORT2
u_asciiToUpper(UChar c);
// TODO: Add u_asciiToLower if/when there is a need for it.
/** /**
* NUL-terminate a UChar * string if possible. * NUL-terminate a UChar * string if possible.
* If length < destCapacity then NUL-terminate. * If length < destCapacity then NUL-terminate.

View File

@ -1451,6 +1451,14 @@ u_unescape(const char *src, UChar *dest, int32_t destCapacity) {
} \ } \
} UPRV_BLOCK_MACRO_END } UPRV_BLOCK_MACRO_END
U_CAPI UChar U_EXPORT2
u_asciiToUpper(UChar c) {
if (u'a' <= c && c <= u'z') {
c = c + u'A' - u'a';
}
return c;
}
U_CAPI int32_t U_EXPORT2 U_CAPI int32_t U_EXPORT2
u_terminateUChars(UChar *dest, int32_t destCapacity, int32_t length, UErrorCode *pErrorCode) { u_terminateUChars(UChar *dest, int32_t destCapacity, int32_t length, UErrorCode *pErrorCode) {
__TERMINATE_STRING(dest, destCapacity, length, pErrorCode); __TERMINATE_STRING(dest, destCapacity, length, pErrorCode);

View File

@ -16,9 +16,11 @@
#include "unicode/currunit.h" #include "unicode/currunit.h"
#include "unicode/ustring.h" #include "unicode/ustring.h"
#include "unicode/uchar.h"
#include "cstring.h" #include "cstring.h"
#include "uinvchar.h" #include "uinvchar.h"
#include "charstr.h" #include "charstr.h"
#include "ustr_imp.h"
#include "measunit_impl.h" #include "measunit_impl.h"
U_NAMESPACE_BEGIN U_NAMESPACE_BEGIN
@ -29,22 +31,25 @@ CurrencyUnit::CurrencyUnit(ConstChar16Ptr _isoCode, UErrorCode& ec) {
// non-NUL-terminated string to be passed as an argument, so it is not possible to check length. // non-NUL-terminated string to be passed as an argument, so it is not possible to check length.
// However, we allow a NUL-terminated empty string, which should have the same behavior as nullptr. // However, we allow a NUL-terminated empty string, which should have the same behavior as nullptr.
// Consider NUL-terminated strings of length 1 or 2 as invalid. // Consider NUL-terminated strings of length 1 or 2 as invalid.
const char16_t* isoCodeToUse; bool useDefault = false;
if (U_FAILURE(ec) || _isoCode == nullptr || _isoCode[0] == 0) { if (U_FAILURE(ec) || _isoCode == nullptr || _isoCode[0] == 0) {
isoCodeToUse = kDefaultCurrency; useDefault = true;
} else if (_isoCode[1] == 0 || _isoCode[2] == 0) { } else if (_isoCode[1] == 0 || _isoCode[2] == 0) {
isoCodeToUse = kDefaultCurrency; useDefault = true;
ec = U_ILLEGAL_ARGUMENT_ERROR; ec = U_ILLEGAL_ARGUMENT_ERROR;
} else if (!uprv_isInvariantUString(_isoCode, 3)) { } else if (!uprv_isInvariantUString(_isoCode, 3)) {
// TODO: Perform a more strict ASCII check like in ICU4J isAlpha3Code? // TODO: Perform a more strict ASCII check like in ICU4J isAlpha3Code?
isoCodeToUse = kDefaultCurrency; useDefault = true;
ec = U_INVARIANT_CONVERSION_ERROR; ec = U_INVARIANT_CONVERSION_ERROR;
} else { } else {
isoCodeToUse = _isoCode; for (int32_t i=0; i<3; i++) {
isoCode[i] = u_asciiToUpper(_isoCode[i]);
} }
// TODO: Perform uppercasing here like in ICU4J Currency.getInstance()?
uprv_memcpy(isoCode, isoCodeToUse, sizeof(UChar) * 3);
isoCode[3] = 0; isoCode[3] = 0;
}
if (useDefault) {
uprv_memcpy(isoCode, kDefaultCurrency, sizeof(UChar) * 4);
}
char simpleIsoCode[4]; char simpleIsoCode[4];
u_UCharsToChars(isoCode, simpleIsoCode, 4); u_UCharsToChars(isoCode, simpleIsoCode, 4);
initCurrency(simpleIsoCode); initCurrency(simpleIsoCode);
@ -64,13 +69,13 @@ CurrencyUnit::CurrencyUnit(StringPiece _isoCode, UErrorCode& ec) {
ec = U_INVARIANT_CONVERSION_ERROR; ec = U_INVARIANT_CONVERSION_ERROR;
} else { } else {
// Have to use isoCodeBuffer to ensure the string is NUL-terminated // Have to use isoCodeBuffer to ensure the string is NUL-terminated
uprv_strncpy(isoCodeBuffer, _isoCode.data(), 3); for (int32_t i=0; i<3; i++) {
isoCodeBuffer[i] = uprv_toupper(_isoCode.data()[i]);
}
isoCodeBuffer[3] = 0; isoCodeBuffer[3] = 0;
isoCodeToUse = isoCodeBuffer; isoCodeToUse = isoCodeBuffer;
} }
// TODO: Perform uppercasing here like in ICU4J Currency.getInstance()? u_charsToUChars(isoCodeToUse, isoCode, 4);
u_charsToUChars(isoCodeToUse, isoCode, 3);
isoCode[3] = 0;
initCurrency(isoCodeToUse); initCurrency(isoCodeToUse);
} }

View File

@ -2172,6 +2172,10 @@ void NumberFormatTest::TestCurrencyUnit(void){
static const char INV8[] = "{$%"; static const char INV8[] = "{$%";
static const UChar ZZZ[] = u"zz"; static const UChar ZZZ[] = u"zz";
static const char ZZZ8[] = "zz"; static const char ZZZ8[] = "zz";
static const UChar JPY[] = u"JPY";
static const char JPY8[] = "JPY";
static const UChar jpy[] = u"jpy";
static const char jpy8[] = "jpy";
UChar* EUR = (UChar*) malloc(6); UChar* EUR = (UChar*) malloc(6);
EUR[0] = u'E'; EUR[0] = u'E';
@ -2289,6 +2293,27 @@ void NumberFormatTest::TestCurrencyUnit(void){
assertEquals("Copying from meter should fail", ec, U_ILLEGAL_ARGUMENT_ERROR); assertEquals("Copying from meter should fail", ec, U_ILLEGAL_ARGUMENT_ERROR);
assertEquals("Copying should not give uninitialized ISO code", u"", failure.getISOCurrency()); assertEquals("Copying should not give uninitialized ISO code", u"", failure.getISOCurrency());
// Test equality
ec = U_ZERO_ERROR;
assertFalse("FAIL: USD == JPY", CurrencyUnit(USD, ec) == CurrencyUnit(JPY, ec));
assertTrue("FAIL: USD != USD", CurrencyUnit(USD, ec) == CurrencyUnit(USD, ec));
assertTrue("FAIL: JPY != jpy", CurrencyUnit(JPY, ec) == CurrencyUnit(jpy, ec));
assertTrue("FAIL: jpy != JPY", CurrencyUnit(jpy, ec) == CurrencyUnit(JPY, ec));
// Test equality with system charset instances
assertFalse("FAIL: USD8 == JPY8", CurrencyUnit(USD8, ec) == CurrencyUnit(JPY8, ec));
assertTrue("FAIL: USD8 != USD8", CurrencyUnit(USD8, ec) == CurrencyUnit(USD8, ec));
assertTrue("FAIL: JPY8 != jpy8", CurrencyUnit(JPY8, ec) == CurrencyUnit(jpy8, ec));
assertTrue("FAIL: jpy8 != JPY8", CurrencyUnit(jpy8, ec) == CurrencyUnit(JPY8, ec));
// Test equality between UTF-16 and system charset instances
assertTrue("FAIL: USD != USD8", CurrencyUnit(USD, ec) == CurrencyUnit(USD8, ec));
assertTrue("FAIL: USD8 != USD", CurrencyUnit(USD8, ec) == CurrencyUnit(USD, ec));
assertTrue("FAIL: JPY != jpy8", CurrencyUnit(JPY, ec) == CurrencyUnit(jpy8, ec));
assertTrue("FAIL: JPY8 != jpy", CurrencyUnit(JPY8, ec) == CurrencyUnit(jpy, ec));
assertTrue("FAIL: jpy != JPY8", CurrencyUnit(jpy, ec) == CurrencyUnit(JPY8, ec));
assertTrue("FAIL: jpy8 != JPY", CurrencyUnit(jpy8, ec) == CurrencyUnit(JPY, ec));
free(EUR); free(EUR);
free(EUR8); free(EUR8);
} }

View File

@ -52,6 +52,7 @@ public class CurrencyTest extends TestFmwk {
Currency usd = Currency.getInstance("USD"); Currency usd = Currency.getInstance("USD");
/*int hash = */usd.hashCode(); /*int hash = */usd.hashCode();
Currency jpy = Currency.getInstance("JPY"); Currency jpy = Currency.getInstance("JPY");
Currency jpy2 = Currency.getInstance("jpy");
if (usd.equals(jpy)) { if (usd.equals(jpy)) {
errln("FAIL: USD == JPY"); errln("FAIL: USD == JPY");
} }
@ -64,6 +65,12 @@ public class CurrencyTest extends TestFmwk {
if (!usd.equals(usd)) { if (!usd.equals(usd)) {
errln("FAIL: USD != USD"); errln("FAIL: USD != USD");
} }
if (!jpy.equals(jpy2)) {
errln("FAIL: JPY != jpy");
}
if (!jpy2.equals(jpy)) {
errln("FAIL: jpy != JPY");
}
try { try {
Currency nullCurrency = Currency.getInstance((String)null); Currency nullCurrency = Currency.getInstance((String)null);