ICU-20629 Fixing DTPG hour-format locale and region fallback logic.

Includes changes in C and J.  Makes region.cpp no longer depend on DecimalFormat.  See also: CLDR-13071, ICU-20640, ICU-9982.
This commit is contained in:
Shane Carr 2019-05-29 07:50:25 +00:00 committed by Shane F. Carr
parent 633a975849
commit 46c86b47cc
13 changed files with 886 additions and 674 deletions

View file

@ -276,6 +276,16 @@ int32_t ICU_Utility::parsePattern(const UnicodeString& pat,
return -1; // text ended before end of pat
}
int32_t ICU_Utility::parseAsciiInteger(const UnicodeString& str, int32_t& pos) {
int32_t result = 0;
UChar c;
while (pos < str.length() && (c = str.charAt(pos)) >= u'0' && c <= u'9') {
result = result * 10 + (c - u'0');
pos++;
}
return result;
}
/**
* Append a character to a rule that is being built up. To flush
* the quoteBuf to rule, make one final call with isLiteral == TRUE.

View file

@ -179,12 +179,21 @@ class U_COMMON_API ICU_Utility /* not : public UObject because all methods are s
* Parse an integer at pos, either of the form \d+ or of the form
* 0x[0-9A-Fa-f]+ or 0[0-7]+, that is, in standard decimal, hex,
* or octal format.
* @param pos INPUT-OUTPUT parameter. On input, the first
* character to parse. On output, the character after the last
* parsed character.
* @param pos INPUT-OUTPUT parameter. On input, the index of the first
* character to parse. On output, the index of the character after the
* last parsed character.
*/
static int32_t parseInteger(const UnicodeString& rule, int32_t& pos, int32_t limit);
/**
* Parse an integer at pos using only ASCII digits.
* Base 10 only.
* @param pos INPUT-OUTPUT parameter. On input, the index of the first
* character to parse. On output, the index of the character after the
* last parsed character.
*/
static int32_t parseAsciiInteger(const UnicodeString& str, int32_t& pos);
/**
* Parse a Unicode identifier from the given string at the given
* position. Return the identifier, or an empty string if there

File diff suppressed because it is too large Load diff

View file

@ -65,6 +65,7 @@ likelySubtags:table(nofallback){
arn{"arn_Latn_CL"}
aro{"aro_Latn_BO"}
arq{"arq_Arab_DZ"}
ars{"ars_Arab_SA"}
ary{"ary_Arab_MA"}
arz{"arz_Arab_EG"}
as{"as_Beng_IN"}

View file

@ -268,6 +268,8 @@ static ECalType getCalendarTypeForLocale(const char *locid) {
// canonicalize, so grandfathered variant will be transformed to keywords
// e.g ja_JP_TRADITIONAL -> ja_JP@calendar=japanese
// NOTE: Since ICU-20187, ja_JP_TRADITIONAL no longer canonicalizes, and
// the Gregorian calendar is returned instead.
int32_t canonicalLen = uloc_canonicalize(locid, canonicalName, sizeof(canonicalName) - 1, &status);
if (U_FAILURE(status)) {
return CALTYPE_GREGORIAN;

View file

@ -28,6 +28,7 @@
#include "unicode/ures.h"
#include "unicode/ustring.h"
#include "unicode/rep.h"
#include "unicode/region.h"
#include "cpputils.h"
#include "mutex.h"
#include "umutex.h"
@ -613,29 +614,56 @@ U_CFUNC void U_CALLCONV DateTimePatternGenerator::loadAllowedHourFormatsData(UEr
ures_getAllItemsWithFallback(rb.getAlias(), "timeData", sink, status);
}
void DateTimePatternGenerator::getAllowedHourFormats(const Locale &locale, UErrorCode &status) {
if (U_FAILURE(status)) { return; }
Locale maxLocale(locale);
maxLocale.addLikelySubtags(status);
if (U_FAILURE(status)) {
return;
}
const char *country = maxLocale.getCountry();
if (*country == '\0') { country = "001"; }
const char *language = maxLocale.getLanguage();
static int32_t* getAllowedHourFormatsLangCountry(const char* language, const char* country, UErrorCode& status) {
CharString langCountry;
langCountry.append(language, static_cast<int32_t>(uprv_strlen(language)), status);
langCountry.append(language, status);
langCountry.append('_', status);
langCountry.append(country, static_cast<int32_t>(uprv_strlen(country)), status);
langCountry.append(country, status);
int32_t *allowedFormats;
int32_t* allowedFormats;
allowedFormats = (int32_t *)uhash_get(localeToAllowedHourFormatsMap, langCountry.data());
if (allowedFormats == nullptr) {
allowedFormats = (int32_t *)uhash_get(localeToAllowedHourFormatsMap, const_cast<char *>(country));
}
return allowedFormats;
}
void DateTimePatternGenerator::getAllowedHourFormats(const Locale &locale, UErrorCode &status) {
if (U_FAILURE(status)) { return; }
const char *language = locale.getLanguage();
const char *country = locale.getCountry();
Locale maxLocale; // must be here for correct lifetime
if (*language == '\0' || *country == '\0') {
maxLocale = locale;
UErrorCode localStatus = U_ZERO_ERROR;
maxLocale.addLikelySubtags(localStatus);
if (U_SUCCESS(localStatus)) {
language = maxLocale.getLanguage();
country = maxLocale.getCountry();
}
}
if (*language == '\0') {
// Unexpected, but fail gracefully
language = "und";
}
if (*country == '\0') {
country = "001";
}
int32_t* allowedFormats = getAllowedHourFormatsLangCountry(language, country, status);
// Check if the region has an alias
if (allowedFormats == nullptr) {
UErrorCode localStatus = U_ZERO_ERROR;
const Region* region = Region::getInstance(country, localStatus);
if (U_SUCCESS(localStatus)) {
country = region->getRegionCode(); // the real region code
allowedFormats = getAllowedHourFormatsLangCountry(language, country, status);
}
}
if (allowedFormats != nullptr) { // Lookup is successful
// Here allowedFormats points to a list consisting of key for preferredFormat,
// followed by one or more keys for allowedFormats, then followed by ALLOWED_HOUR_FORMAT_UNKNOWN.

View file

@ -25,7 +25,6 @@
#include "unicode/uobject.h"
#include "unicode/unistr.h"
#include "unicode/ures.h"
#include "unicode/decimfmt.h"
#include "ucln_in.h"
#include "cstring.h"
#include "mutex.h"
@ -33,6 +32,7 @@
#include "umutex.h"
#include "uresimp.h"
#include "region_impl.h"
#include "util.h"
#if !UCONFIG_NO_FORMATTING
@ -87,7 +87,6 @@ void U_CALLCONV Region::loadRegionData(UErrorCode &status) {
LocalUHashtablePointer newRegionIDMap(uhash_open(uhash_hashUnicodeString, uhash_compareUnicodeString, NULL, &status));
LocalUHashtablePointer newNumericCodeMap(uhash_open(uhash_hashLong,uhash_compareLong,NULL,&status));
LocalUHashtablePointer newRegionAliases(uhash_open(uhash_hashUnicodeString,uhash_compareUnicodeString,NULL,&status));
LocalPointer<DecimalFormat> df(new DecimalFormat(status), status);
LocalPointer<UVector> continents(new UVector(uprv_deleteUObject, uhash_compareUnicodeString, status), status);
LocalPointer<UVector> groupings(new UVector(uprv_deleteUObject, uhash_compareUnicodeString, status), status);
@ -115,7 +114,6 @@ void U_CALLCONV Region::loadRegionData(UErrorCode &status) {
}
// now, initialize
df->setParseIntegerOnly(TRUE);
uhash_setValueDeleter(newRegionIDMap.getAlias(), deleteRegion); // regionIDMap owns objs
uhash_setKeyDeleter(newRegionAliases.getAlias(), uprv_deleteUObject); // regionAliases owns the string keys
@ -192,11 +190,10 @@ void U_CALLCONV Region::loadRegionData(UErrorCode &status) {
r->idStr.extract(0,r->idStr.length(),r->id,sizeof(r->id),US_INV);
r->fType = URGN_TERRITORY; // Only temporary - figure out the real type later once the aliases are known.
Formattable result;
UErrorCode ps = U_ZERO_ERROR;
df->parse(r->idStr,result,ps);
if ( U_SUCCESS(ps) ) {
r->code = result.getLong(); // Convert string to number
int32_t pos = 0;
int32_t result = ICU_Utility::parseAsciiInteger(r->idStr, pos);
if (pos > 0) {
r->code = result; // Convert string to number
uhash_iput(newNumericCodeMap.getAlias(),r->code,(void *)(r.getAlias()),&status);
r->fType = URGN_SUBCONTINENT;
} else {
@ -230,11 +227,10 @@ void U_CALLCONV Region::loadRegionData(UErrorCode &status) {
aliasFromRegion->idStr.setTo(*aliasFromStr);
aliasFromRegion->idStr.extract(0,aliasFromRegion->idStr.length(),aliasFromRegion->id,sizeof(aliasFromRegion->id),US_INV);
uhash_put(newRegionIDMap.getAlias(),(void *)&(aliasFromRegion->idStr),(void *)aliasFromRegion,&status);
Formattable result;
UErrorCode ps = U_ZERO_ERROR;
df->parse(aliasFromRegion->idStr,result,ps);
if ( U_SUCCESS(ps) ) {
aliasFromRegion->code = result.getLong(); // Convert string to number
int32_t pos = 0;
int32_t result = ICU_Utility::parseAsciiInteger(aliasFromRegion->idStr, pos);
if ( pos > 0 ) {
aliasFromRegion->code = result; // Convert string to number
uhash_iput(newNumericCodeMap.getAlias(),aliasFromRegion->code,(void *)aliasFromRegion,&status);
} else {
aliasFromRegion->code = -1;
@ -279,11 +275,10 @@ void U_CALLCONV Region::loadRegionData(UErrorCode &status) {
Region *r = (Region *)uhash_get(newRegionIDMap.getAlias(),(void *)&codeMappingID);
if ( r ) {
Formattable result;
UErrorCode ps = U_ZERO_ERROR;
df->parse(codeMappingNumber,result,ps);
if ( U_SUCCESS(ps) ) {
r->code = result.getLong(); // Convert string to number
int32_t pos = 0;
int32_t result = ICU_Utility::parseAsciiInteger(codeMappingNumber, pos);
if ( pos > 0 ) {
r->code = result; // Convert string to number
uhash_iput(newNumericCodeMap.getAlias(),r->code,(void *)r,&status);
}
LocalPointer<UnicodeString> code3(new UnicodeString(codeMapping3Letter), status);
@ -516,15 +511,8 @@ Region::getInstance (int32_t code, UErrorCode &status) {
Region *r = (Region *)uhash_iget(numericCodeMap,code);
if ( !r ) { // Just in case there's an alias that's numeric, try to find it.
UnicodeString pat = UNICODE_STRING_SIMPLE("0");
LocalPointer<DecimalFormat> df(new DecimalFormat(pat,status), status);
if( U_FAILURE(status) ) {
return NULL;
}
UnicodeString id;
id.remove();
FieldPosition posIter;
df->format(code,id, posIter, status);
ICU_Utility::appendNumber(id, code, 10, 1);
r = (Region *)uhash_get(regionAliases,&id);
}

View file

@ -849,7 +849,7 @@ library: i18n
group: region
region.o uregion.o
deps
formatting # Temporary, TODO: Ticket #9982 class Region should use low-level ASCII-integer functions, and probably be moved to the common library.
icu_utility
resourcebundle
uvector uclean_i18n
@ -1018,6 +1018,7 @@ group: formatting
trigonometry # for astro.o
sharedbreakiterator # for reldatefmt.o
uclean_i18n
region
group: sharedbreakiterator
sharedbreakiterator.o

View file

@ -41,12 +41,13 @@ void IntlTestDateTimePatternGeneratorAPI::runIndexedTest( int32_t index, UBool e
TESTCASE(5, testSkeletonsWithDayPeriods);
TESTCASE(6, testGetFieldDisplayNames);
TESTCASE(7, testJjMapping);
TESTCASE(8, testFallbackWithDefaultRootLocale);
TESTCASE(8, test20640_HourCyclArsEnNH);
TESTCASE(9, testFallbackWithDefaultRootLocale);
default: name = ""; break;
}
}
#define MAX_LOCALE 11
#define MAX_LOCALE 12
/**
* Test various generic API methods of DateTimePatternGenerator for API coverage.
@ -85,11 +86,12 @@ void IntlTestDateTimePatternGeneratorAPI::testAPI(/*char *par*/)
{"zh", "Hans", "CN", ""}, // 7
{"zh", "TW", "", "calendar=roc"}, // 8
{"ru", "", "", ""}, // 9
{"zh", "", "", "calendar=chinese"}, // 10
{"zh", "", "", "calendar=chinese"}, // 10
{"ja", "JP", "TRADITIONAL", ""}, // 11
};
// For Weds, Jan 13, 1999, 23:58:59
UnicodeString patternResults[] = {
UnicodeString patternResults_en_US[] = {
// en_US // 0 en_US
UnicodeString("1/1999"), // 00: yM
UnicodeString("Jan 1999"), // 01: yMMM
@ -108,7 +110,9 @@ void IntlTestDateTimePatternGeneratorAPI::testAPI(/*char *par*/)
UnicodeString("13 Wed"), // 14: Ed -> d EEE
UnicodeString("11:58:59.123 PM"), // 15: jmmssSSS -> "h:mm:ss.SSS a"
UnicodeString("11:58"), // 16: JJmm
};
UnicodeString patternResults_en_US_japanese[] = {
// en_US@calendar=japanese // 1 en_US@calendar=japanese
UnicodeString("1/11 H"), // 0: yM
UnicodeString("Jan 11 Heisei"), // 1: yMMM
@ -127,7 +131,9 @@ void IntlTestDateTimePatternGeneratorAPI::testAPI(/*char *par*/)
UnicodeString("13 Wed"), // 14: Ed -> d EEE
UnicodeString("11:58:59.123 PM"), // 15: jmmssSSS -> "h:mm:ss.SSS a"
UnicodeString("11:58"), // 16: JJmm
};
UnicodeString patternResults_de_DE[] = {
// de_DE // 2 de_DE
UnicodeString("1.1999"), // 00: yM
UnicodeString("Jan. 1999"), // 01: yMMM
@ -146,7 +152,9 @@ void IntlTestDateTimePatternGeneratorAPI::testAPI(/*char *par*/)
UnicodeString("Mi., 13."), // 14: Ed -> EEE d.
UnicodeString("23:58:59,123"), // 15: jmmssSSS -> "HH:mm:ss,SSS"
UnicodeString("23:58"), // 16: JJmm
};
UnicodeString patternResults_fi[] = {
// fi // 3 fi
UnicodeString("1.1999"), // 00: yM (fixed expected result per ticket:6626:)
UnicodeString("tammi 1999"), // 01: yMMM
@ -165,7 +173,9 @@ void IntlTestDateTimePatternGeneratorAPI::testAPI(/*char *par*/)
UnicodeString("ke 13."), // 14: Ed -> ccc d.
UnicodeString("23.58.59,123"), // 15: jmmssSSS -> "H.mm.ss,SSS"
UnicodeString("23.58"), // 16: JJmm
};
UnicodeString patternResults_es[] = {
// es // 4 es
UnicodeString("1/1999"), // 00: yM -> "M/y"
UnicodeString("ene. 1999"), // 01: yMMM -> "MMM y"
@ -184,7 +194,9 @@ void IntlTestDateTimePatternGeneratorAPI::testAPI(/*char *par*/)
CharsToUnicodeString("mi\\u00E9. 13"), // 14: Ed -> "EEE d"
UnicodeString("23:58:59,123"), // 15: jmmssSSS -> "H:mm:ss,SSS"
UnicodeString("23:58"), // 16: JJmm
};
UnicodeString patternResults_ja[] = {
// ja // 5 ja
UnicodeString("1999/1"), // 00: yM -> y/M
CharsToUnicodeString("1999\\u5E741\\u6708"), // 01: yMMM -> y\u5E74M\u6708
@ -203,7 +215,9 @@ void IntlTestDateTimePatternGeneratorAPI::testAPI(/*char *par*/)
CharsToUnicodeString("13\\u65E5(\\u6C34)"), // 14: Ed -> d\u65E5(EEE)
UnicodeString("23:58:59.123"), // 15: jmmssSSS -> "H:mm:ss.SSS"
UnicodeString("23:58"), // 16: JJmm
};
UnicodeString patternResults_ja_japanese[] = {
// ja@calendar=japanese // 6 ja@calendar=japanese
UnicodeString("H11/1"), // 00: yM -> GGGGGy/m
CharsToUnicodeString("\\u5E73\\u621011\\u5E741\\u6708"), // 01: yMMM -> Gy\u5E74M\u6708
@ -222,7 +236,9 @@ void IntlTestDateTimePatternGeneratorAPI::testAPI(/*char *par*/)
CharsToUnicodeString("13\\u65E5(\\u6C34)"), // 14: Ed -> d\u65E5(EEE)
UnicodeString("23:58:59.123"), // 15: jmmssSSS -> "H:mm:ss.SSS"
UnicodeString("23:58"), // 16: JJmm
};
UnicodeString patternResults_zh_Hans_CN[] = {
// zh_Hans_CN // 7 zh_Hans_CN
CharsToUnicodeString("1999\\u5E741\\u6708"), // 00: yM -> y\u5E74M\u6708
CharsToUnicodeString("1999\\u5E741\\u6708"), // 01: yMMM -> yyyy\u5E74MMM (fixed expected result per ticket:6626:)
@ -241,7 +257,9 @@ void IntlTestDateTimePatternGeneratorAPI::testAPI(/*char *par*/)
CharsToUnicodeString("13\\u65E5\\u5468\\u4E09"), // 14: Ed -> d\u65E5EEE
CharsToUnicodeString("\\u4E0B\\u534811:58:59.123"), // 15: jmmssSSS -> "ah:mm:ss.SSS"
UnicodeString("11:58"), // 16: JJmm
};
UnicodeString patternResults_zh_TW_roc[] = {
// zh_TW@calendar=roc // 8 zh_TW@calendar=roc
CharsToUnicodeString("\\u6C11\\u570B88/1"), // 00: yM -> Gy/M
CharsToUnicodeString("\\u6C11\\u570B88\\u5E741\\u6708"), // 01: yMMM -> Gy\u5E74M\u6708
@ -260,7 +278,9 @@ void IntlTestDateTimePatternGeneratorAPI::testAPI(/*char *par*/)
CharsToUnicodeString("13 \\u9031\\u4E09"), // 14: Ed -> d E
CharsToUnicodeString("\\u4E0B\\u534811:58:59.123"), // 15: jmmssSSS -> "ah:mm:ss.SSS"
UnicodeString("11:58"), // 16: JJmm
};
UnicodeString patternResults_ru[] = {
// ru // 9 ru
UnicodeString("01.1999"), // 00: yM -> MM.y
CharsToUnicodeString("\\u044F\\u043D\\u0432. 1999 \\u0433."), // 01: yMMM -> LLL y
@ -279,7 +299,9 @@ void IntlTestDateTimePatternGeneratorAPI::testAPI(/*char *par*/)
CharsToUnicodeString("\\u0441\\u0440, 13"), // 14: Ed -> EEE, d
UnicodeString("23:58:59,123"), // 15: jmmssSSS -> "H:mm:ss,SSS"
UnicodeString("23:58"), // 16: JJmm
};
UnicodeString patternResults_zh_chinese[] = {
// zh@calendar=chinese // 10 zh@calendar=chinese
CharsToUnicodeString("1998\\u620A\\u5BC5\\u5E74\\u5341\\u4E00\\u6708"), // 00: yMMM
CharsToUnicodeString("1998\\u620A\\u5BC5\\u5E74\\u5341\\u4E00\\u6708"), // 01: yMMM
@ -298,8 +320,42 @@ void IntlTestDateTimePatternGeneratorAPI::testAPI(/*char *par*/)
CharsToUnicodeString("26\\u65E5\\u5468\\u4E09"), // 14: Ed -> d\u65E5EEE
CharsToUnicodeString("\\u4E0B\\u534811:58:59.123"), // 15: jmmssSS
UnicodeString("11:58"), // 16: JJmm
};
UnicodeString(),
UnicodeString patternResults_ja_jp_traditional[] = {
// ja_JP_TRADITIONAL // 11 ja_JP_TRADITIONAL
u"AD1999/1", // 00: yM
u"西暦1999年1月", // 01: yMMM
u"1999年1月13日", // 02: yMd
u"西暦1999年1月13日", // 03: yMMMd
u"1/13", // 04: Md
u"1月13日", // 05: MMMd
u"1月13日", // 06: MMMMd
u"西暦1999/Q1", // 07: yQQQ
u"午後11:58", // 08: hhmm
u"23:58", // 09: HHmm
u"23:58", // 10: jjmm
u"58:59", // 11: mmss
u"西暦1999年1月", // 12: yyyyMMMM
u"1月13日(水)", // 13: MMMEd
u"13日(水)", // 14: Ed
u"23:58:59.123", // 15: jmmssSSS
u"23:58", // 16: JJmm
};
UnicodeString* patternResults[] = {
patternResults_en_US, // 0
patternResults_en_US_japanese, // 1
patternResults_de_DE, // 2
patternResults_fi, // 3
patternResults_es, // 4
patternResults_ja, // 5
patternResults_ja_japanese, // 6
patternResults_zh_Hans_CN, // 7
patternResults_zh_TW_roc, // 8
patternResults_ru, // 9
patternResults_zh_chinese, // 10
patternResults_ja_jp_traditional, // 11
};
UnicodeString patternTests2[] = {
@ -635,6 +691,7 @@ void IntlTestDateTimePatternGeneratorAPI::testAPI(/*char *par*/)
UDate testDate= LocaleTest::date(99, 0, 13, 23, 58, 59) + 123.0;
while (localeIndex < MAX_LOCALE )
{
resultIndex=0;
int32_t dataIndex=0;
UnicodeString bestPattern;
@ -653,9 +710,12 @@ void IntlTestDateTimePatternGeneratorAPI::testAPI(/*char *par*/)
SimpleDateFormat sdf(bestPattern, loc, status);
resultDate.remove();
resultDate = sdf.format(testDate, resultDate);
if ( resultDate != patternResults[resultIndex] ) {
if ( resultDate != patternResults[localeIndex][resultIndex] ) {
auto* calendar = sdf.getCalendar();
errln(UnicodeString("\nERROR: Test various skeletons[") + (dataIndex-1) + UnicodeString("], localeIndex ") + localeIndex +
UnicodeString(". Got: \"") + resultDate + UnicodeString("\" Expected: \"") + patternResults[resultIndex] + "\"" );
u". Got: \"" + resultDate +
u"\" with calendar " + calendar->getType() +
u" Expected: \"" + patternResults[localeIndex][resultIndex] + u"\"");
}
resultIndex++;
@ -1329,12 +1389,13 @@ void IntlTestDateTimePatternGeneratorAPI::testJjMapping() {
for (; *charPtr != (UChar)0; charPtr++) {
if (jPatSkeleton.indexOf(*charPtr) >= 0) {
if (shortPatSkeleton.indexOf(*charPtr) < 0) {
char jcBuf[2], spBuf[32];
char jcBuf[2], spBuf[32], jpBuf[32];
u_austrncpy(jcBuf, charPtr, 1);
jcBuf[1] = 0;
shortPattern.extract(0, shortPattern.length(), spBuf, 32);
jPattern.extract(0, jPattern.length(), jpBuf, 32);
const char* dfmtCalType = (dfmt->getCalendar())->getType();
errln("ERROR: locale %s, expected j resolved char %s to occur in short time pattern %s for %s", localeID, jcBuf, spBuf, dfmtCalType);
errln("ERROR: locale %s, expected j resolved char %s to occur in short time pattern '%s' for %s (best pattern: '%s')", localeID, jcBuf, spBuf, dfmtCalType, jpBuf);
}
break;
}
@ -1342,6 +1403,46 @@ void IntlTestDateTimePatternGeneratorAPI::testJjMapping() {
}
}
void IntlTestDateTimePatternGeneratorAPI::test20640_HourCyclArsEnNH() {
IcuTestErrorCode status(*this, "test20640_HourCyclArsEnNH");
const struct TestCase {
const char* localeName;
const char16_t* expectedDtpgPattern;
const char16_t* expectedTimePattern;
} cases[] = {
// ars is interesting because it does not have a region, but it aliases
// to ar_SA, which has a region.
{"ars", u"h a", u"h:mm a"},
// en_NH is interesting because NH is a depregated region code.
{"en_NH", u"h a", u"h:mm a"},
};
for (auto& cas : cases) {
status.setScope(cas.localeName);
Locale loc(cas.localeName);
LocalPointer<DateFormat> dtf(DateFormat::createTimeInstance(DateFormat::kShort, loc), status);
LocalPointer<DateTimePatternGenerator> dtpg(DateTimePatternGenerator::createInstance(loc, status));
if (status.errIfFailureAndReset()) {
return;
}
UnicodeString timePattern;
dynamic_cast<SimpleDateFormat*>(dtf.getAlias())->toPattern(timePattern);
UnicodeString dtpgPattern = dtpg->getBestPattern(u"j", status);
if (status.errIfFailureAndReset()) {
return;
}
assertEquals(UnicodeString("dtpgPattern ") + cas.localeName,
cas.expectedDtpgPattern, dtpgPattern);
assertEquals(UnicodeString("timePattern ") + cas.localeName,
cas.expectedTimePattern, timePattern);
}
}
void IntlTestDateTimePatternGeneratorAPI::testFallbackWithDefaultRootLocale() {
UErrorCode status = U_ZERO_ERROR;
char original[ULOC_FULLNAME_CAPACITY];

View file

@ -33,6 +33,7 @@ private:
void testSkeletonsWithDayPeriods();
void testGetFieldDisplayNames();
void testJjMapping();
void test20640_HourCyclArsEnNH();
void testFallbackWithDefaultRootLocale();
};

View file

@ -37,6 +37,7 @@ import com.ibm.icu.impl.UResource;
import com.ibm.icu.util.Calendar;
import com.ibm.icu.util.Freezable;
import com.ibm.icu.util.ICUCloneNotSupportedException;
import com.ibm.icu.util.Region;
import com.ibm.icu.util.ULocale;
import com.ibm.icu.util.ULocale.Category;
import com.ibm.icu.util.UResourceBundle;
@ -319,6 +320,15 @@ public class DateTimePatternGenerator implements Freezable<DateTimePatternGenera
private static final String[] LAST_RESORT_ALLOWED_HOUR_FORMAT = {"H"};
private String[] getAllowedHourFormatsLangCountry(String language, String country) {
String langCountry = language + "_" + country;
String[] list = LOCALE_TO_ALLOWED_HOUR.get(langCountry);
if (list == null) {
list = LOCALE_TO_ALLOWED_HOUR.get(country);
}
return list;
}
private void getAllowedHourFormats(ULocale uLocale) {
// key can be either region or locale (lang_region)
// ZW{
@ -338,23 +348,38 @@ public class DateTimePatternGenerator implements Freezable<DateTimePatternGenera
// preferred{"h"}
// }
ULocale max = ULocale.addLikelySubtags(uLocale);
String country = max.getCountry();
String language = uLocale.getLanguage();
String country = uLocale.getCountry();
if (language.isEmpty() || country.isEmpty()) {
ULocale max = ULocale.addLikelySubtags(uLocale);
language = max.getLanguage();
country = max.getCountry();
}
if (language.isEmpty()) {
// Unexpected, but fail gracefully
language = "und";
}
if (country.isEmpty()) {
country = "001";
}
String langCountry = max.getLanguage() + "_" + country;
String[] list = LOCALE_TO_ALLOWED_HOUR.get(langCountry);
String[] list = getAllowedHourFormatsLangCountry(language, country);
// Check if the region has an alias
if (list == null) {
list = LOCALE_TO_ALLOWED_HOUR.get(country);
Region region = Region.getInstance(country);
country = region.toString();
list = getAllowedHourFormatsLangCountry(language, country);
}
if (list != null) {
defaultHourFormatChar = list[0].charAt(0);
allowedHourFormats = Arrays.copyOfRange(list, 1, list.length - 1);
} else {
allowedHourFormats = LAST_RESORT_ALLOWED_HOUR_FORMAT;
defaultHourFormatChar = allowedHourFormats[0].charAt(0);
}
if (list != null) {
defaultHourFormatChar = list[0].charAt(0);
allowedHourFormats = Arrays.copyOfRange(list, 1, list.length-1);
} else {
allowedHourFormats = LAST_RESORT_ALLOWED_HOUR_FORMAT;
defaultHourFormatChar = allowedHourFormats[0].charAt(0);
}
}
private static class DayPeriodAllowedHoursSink extends UResource.Sink {

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:c7156a5d2863005845db80b4bee68a1b7ad078af805e166897f07fdcf2c09f62
size 12841498
oid sha256:a502f64af80cdac743cbf045a3aa89c4b3c08d54980e38e79082c2a647325877
size 12841510

View file

@ -576,6 +576,26 @@ public class DateTimeGeneratorTest extends TestFmwk {
new String[] {"Ed", "26\u65E5\u5468\u4E09"},
new String[] {"jmmssSSS", "\u4E0B\u534811:58:59.123"},
new String[] {"JJmm", "11:58"},
new ULocale("ja_JP_TRADITIONAL"),
// TODO: This is different in C++ and Java.
new String[] {"yM", "1999/1",},
new String[] {"yMMM", "1999年1月"},
new String[] {"yMd", "1999/1/13"},
new String[] {"yMMMd", "1999年1月13日"},
new String[] {"Md", "1/13"},
new String[] {"MMMd", "1月13日"},
new String[] {"MMMMd", "1月13日"},
new String[] {"yQQQ", "1999/Q1"},
new String[] {"hhmm", "午後11:58"},
new String[] {"HHmm", "23:58"},
new String[] {"jjmm", "23:58"},
new String[] {"mmss", "58:59"},
new String[] {"yyyyMMMM", "1999年1月"},
new String[] {"MMMEd", "1月13日(水)"},
new String[] {"Ed", "13日(水)"},
new String[] {"jmmssSSS", "23:58:59.123"},
new String[] {"JJmm", "23:58"}
};
@Test
@ -1707,4 +1727,29 @@ public class DateTimeGeneratorTest extends TestFmwk {
}
}
}
@Test
public void test20640_HourCyclArsEnNH() {
String[][] cases = new String[][]{
// ars is interesting because it does not have a region, but it aliases
// to ar_SA, which has a region.
{"ars", "h a", "h:mm a"},
// en_NH is interesting because NH is a depregated region code.
{"en_NH", "h a", "h:mm a"},
};
for (String[] cas : cases) {
ULocale loc = new ULocale(cas[0]);
DateFormat dtf = DateFormat.getTimeInstance(DateFormat.SHORT, loc);
DateTimePatternGenerator dtpg = DateTimePatternGenerator.getInstance(loc);
String timePattern = ((SimpleDateFormat)dtf).toPattern();
String dtpgPattern = dtpg.getBestPattern("j");
assertEquals("dtpgPattern " + cas[0],
cas[1], dtpgPattern);
assertEquals("timePattern " + cas[1],
cas[2], timePattern);
}
}
}