mirror of
https://github.com/unicode-org/icu.git
synced 2025-04-14 17:24:01 +00:00
ICU-13183 to trunk - Handle abB as normal fields (force add/remove per HhKk),\nhandle field lengths for skel metachars jC, add loc data with Bh skels, other fixes
X-SVN-Rev: 40129
This commit is contained in:
commit
1b2cc7d1fb
11 changed files with 544 additions and 180 deletions
|
@ -633,7 +633,12 @@ en{
|
|||
Year{"{0} {1}"}
|
||||
}
|
||||
availableFormats{
|
||||
Bh{"h B"}
|
||||
Bhm{"h:mm B"}
|
||||
Bhms{"h:mm:ss B"}
|
||||
E{"ccc"}
|
||||
EBhm{"E h:mm B"}
|
||||
EBhms{"E h:mm:ss B"}
|
||||
EHm{"E HH:mm"}
|
||||
EHms{"E HH:mm:ss"}
|
||||
Ed{"d E"}
|
||||
|
|
|
@ -966,7 +966,12 @@ root{
|
|||
Year{"{1} {0}"}
|
||||
}
|
||||
availableFormats{
|
||||
Bh{"h B"}
|
||||
Bhm{"h:mm B"}
|
||||
Bhms{"h:mm:ss B"}
|
||||
E{"ccc"}
|
||||
EBhm{"E h:mm B"}
|
||||
EBhms{"E h:mm:ss B"}
|
||||
EHm{"E HH:mm"}
|
||||
EHms{"E HH:mm:ss"}
|
||||
Ed{"d, E"}
|
||||
|
|
|
@ -1133,7 +1133,12 @@ zh{
|
|||
Timezone{"{1}{0}"}
|
||||
}
|
||||
availableFormats{
|
||||
Bh{"Bh时"}
|
||||
Bhm{"Bh:mm"}
|
||||
Bhms{"Bh:mm:ss"}
|
||||
E{"ccc"}
|
||||
EBhm{"EBh:mm"}
|
||||
EBms{"EBh:mm:ss"}
|
||||
EHm{"EHH:mm"}
|
||||
EHms{"EHH:mm:ss"}
|
||||
Ed{"d日E"}
|
||||
|
|
|
@ -134,15 +134,18 @@ U_NAMESPACE_BEGIN
|
|||
// class DateTimePatternGenerator
|
||||
// *****************************************************************************
|
||||
static const UChar Canonical_Items[] = {
|
||||
// GyQMwWEdDFHmsSv
|
||||
CAP_G, LOW_Y, CAP_Q, CAP_M, LOW_W, CAP_W, CAP_E, LOW_D, CAP_D, CAP_F,
|
||||
// GyQMwWEDFdaHmsSv
|
||||
CAP_G, LOW_Y, CAP_Q, CAP_M, LOW_W, CAP_W, CAP_E,
|
||||
CAP_D, CAP_F, LOW_D, LOW_A, // The UDATPG_x_FIELD constants and these fields have a different order than in ICU4J
|
||||
CAP_H, LOW_M, LOW_S, CAP_S, LOW_V, 0
|
||||
};
|
||||
|
||||
static const dtTypeElem dtTypes[] = {
|
||||
// patternChar, field, type, minLen, weight
|
||||
{CAP_G, UDATPG_ERA_FIELD, DT_SHORT, 1, 3,},
|
||||
{CAP_G, UDATPG_ERA_FIELD, DT_LONG, 4, 0},
|
||||
{CAP_G, UDATPG_ERA_FIELD, DT_LONG, 4, 0},
|
||||
{CAP_G, UDATPG_ERA_FIELD, DT_NARROW, 5, 0},
|
||||
|
||||
{LOW_Y, UDATPG_YEAR_FIELD, DT_NUMERIC, 1, 20},
|
||||
{CAP_Y, UDATPG_YEAR_FIELD, DT_NUMERIC + DT_DELTA, 1, 20},
|
||||
{LOW_U, UDATPG_YEAR_FIELD, DT_NUMERIC + 2*DT_DELTA, 1, 20},
|
||||
|
@ -150,12 +153,16 @@ static const dtTypeElem dtTypes[] = {
|
|||
{CAP_U, UDATPG_YEAR_FIELD, DT_SHORT, 1, 3},
|
||||
{CAP_U, UDATPG_YEAR_FIELD, DT_LONG, 4, 0},
|
||||
{CAP_U, UDATPG_YEAR_FIELD, DT_NARROW, 5, 0},
|
||||
|
||||
{CAP_Q, UDATPG_QUARTER_FIELD, DT_NUMERIC, 1, 2},
|
||||
{CAP_Q, UDATPG_QUARTER_FIELD, DT_SHORT, 3, 0},
|
||||
{CAP_Q, UDATPG_QUARTER_FIELD, DT_LONG, 4, 0},
|
||||
{CAP_Q, UDATPG_QUARTER_FIELD, DT_NARROW, 5, 0},
|
||||
{LOW_Q, UDATPG_QUARTER_FIELD, DT_NUMERIC + DT_DELTA, 1, 2},
|
||||
{LOW_Q, UDATPG_QUARTER_FIELD, DT_SHORT + DT_DELTA, 3, 0},
|
||||
{LOW_Q, UDATPG_QUARTER_FIELD, DT_LONG + DT_DELTA, 4, 0},
|
||||
{LOW_Q, UDATPG_QUARTER_FIELD, DT_SHORT - DT_DELTA, 3, 0},
|
||||
{LOW_Q, UDATPG_QUARTER_FIELD, DT_LONG - DT_DELTA, 4, 0},
|
||||
{LOW_Q, UDATPG_QUARTER_FIELD, DT_NARROW - DT_DELTA, 5, 0},
|
||||
|
||||
{CAP_M, UDATPG_MONTH_FIELD, DT_NUMERIC, 1, 2},
|
||||
{CAP_M, UDATPG_MONTH_FIELD, DT_SHORT, 3, 0},
|
||||
{CAP_M, UDATPG_MONTH_FIELD, DT_LONG, 4, 0},
|
||||
|
@ -165,32 +172,66 @@ static const dtTypeElem dtTypes[] = {
|
|||
{CAP_L, UDATPG_MONTH_FIELD, DT_LONG - DT_DELTA, 4, 0},
|
||||
{CAP_L, UDATPG_MONTH_FIELD, DT_NARROW - DT_DELTA, 5, 0},
|
||||
{LOW_L, UDATPG_MONTH_FIELD, DT_NUMERIC + DT_DELTA, 1, 1},
|
||||
|
||||
{LOW_W, UDATPG_WEEK_OF_YEAR_FIELD, DT_NUMERIC, 1, 2},
|
||||
{CAP_W, UDATPG_WEEK_OF_MONTH_FIELD, DT_NUMERIC + DT_DELTA, 1, 0},
|
||||
|
||||
{CAP_W, UDATPG_WEEK_OF_MONTH_FIELD, DT_NUMERIC, 1, 0},
|
||||
|
||||
{CAP_E, UDATPG_WEEKDAY_FIELD, DT_SHORT, 1, 3},
|
||||
{CAP_E, UDATPG_WEEKDAY_FIELD, DT_LONG, 4, 0},
|
||||
{CAP_E, UDATPG_WEEKDAY_FIELD, DT_NARROW, 5, 0},
|
||||
{CAP_E, UDATPG_WEEKDAY_FIELD, DT_SHORTER, 6, 0},
|
||||
{LOW_C, UDATPG_WEEKDAY_FIELD, DT_NUMERIC + 2*DT_DELTA, 1, 2},
|
||||
{LOW_C, UDATPG_WEEKDAY_FIELD, DT_SHORT - 2*DT_DELTA, 3, 0},
|
||||
{LOW_C, UDATPG_WEEKDAY_FIELD, DT_LONG - 2*DT_DELTA, 4, 0},
|
||||
{LOW_C, UDATPG_WEEKDAY_FIELD, DT_NARROW - 2*DT_DELTA, 5, 0},
|
||||
{LOW_C, UDATPG_WEEKDAY_FIELD, DT_SHORTER - 2*DT_DELTA, 6, 0},
|
||||
{LOW_E, UDATPG_WEEKDAY_FIELD, DT_NUMERIC + DT_DELTA, 1, 2}, // LOW_E is currently not used in CLDR data, should not be canonical
|
||||
{LOW_E, UDATPG_WEEKDAY_FIELD, DT_SHORT - DT_DELTA, 3, 0},
|
||||
{LOW_E, UDATPG_WEEKDAY_FIELD, DT_LONG - DT_DELTA, 4, 0},
|
||||
{LOW_E, UDATPG_WEEKDAY_FIELD, DT_NARROW - DT_DELTA, 5, 0},
|
||||
{LOW_E, UDATPG_WEEKDAY_FIELD, DT_SHORTER - DT_DELTA, 6, 0},
|
||||
|
||||
{LOW_D, UDATPG_DAY_FIELD, DT_NUMERIC, 1, 2},
|
||||
{CAP_D, UDATPG_DAY_OF_YEAR_FIELD, DT_NUMERIC + DT_DELTA, 1, 3},
|
||||
{CAP_F, UDATPG_DAY_OF_WEEK_IN_MONTH_FIELD, DT_NUMERIC + 2*DT_DELTA, 1, 0},
|
||||
{LOW_G, UDATPG_DAY_FIELD, DT_NUMERIC + 3*DT_DELTA, 1, 20}, // really internal use, so we don't care
|
||||
{LOW_A, UDATPG_DAYPERIOD_FIELD, DT_SHORT, 1, 0},
|
||||
{LOW_G, UDATPG_DAY_FIELD, DT_NUMERIC + DT_DELTA, 1, 20}, // really internal use, so we don't care
|
||||
|
||||
{CAP_D, UDATPG_DAY_OF_YEAR_FIELD, DT_NUMERIC, 1, 3},
|
||||
|
||||
{CAP_F, UDATPG_DAY_OF_WEEK_IN_MONTH_FIELD, DT_NUMERIC, 1, 0},
|
||||
|
||||
{LOW_A, UDATPG_DAYPERIOD_FIELD, DT_SHORT, 1, 3},
|
||||
{LOW_A, UDATPG_DAYPERIOD_FIELD, DT_LONG, 4, 0},
|
||||
{LOW_A, UDATPG_DAYPERIOD_FIELD, DT_NARROW, 5, 0},
|
||||
{LOW_B, UDATPG_DAYPERIOD_FIELD, DT_SHORT - DT_DELTA, 1, 3},
|
||||
{LOW_B, UDATPG_DAYPERIOD_FIELD, DT_LONG - DT_DELTA, 4, 0},
|
||||
{LOW_B, UDATPG_DAYPERIOD_FIELD, DT_NARROW - DT_DELTA, 5, 0},
|
||||
// b needs to be closer to a than to B, so we make this 3*DT_DELTA
|
||||
{CAP_B, UDATPG_DAYPERIOD_FIELD, DT_SHORT - 3*DT_DELTA, 1, 3},
|
||||
{CAP_B, UDATPG_DAYPERIOD_FIELD, DT_LONG - 3*DT_DELTA, 4, 0},
|
||||
{CAP_B, UDATPG_DAYPERIOD_FIELD, DT_NARROW - 3*DT_DELTA, 5, 0},
|
||||
|
||||
{CAP_H, UDATPG_HOUR_FIELD, DT_NUMERIC + 10*DT_DELTA, 1, 2}, // 24 hour
|
||||
{LOW_K, UDATPG_HOUR_FIELD, DT_NUMERIC + 11*DT_DELTA, 1, 2}, // 24 hour
|
||||
{LOW_H, UDATPG_HOUR_FIELD, DT_NUMERIC, 1, 2}, // 12 hour
|
||||
{CAP_K, UDATPG_HOUR_FIELD, DT_NUMERIC + DT_DELTA, 1, 2}, // 12 hour
|
||||
// The C code has had versions of the following 3, keep & update. Should not need these, but...
|
||||
// Without these, certain tests using e.g. staticGetSkeleton fail because j/J in patterns
|
||||
// get skipped instead of mapped to the right hour chars, for example in
|
||||
// DateFormatTest::TestPatternFromSkeleton
|
||||
// IntlTestDateTimePatternGeneratorAPI:: testStaticGetSkeleton
|
||||
// DateIntervalFormatTest::testTicket11985
|
||||
// Need to investigate better handling of jJC replacement e.g. in staticGetSkeleton.
|
||||
{CAP_J, UDATPG_HOUR_FIELD, DT_NUMERIC + 5*DT_DELTA, 1, 2}, // 12/24 hour no AM/PM
|
||||
{LOW_J, UDATPG_HOUR_FIELD, DT_NUMERIC + 6*DT_DELTA, 1, 6}, // 12/24 hour
|
||||
{CAP_C, UDATPG_HOUR_FIELD, DT_NUMERIC + 7*DT_DELTA, 1, 6}, // 12/24 hour with preferred dayPeriods for 12
|
||||
|
||||
{LOW_M, UDATPG_MINUTE_FIELD, DT_NUMERIC, 1, 2},
|
||||
|
||||
{LOW_S, UDATPG_SECOND_FIELD, DT_NUMERIC, 1, 2},
|
||||
{CAP_S, UDATPG_FRACTIONAL_SECOND_FIELD, DT_NUMERIC + DT_DELTA, 1, 1000},
|
||||
{CAP_A, UDATPG_SECOND_FIELD, DT_NUMERIC + 2*DT_DELTA, 1, 1000},
|
||||
{CAP_A, UDATPG_SECOND_FIELD, DT_NUMERIC + DT_DELTA, 1, 1000},
|
||||
|
||||
{CAP_S, UDATPG_FRACTIONAL_SECOND_FIELD, DT_NUMERIC, 1, 1000},
|
||||
|
||||
{LOW_V, UDATPG_ZONE_FIELD, DT_SHORT - 2*DT_DELTA, 1, 0},
|
||||
{LOW_V, UDATPG_ZONE_FIELD, DT_LONG - 2*DT_DELTA, 4, 0},
|
||||
{LOW_Z, UDATPG_ZONE_FIELD, DT_SHORT, 1, 3},
|
||||
|
@ -202,24 +243,27 @@ static const dtTypeElem dtTypes[] = {
|
|||
{CAP_O, UDATPG_ZONE_FIELD, DT_LONG - DT_DELTA, 4, 0},
|
||||
{CAP_V, UDATPG_ZONE_FIELD, DT_SHORT - DT_DELTA, 1, 0},
|
||||
{CAP_V, UDATPG_ZONE_FIELD, DT_LONG - DT_DELTA, 2, 0},
|
||||
{CAP_V, UDATPG_ZONE_FIELD, DT_LONG-1 - DT_DELTA, 3, 0},
|
||||
{CAP_V, UDATPG_ZONE_FIELD, DT_LONG-2 - DT_DELTA, 4, 0},
|
||||
{CAP_X, UDATPG_ZONE_FIELD, DT_NARROW - DT_DELTA, 1, 0},
|
||||
{CAP_X, UDATPG_ZONE_FIELD, DT_SHORT - DT_DELTA, 2, 0},
|
||||
{CAP_X, UDATPG_ZONE_FIELD, DT_LONG - DT_DELTA, 4, 0},
|
||||
{LOW_X, UDATPG_ZONE_FIELD, DT_NARROW - DT_DELTA, 1, 0},
|
||||
{LOW_X, UDATPG_ZONE_FIELD, DT_SHORT - DT_DELTA, 2, 0},
|
||||
{LOW_X, UDATPG_ZONE_FIELD, DT_LONG - DT_DELTA, 4, 0},
|
||||
{LOW_J, UDATPG_HOUR_FIELD, DT_NUMERIC, 1, 2}, // 12/24 hour
|
||||
{CAP_J, UDATPG_HOUR_FIELD, DT_NUMERIC, 1, 2}, // 12/24 hour no AM/PM
|
||||
|
||||
{0, UDATPG_FIELD_COUNT, 0, 0, 0} , // last row of dtTypes[]
|
||||
};
|
||||
|
||||
static const char* const CLDR_FIELD_APPEND[] = {
|
||||
"Era", "Year", "Quarter", "Month", "Week", "*", "Day-Of-Week", "Day", "*", "*", "*",
|
||||
"Era", "Year", "Quarter", "Month", "Week", "*", "Day-Of-Week",
|
||||
"*", "*", "Day", "*", // The UDATPG_x_FIELD constants and these fields have a different order than in ICU4J
|
||||
"Hour", "Minute", "Second", "*", "Timezone"
|
||||
};
|
||||
|
||||
static const char* const CLDR_FIELD_NAME[] = {
|
||||
"era", "year", "quarter", "month", "week", "*", "weekday", "*", "*", "day", "dayperiod",
|
||||
"era", "year", "quarter", "month", "week", "weekOfMonth", "weekday",
|
||||
"dayOfYear", "weekdayOfMonth", "day", "dayperiod", // The UDATPG_x_FIELD constants and these fields have a different order than in ICU4J
|
||||
"hour", "minute", "second", "*", "zone"
|
||||
};
|
||||
|
||||
|
@ -963,47 +1007,13 @@ DateTimePatternGenerator::getBestPattern(const UnicodeString& patternForm, UDate
|
|||
int32_t timeMask=(1<<UDATPG_FIELD_COUNT) - 1 - dateMask;
|
||||
|
||||
// Replace hour metacharacters 'j', 'C' and 'J', set flags as necessary
|
||||
UnicodeString patternFormCopy = UnicodeString(patternForm);
|
||||
int32_t patPos, patLen = patternFormCopy.length();
|
||||
UBool inQuoted = FALSE;
|
||||
for (patPos = 0; patPos < patLen; patPos++) {
|
||||
UChar patChr = patternFormCopy.charAt(patPos);
|
||||
if (patChr == SINGLE_QUOTE) {
|
||||
inQuoted = !inQuoted;
|
||||
} else if (!inQuoted) {
|
||||
if (patChr == LOW_J) {
|
||||
patternFormCopy.setCharAt(patPos, fDefaultHourFormatChar);
|
||||
} else if (patChr == CAP_C) {
|
||||
AllowedHourFormat preferred;
|
||||
if (fAllowedHourFormats[0] != ALLOWED_HOUR_FORMAT_UNKNOWN) {
|
||||
preferred = (AllowedHourFormat)fAllowedHourFormats[0];
|
||||
} else {
|
||||
status = U_INVALID_FORMAT_ERROR;
|
||||
return UnicodeString();
|
||||
}
|
||||
|
||||
if (preferred == ALLOWED_HOUR_FORMAT_H || preferred == ALLOWED_HOUR_FORMAT_HB || preferred == ALLOWED_HOUR_FORMAT_Hb) {
|
||||
patternFormCopy.setCharAt(patPos, CAP_H);
|
||||
} else {
|
||||
patternFormCopy.setCharAt(patPos, LOW_H);
|
||||
}
|
||||
|
||||
if (preferred == ALLOWED_HOUR_FORMAT_HB || preferred == ALLOWED_HOUR_FORMAT_hB) {
|
||||
flags |= kDTPGSkeletonUsesCapB;
|
||||
} else if (preferred == ALLOWED_HOUR_FORMAT_Hb || preferred == ALLOWED_HOUR_FORMAT_hb) {
|
||||
flags |= kDTPGSkeletonUsesLowB;
|
||||
}
|
||||
} else if (patChr == CAP_J) {
|
||||
// Get pattern for skeleton with H, then replace H or k
|
||||
// with fDefaultHourFormatChar (if different)
|
||||
patternFormCopy.setCharAt(patPos, CAP_H);
|
||||
flags |= kDTPGSkeletonUsesCapJ;
|
||||
}
|
||||
}
|
||||
UnicodeString patternFormMapped = mapSkeletonMetacharacters(patternForm, &flags, status);
|
||||
if (U_FAILURE(status)) {
|
||||
return UnicodeString();
|
||||
}
|
||||
|
||||
resultPattern.remove();
|
||||
dtMatcher->set(patternFormCopy, fp);
|
||||
dtMatcher->set(patternFormMapped, fp);
|
||||
const PtnSkeleton* specifiedSkeleton=NULL;
|
||||
bestPattern=getBestRaw(*dtMatcher, -1, distanceInfo, &specifiedSkeleton);
|
||||
if ( distanceInfo->missingFieldMask==0 && distanceInfo->extraFieldMask==0 ) {
|
||||
|
@ -1032,6 +1042,82 @@ DateTimePatternGenerator::getBestPattern(const UnicodeString& patternForm, UDate
|
|||
return resultPattern;
|
||||
}
|
||||
|
||||
/*
|
||||
* Map a skeleton that may have metacharacters jJC to one without, by replacing
|
||||
* the metacharacters with locale-appropriate fields of of h/H/k/K and of a/b/B
|
||||
* (depends on fDefaultHourFormatChar and fAllowedHourFormats being set, which in
|
||||
* turn depends on initData having been run). This method also updates the flags
|
||||
* as necessary. Returns the updated skeleton.
|
||||
*/
|
||||
UnicodeString
|
||||
DateTimePatternGenerator::mapSkeletonMetacharacters(const UnicodeString& patternForm, int32_t* flags, UErrorCode& status) {
|
||||
UnicodeString patternFormMapped;
|
||||
patternFormMapped.remove();
|
||||
UBool inQuoted = FALSE;
|
||||
int32_t patPos, patLen = patternForm.length();
|
||||
for (patPos = 0; patPos < patLen; patPos++) {
|
||||
UChar patChr = patternForm.charAt(patPos);
|
||||
if (patChr == SINGLE_QUOTE) {
|
||||
inQuoted = !inQuoted;
|
||||
} else if (!inQuoted) {
|
||||
// Handle special mappings for 'j' and 'C' in which fields lengths
|
||||
// 1,3,5 => hour field length 1
|
||||
// 2,4,6 => hour field length 2
|
||||
// 1,2 => abbreviated dayPeriod (field length 1..3)
|
||||
// 3,4 => long dayPeriod (field length 4)
|
||||
// 5,6 => narrow dayPeriod (field length 5)
|
||||
if (patChr == LOW_J || patChr == CAP_C) {
|
||||
int32_t extraLen = 0; // 1 less than total field length
|
||||
while (patPos+1 < patLen && patternForm.charAt(patPos+1)==patChr) {
|
||||
extraLen++;
|
||||
patPos++;
|
||||
}
|
||||
int32_t hourLen = 1 + (extraLen & 1);
|
||||
int32_t dayPeriodLen = (extraLen < 2)? 1: 3 + (extraLen >> 1);
|
||||
UChar hourChar = LOW_H;
|
||||
UChar dayPeriodChar = LOW_A;
|
||||
if (patChr == LOW_J) {
|
||||
hourChar = fDefaultHourFormatChar;
|
||||
} else {
|
||||
AllowedHourFormat preferred;
|
||||
if (fAllowedHourFormats[0] != ALLOWED_HOUR_FORMAT_UNKNOWN) {
|
||||
preferred = (AllowedHourFormat)fAllowedHourFormats[0];
|
||||
} else {
|
||||
status = U_INVALID_FORMAT_ERROR;
|
||||
return UnicodeString();
|
||||
}
|
||||
if (preferred == ALLOWED_HOUR_FORMAT_H || preferred == ALLOWED_HOUR_FORMAT_HB || preferred == ALLOWED_HOUR_FORMAT_Hb) {
|
||||
hourChar = CAP_H;
|
||||
}
|
||||
// in #13183 just add b/B to skeleton, no longer need to set special flags
|
||||
if (preferred == ALLOWED_HOUR_FORMAT_HB || preferred == ALLOWED_HOUR_FORMAT_hB) {
|
||||
dayPeriodChar = CAP_B;
|
||||
} else if (preferred == ALLOWED_HOUR_FORMAT_Hb || preferred == ALLOWED_HOUR_FORMAT_hb) {
|
||||
dayPeriodChar = LOW_B;
|
||||
}
|
||||
}
|
||||
if (hourChar==CAP_H || hourChar==LOW_K) {
|
||||
dayPeriodLen = 0;
|
||||
}
|
||||
while (dayPeriodLen-- > 0) {
|
||||
patternFormMapped.append(dayPeriodChar);
|
||||
}
|
||||
while (hourLen-- > 0) {
|
||||
patternFormMapped.append(hourChar);
|
||||
}
|
||||
} else if (patChr == CAP_J) {
|
||||
// Get pattern for skeleton with H, then replace H or k
|
||||
// with fDefaultHourFormatChar (if different)
|
||||
patternFormMapped.append(CAP_H);
|
||||
*flags |= kDTPGSkeletonUsesCapJ;
|
||||
} else {
|
||||
patternFormMapped.append(patChr);
|
||||
}
|
||||
}
|
||||
}
|
||||
return patternFormMapped;
|
||||
}
|
||||
|
||||
UnicodeString
|
||||
DateTimePatternGenerator::replaceFieldTypes(const UnicodeString& pattern,
|
||||
const UnicodeString& skeleton,
|
||||
|
@ -1299,17 +1385,7 @@ DateTimePatternGenerator::adjustFieldTypes(const UnicodeString& pattern,
|
|||
const dtTypeElem *row = &dtTypes[canonicalIndex];
|
||||
int32_t typeValue = row->field;
|
||||
|
||||
// Handle special day periods.
|
||||
if (typeValue == UDATPG_DAYPERIOD_FIELD && flags != 0) {
|
||||
UChar c = NONE; // '0'
|
||||
if (flags & kDTPGSkeletonUsesCapB) { c = CAP_B; }
|
||||
if (flags & kDTPGSkeletonUsesLowB) { c = LOW_B; }
|
||||
|
||||
if (c != NONE) {
|
||||
for (int32_t i = 0; i < field.length(); ++i)
|
||||
field.setCharAt(i, c);
|
||||
}
|
||||
}
|
||||
// handle day periods - with #13183, no longer need special handling here, integrated with normal types
|
||||
|
||||
if ((flags & kDTPGFixFractionalSeconds) != 0 && typeValue == UDATPG_SECOND_FIELD) {
|
||||
field += decimal;
|
||||
|
@ -1844,9 +1920,7 @@ DateTimeMatcher::set(const UnicodeString& pattern, FormatParser* fp, PtnSkeleton
|
|||
fp->set(pattern);
|
||||
for (i=0; i < fp->itemNumber; i++) {
|
||||
const UnicodeString& value = fp->items[i];
|
||||
if ( value.charAt(0) == LOW_A ) {
|
||||
continue; // skip 'a'
|
||||
}
|
||||
// don't skip 'a' anymore, dayPeriod handled specially below
|
||||
|
||||
if ( fp->isQuoteLiteral(value) ) {
|
||||
UnicodeString quoteLiteral;
|
||||
|
@ -1861,7 +1935,7 @@ DateTimeMatcher::set(const UnicodeString& pattern, FormatParser* fp, PtnSkeleton
|
|||
int32_t field = row->field;
|
||||
skeletonResult.original.populate(field, value);
|
||||
UChar repeatChar = row->patternChar;
|
||||
int32_t repeatCount = row->minLen; // #7930 removes cap at 3
|
||||
int32_t repeatCount = row->minLen;
|
||||
skeletonResult.baseOriginal.populate(field, repeatChar, repeatCount);
|
||||
int16_t subField = row->type;
|
||||
if ( row->type > 0) {
|
||||
|
@ -1869,6 +1943,29 @@ DateTimeMatcher::set(const UnicodeString& pattern, FormatParser* fp, PtnSkeleton
|
|||
}
|
||||
skeletonResult.type[field] = subField;
|
||||
}
|
||||
// #13183, handle special behavior for day period characters (a, b, B)
|
||||
if (!skeletonResult.original.isFieldEmpty(UDATPG_HOUR_FIELD)) {
|
||||
if (skeletonResult.original.getFieldChar(UDATPG_HOUR_FIELD)==LOW_H || skeletonResult.original.getFieldChar(UDATPG_HOUR_FIELD)==CAP_K) {
|
||||
// We have a skeleton with 12-hour-cycle format
|
||||
if (skeletonResult.original.isFieldEmpty(UDATPG_DAYPERIOD_FIELD)) {
|
||||
// But we do not have a day period in the skeleton; add the default DAYPERIOD (currently "a")
|
||||
for (i = 0; dtTypes[i].patternChar != 0; i++) {
|
||||
if ( dtTypes[i].field == UDATPG_DAYPERIOD_FIELD ) {
|
||||
// first entry for UDATPG_DAYPERIOD_FIELD
|
||||
skeletonResult.original.populate(UDATPG_DAYPERIOD_FIELD, dtTypes[i].patternChar, dtTypes[i].minLen);
|
||||
skeletonResult.baseOriginal.populate(UDATPG_DAYPERIOD_FIELD, dtTypes[i].patternChar, dtTypes[i].minLen);
|
||||
skeletonResult.type[UDATPG_DAYPERIOD_FIELD] = dtTypes[i].type;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Skeleton has 24-hour-cycle hour format and has dayPeriod, delete dayPeriod (i.e. ignore it)
|
||||
skeletonResult.original.clearField(UDATPG_DAYPERIOD_FIELD);
|
||||
skeletonResult.baseOriginal.clearField(UDATPG_DAYPERIOD_FIELD);
|
||||
skeletonResult.type[UDATPG_DAYPERIOD_FIELD] = NONE;
|
||||
}
|
||||
}
|
||||
copyFrom(skeletonResult);
|
||||
}
|
||||
|
||||
|
|
|
@ -92,10 +92,11 @@
|
|||
#define LOW_X ((UChar)0x0078)
|
||||
#define LOW_Y ((UChar)0x0079)
|
||||
#define LOW_Z ((UChar)0x007A)
|
||||
#define DT_SHORT -0x102
|
||||
#define DT_LONG -0x103
|
||||
#define DT_NUMERIC 0x100
|
||||
#define DT_NARROW -0x101
|
||||
#define DT_SHORTER -0x102
|
||||
#define DT_SHORT -0x103
|
||||
#define DT_LONG -0x104
|
||||
#define DT_NUMERIC 0x100
|
||||
#define DT_DELTA 0x10
|
||||
|
||||
U_NAMESPACE_BEGIN
|
||||
|
|
|
@ -206,11 +206,11 @@ public:
|
|||
* @return conflicting status. The value could be UDATPG_NO_CONFLICT,
|
||||
* UDATPG_BASE_CONFLICT or UDATPG_CONFLICT.
|
||||
* @stable ICU 3.8
|
||||
* <p>
|
||||
* <h4>Sample code</h4>
|
||||
* \snippet samples/dtptngsample/dtptngsample.cpp getBestPatternExample1
|
||||
* \snippet samples/dtptngsample/dtptngsample.cpp addPatternExample
|
||||
* <p>
|
||||
* <p>
|
||||
* <h4>Sample code</h4>
|
||||
* \snippet samples/dtptngsample/dtptngsample.cpp getBestPatternExample1
|
||||
* \snippet samples/dtptngsample/dtptngsample.cpp addPatternExample
|
||||
* <p>
|
||||
*/
|
||||
UDateTimePatternConflict addPattern(const UnicodeString& pattern,
|
||||
UBool override,
|
||||
|
@ -312,11 +312,11 @@ public:
|
|||
* @return bestPattern
|
||||
* The best pattern found from the given skeleton.
|
||||
* @stable ICU 3.8
|
||||
* <p>
|
||||
* <h4>Sample code</h4>
|
||||
* \snippet samples/dtptngsample/dtptngsample.cpp getBestPatternExample1
|
||||
* \snippet samples/dtptngsample/dtptngsample.cpp getBestPatternExample
|
||||
* <p>
|
||||
* <p>
|
||||
* <h4>Sample code</h4>
|
||||
* \snippet samples/dtptngsample/dtptngsample.cpp getBestPatternExample1
|
||||
* \snippet samples/dtptngsample/dtptngsample.cpp getBestPatternExample
|
||||
* <p>
|
||||
*/
|
||||
UnicodeString getBestPattern(const UnicodeString& skeleton, UErrorCode& status);
|
||||
|
||||
|
@ -360,11 +360,11 @@ public:
|
|||
* which must not indicate a failure before the function call.
|
||||
* @return pattern adjusted to match the skeleton fields widths and subtypes.
|
||||
* @stable ICU 3.8
|
||||
* <p>
|
||||
* <h4>Sample code</h4>
|
||||
* \snippet samples/dtptngsample/dtptngsample.cpp getBestPatternExample1
|
||||
* \snippet samples/dtptngsample/dtptngsample.cpp replaceFieldTypesExample
|
||||
* <p>
|
||||
* <p>
|
||||
* <h4>Sample code</h4>
|
||||
* \snippet samples/dtptngsample/dtptngsample.cpp getBestPatternExample1
|
||||
* \snippet samples/dtptngsample/dtptngsample.cpp replaceFieldTypesExample
|
||||
* <p>
|
||||
*/
|
||||
UnicodeString replaceFieldTypes(const UnicodeString& pattern,
|
||||
const UnicodeString& skeleton,
|
||||
|
@ -526,9 +526,8 @@ private:
|
|||
enum {
|
||||
kDTPGNoFlags = 0,
|
||||
kDTPGFixFractionalSeconds = 1,
|
||||
kDTPGSkeletonUsesCapJ = 2,
|
||||
kDTPGSkeletonUsesLowB = 3,
|
||||
kDTPGSkeletonUsesCapB = 4
|
||||
kDTPGSkeletonUsesCapJ = 2
|
||||
// with #13183, no longer need flags for b, B
|
||||
};
|
||||
|
||||
void initData(const Locale &locale, UErrorCode &status);
|
||||
|
@ -546,6 +545,7 @@ private:
|
|||
UDateTimePatternField getAppendNameNumber(const char* field) const;
|
||||
UnicodeString& getMutableAppendItemName(UDateTimePatternField field);
|
||||
void getAppendName(UDateTimePatternField field, UnicodeString& value);
|
||||
UnicodeString mapSkeletonMetacharacters(const UnicodeString& patternForm, int32_t* flags, UErrorCode& status);
|
||||
int32_t getCanonicalIndex(const UnicodeString& field);
|
||||
const UnicodeString* getBestRaw(DateTimeMatcher& source, int32_t includeMask, DistanceInfo* missingFields, const PtnSkeleton** specifiedSkeletonPtr = 0);
|
||||
UnicodeString adjustFieldTypes(const UnicodeString& pattern, const PtnSkeleton* specifiedSkeleton, int32_t flags, UDateTimePatternMatchOptions options = UDATPG_MATCH_NO_OPTIONS);
|
||||
|
|
|
@ -35,6 +35,7 @@ void IntlTestDateTimePatternGeneratorAPI::runIndexedTest( int32_t index, UBool e
|
|||
TESTCASE(2, testAllFieldPatterns);
|
||||
TESTCASE(3, testStaticGetSkeleton);
|
||||
TESTCASE(4, testC);
|
||||
TESTCASE(5, testSkeletonsWithDayPeriods);
|
||||
default: name = ""; break;
|
||||
}
|
||||
}
|
||||
|
@ -976,13 +977,17 @@ void IntlTestDateTimePatternGeneratorAPI::testAllFieldPatterns(/*char *par*/)
|
|||
{ 'e', {1,2,3,4,5,6}, "Eec" }, // local day of week
|
||||
{ 'c', {1,2,3,4,5,6}, "Eec" }, // standalone local day of week
|
||||
// day period
|
||||
// { 'a', {1,0}, "a" }, // am or pm // not clear this one is supposed to work (it doesn't)
|
||||
{ 'a', {1,2,3,4,5,0}, "a" }, // am or pm
|
||||
{ 'b', {1,2,3,4,5,0}, "b" }, // dayPeriod AM/PM/noon
|
||||
{ 'B', {1,2,3,4,5,0}, "B" }, // dayPeriod ranges
|
||||
// hour
|
||||
{ 'h', {1,2,0}, "hK" }, // 12 (1-12)
|
||||
{ 'H', {1,2,0}, "Hk" }, // 24 (0-23)
|
||||
{ 'K', {1,2,0}, "hK" }, // 12 (0-11)
|
||||
{ 'k', {1,2,0}, "Hk" }, // 24 (1-24)
|
||||
{ 'j', {1,2,0}, "hHKk" }, // locale default
|
||||
{ 'J', {1,2,0}, "hHKk" }, // locale default, without any dayPeriod
|
||||
{ 'C', {1,2,0}, "hHKk" }, // locale allowed first entry, possibly with b or B
|
||||
// minute
|
||||
{ 'm', {1,2,0}, "m" }, // x
|
||||
// second & fractions
|
||||
|
@ -1084,25 +1089,39 @@ void IntlTestDateTimePatternGeneratorAPI::testStaticGetSkeleton(/*char *par*/)
|
|||
}
|
||||
|
||||
void IntlTestDateTimePatternGeneratorAPI::testC() {
|
||||
UErrorCode status = U_ZERO_ERROR;
|
||||
const int32_t numLocales = 6;
|
||||
|
||||
const char* tests[numLocales][3] = {
|
||||
{"zh", "Cm", "Bh:mm"},
|
||||
{"de", "Cm", "HH:mm"},
|
||||
{"en", "Cm", "h:mm a"},
|
||||
{"en-BN", "Cm", "h:mm b"},
|
||||
{"gu-IN", "Cm", "h:mm B"},
|
||||
{"und-IN", "Cm", "h:mm a"},
|
||||
const char* tests[][3] = {
|
||||
// These may change with actual data for Bhmm/bhmm skeletons
|
||||
{"zh", "Cm", "Bh:mm"},
|
||||
{"zh", "CCm", "Bhh:mm"},
|
||||
{"zh", "CCCm", "BBBBh:mm"},
|
||||
{"zh", "CCCCm", "BBBBhh:mm"},
|
||||
{"zh", "CCCCCm", "BBBBBh:mm"},
|
||||
{"zh", "CCCCCCm", "BBBBBhh:mm"},
|
||||
{"de", "Cm", "HH:mm"},
|
||||
{"de", "CCm", "HH:mm"},
|
||||
{"de", "CCCm", "HH:mm"},
|
||||
{"de", "CCCCm", "HH:mm"},
|
||||
{"en", "Cm", "h:mm a"},
|
||||
{"en", "CCm", "hh:mm a"},
|
||||
{"en", "CCCm", "h:mm aaaa"},
|
||||
{"en", "CCCCm", "hh:mm aaaa"},
|
||||
{"en", "CCCCCm", "h:mm aaaaa"},
|
||||
{"en", "CCCCCCm", "hh:mm aaaaa"},
|
||||
{"en-BN", "Cm", "h:mm b"},
|
||||
{"gu-IN", "Cm", "h:mm B"},
|
||||
{"und-IN", "Cm", "h:mm a"}
|
||||
};
|
||||
|
||||
for (int32_t i = 0; i < numLocales; ++i) {
|
||||
UErrorCode status = U_ZERO_ERROR;
|
||||
int32_t numTests = UPRV_LENGTHOF(tests);
|
||||
for (int32_t i = 0; i < numTests; ++i) {
|
||||
DateTimePatternGenerator *gen = DateTimePatternGenerator::createInstance(Locale(tests[i][0]), status);
|
||||
if (gen == NULL) {
|
||||
dataerrln("FAIL: DateTimePatternGenerator::createInstance failed for %s", tests[i][0]);
|
||||
return;
|
||||
}
|
||||
UnicodeString pattern = gen->getBestPattern(tests[i][1], status);
|
||||
UDateTimePatternMatchOptions options = UDATPG_MATCH_HOUR_FIELD_LENGTH;
|
||||
UnicodeString pattern = gen->getBestPattern(tests[i][1], options, status);
|
||||
UnicodeString expectedPattern = tests[i][2];
|
||||
|
||||
char message[100] = "\0";
|
||||
|
@ -1114,4 +1133,81 @@ void IntlTestDateTimePatternGeneratorAPI::testC() {
|
|||
}
|
||||
}
|
||||
|
||||
enum { kCharBufMax = 31 };
|
||||
void IntlTestDateTimePatternGeneratorAPI::testSkeletonsWithDayPeriods() {
|
||||
const char * patterns[] = {
|
||||
// since icu4c getEmptyInstance does not call addCanonicalItems (unlike J), set these here:
|
||||
"a", // should get skeleton a
|
||||
"H", // should get skeleton H
|
||||
"m", // should get skeleton m
|
||||
"s", // should get skeleton s
|
||||
// patterns from which to construct sample data for a locale
|
||||
//"H", // should get skeleton H
|
||||
"h a", // should get skeleton ah
|
||||
"B h", // should get skeleton Bh
|
||||
};
|
||||
const char* testItems[][2] = {
|
||||
// sample requested skeletons and results
|
||||
// skel pattern
|
||||
{ "H", "H"},
|
||||
{ "HH", "HH"},
|
||||
{ "aH", "H"},
|
||||
{ "aHH", "HH"},
|
||||
{ "BH", "H"},
|
||||
{ "BHH", "HH"},
|
||||
{ "BBBBH", "H"},
|
||||
{ "h", "h a"},
|
||||
{ "hh", "hh a"},
|
||||
{ "ah", "h a"},
|
||||
{ "ahh", "hh a"},
|
||||
{ "aaaah", "h aaaa"},
|
||||
{ "aaaahh", "hh aaaa"},
|
||||
{ "bh", "h b"},
|
||||
{ "bhh", "hh b"},
|
||||
{ "bbbbh", "h bbbb"},
|
||||
{ "Bh", "B h"},
|
||||
{ "Bhh", "B hh"},
|
||||
{ "BBBBh", "BBBB h"},
|
||||
{ "BBBBhh", "BBBB hh"},
|
||||
{ "a", "a"},
|
||||
{ "aaaaa", "aaaaa"},
|
||||
{ "b", "b"},
|
||||
{ "bbbb", "bbbb"},
|
||||
{ "B", "B"},
|
||||
{ "BBBB", "BBBB"},
|
||||
};
|
||||
UErrorCode status = U_ZERO_ERROR;
|
||||
DateTimePatternGenerator *gen = DateTimePatternGenerator::createEmptyInstance(status);
|
||||
if (U_FAILURE(status)) {
|
||||
errln("ERROR: createEmptyInstance fails, status: %s", u_errorName(status));
|
||||
} else {
|
||||
int32_t i, len = UPRV_LENGTHOF(patterns);
|
||||
for (i = 0; i < len; i++) {
|
||||
UnicodeString conflictingPattern;
|
||||
(void)gen->addPattern(UnicodeString(patterns[i]), TRUE, conflictingPattern, status);
|
||||
if (U_FAILURE(status)) {
|
||||
errln("ERROR: addPattern %s fail, status: %s", patterns[i], u_errorName(status));
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (U_SUCCESS(status)) {
|
||||
len = UPRV_LENGTHOF(testItems);
|
||||
for (i = 0; i < len; i++) {
|
||||
status = U_ZERO_ERROR;
|
||||
UDateTimePatternMatchOptions options = UDATPG_MATCH_HOUR_FIELD_LENGTH;
|
||||
UnicodeString result = gen->getBestPattern(UnicodeString(testItems[i][0]), options, status);
|
||||
if (U_FAILURE(status)) {
|
||||
errln("ERROR: getBestPattern %s fail, status: %s", testItems[i][0], u_errorName(status));
|
||||
} else if (result != UnicodeString(testItems[i][1])) {
|
||||
char charResult[kCharBufMax+1];
|
||||
result.extract(0, result.length(), charResult, kCharBufMax);
|
||||
charResult[kCharBufMax] = 0; // ensure termination
|
||||
errln("ERROR: getBestPattern %s, expected %s, got %s", testItems[i][0], testItems[i][1], charResult);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
delete gen;
|
||||
}
|
||||
|
||||
#endif /* #if !UCONFIG_NO_FORMATTING */
|
||||
|
|
|
@ -30,6 +30,7 @@ private:
|
|||
void testAllFieldPatterns(/* char* par */);
|
||||
void testStaticGetSkeleton(/* char* par */);
|
||||
void testC();
|
||||
void testSkeletonsWithDayPeriods();
|
||||
};
|
||||
|
||||
#endif /* #if !UCONFIG_NO_FORMATTING */
|
||||
|
|
|
@ -549,34 +549,10 @@ public class DateTimePatternGenerator implements Freezable<DateTimePatternGenera
|
|||
private String getBestPattern(String skeleton, DateTimeMatcher skipMatcher, int options) {
|
||||
EnumSet<DTPGflags> flags = EnumSet.noneOf(DTPGflags.class);
|
||||
// Replace hour metacharacters 'j', 'C', and 'J', set flags as necessary
|
||||
StringBuilder skeletonCopy = new StringBuilder(skeleton);
|
||||
boolean inQuoted = false;
|
||||
for (int patPos = 0; patPos < skeleton.length(); patPos++) {
|
||||
char patChr = skeleton.charAt(patPos);
|
||||
if (patChr == '\'') {
|
||||
inQuoted = !inQuoted;
|
||||
} else if (!inQuoted) {
|
||||
if (patChr == 'j') {
|
||||
skeletonCopy.setCharAt(patPos, defaultHourFormatChar);
|
||||
} else if (patChr == 'C') {
|
||||
String preferred = allowedHourFormats[0];
|
||||
skeletonCopy.setCharAt(patPos, preferred.charAt(0));
|
||||
final DTPGflags alt = DTPGflags.getFlag(preferred);
|
||||
if (alt != null) {
|
||||
flags.add(alt);
|
||||
}
|
||||
} else if (patChr == 'J') {
|
||||
// Get pattern for skeleton with H, then (in adjustFieldTypes)
|
||||
// replace H or k with defaultHourFormatChar
|
||||
skeletonCopy.setCharAt(patPos, 'H');
|
||||
flags.add(DTPGflags.SKELETON_USES_CAP_J);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
String skeletonMapped = mapSkeletonMetacharacters(skeleton, flags);
|
||||
String datePattern, timePattern;
|
||||
synchronized(this) {
|
||||
current.set(skeletonCopy.toString(), fp, false);
|
||||
current.set(skeletonMapped, fp, false);
|
||||
PatternWithMatcher bestWithMatcher = getBestRaw(current, -1, _distanceInfo, skipMatcher);
|
||||
if (_distanceInfo.missingFieldMask == 0 && _distanceInfo.extraFieldMask == 0) {
|
||||
// we have a good item. Adjust the field types
|
||||
|
@ -595,6 +571,70 @@ public class DateTimePatternGenerator implements Freezable<DateTimePatternGenera
|
|||
getDateTimeFormat(), 2, 2, timePattern, datePattern);
|
||||
}
|
||||
|
||||
/*
|
||||
* Map a skeleton that may have metacharacters jJC to one without, by replacing
|
||||
* the metacharacters with locale-appropriate fields of of h/H/k/K and of a/b/B
|
||||
* (depends on defaultHourFormatChar and allowedHourFormats being set, which in
|
||||
* turn depends on initData having been run). This method also updates the flags
|
||||
* as necessary. Returns the updated skeleton.
|
||||
*/
|
||||
private String mapSkeletonMetacharacters(String skeleton, EnumSet<DTPGflags> flags) {
|
||||
StringBuilder skeletonCopy = new StringBuilder();
|
||||
boolean inQuoted = false;
|
||||
for (int patPos = 0; patPos < skeleton.length(); patPos++) {
|
||||
char patChr = skeleton.charAt(patPos);
|
||||
if (patChr == '\'') {
|
||||
inQuoted = !inQuoted;
|
||||
} else if (!inQuoted) {
|
||||
// Handle special mappings for 'j' and 'C' in which fields lengths
|
||||
// 1,3,5 => hour field length 1
|
||||
// 2,4,6 => hour field length 2
|
||||
// 1,2 => abbreviated dayPeriod (field length 1..3)
|
||||
// 3,4 => long dayPeriod (field length 4)
|
||||
// 5,6 => narrow dayPeriod (field length 5)
|
||||
if (patChr == 'j' || patChr == 'C') {
|
||||
int extraLen = 0; // 1 less than total field length
|
||||
while (patPos+1 < skeleton.length() && skeleton.charAt(patPos+1) == patChr) {
|
||||
extraLen++;
|
||||
patPos++;
|
||||
}
|
||||
int hourLen = 1 + (extraLen & 1);
|
||||
int dayPeriodLen = (extraLen < 2)? 1: 3 + (extraLen >> 1);
|
||||
char hourChar = 'h';
|
||||
char dayPeriodChar = 'a';
|
||||
if (patChr == 'j') {
|
||||
hourChar = defaultHourFormatChar;
|
||||
} else { // patChr == 'C'
|
||||
String preferred = allowedHourFormats[0];
|
||||
hourChar = preferred.charAt(0);
|
||||
// in #13183 just add b/B to skeleton, no longer need to set special flags
|
||||
char last = preferred.charAt(preferred.length()-1);
|
||||
if (last=='b' || last=='B') {
|
||||
dayPeriodChar = last;
|
||||
}
|
||||
}
|
||||
if (hourChar=='H' || hourChar=='k') {
|
||||
dayPeriodLen = 0;
|
||||
}
|
||||
while (dayPeriodLen-- > 0) {
|
||||
skeletonCopy.append(dayPeriodChar);
|
||||
}
|
||||
while (hourLen-- > 0) {
|
||||
skeletonCopy.append(hourChar);
|
||||
}
|
||||
} else if (patChr == 'J') {
|
||||
// Get pattern for skeleton with H, then (in adjustFieldTypes)
|
||||
// replace H or k with defaultHourFormatChar
|
||||
skeletonCopy.append('H');
|
||||
flags.add(DTPGflags.SKELETON_USES_CAP_J);
|
||||
} else {
|
||||
skeletonCopy.append(patChr);
|
||||
}
|
||||
}
|
||||
}
|
||||
return skeletonCopy.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* PatternInfo supplies output parameters for addPattern(...). It is used because
|
||||
* Java doesn't have real output parameters. It is treated like a struct (eg
|
||||
|
@ -1892,18 +1932,8 @@ public class DateTimePatternGenerator implements Freezable<DateTimePatternGenera
|
|||
private enum DTPGflags {
|
||||
FIX_FRACTIONAL_SECONDS,
|
||||
SKELETON_USES_CAP_J,
|
||||
SKELETON_USES_b,
|
||||
SKELETON_USES_B,
|
||||
// with #13183, no longer need flags for b, B
|
||||
;
|
||||
|
||||
public static DTPGflags getFlag(String preferred) {
|
||||
char last = preferred.charAt(preferred.length()-1);
|
||||
switch (last) {
|
||||
case 'b' : return SKELETON_USES_b;
|
||||
case 'B' : return SKELETON_USES_B;
|
||||
default: return null;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private String adjustFieldTypes(PatternWithMatcher patternWithMatcher, DateTimeMatcher inputRequest, EnumSet<DTPGflags> flags, int options) {
|
||||
|
@ -1923,18 +1953,7 @@ public class DateTimePatternGenerator implements Freezable<DateTimePatternGenera
|
|||
// int type = types[canonicalIndex][1];
|
||||
int type = variableField.getType();
|
||||
|
||||
// handle special day periods
|
||||
if (type == DAYPERIOD
|
||||
&& !flags.isEmpty()) {
|
||||
char c = flags.contains(DTPGflags.SKELETON_USES_b) ? 'b' : flags.contains(DTPGflags.SKELETON_USES_B) ? 'B' : '0';
|
||||
if (c != '0') {
|
||||
int len = fieldBuilder.length();
|
||||
fieldBuilder.setLength(0);
|
||||
for (int i = len; i > 0; --i) {
|
||||
fieldBuilder.append(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
// handle day periods - with #13183, no longer need special handling here, integrated with normal types
|
||||
|
||||
if (flags.contains(DTPGflags.FIX_FRACTIONAL_SECONDS) && type == SECOND) {
|
||||
fieldBuilder.append(decimal);
|
||||
|
@ -2054,8 +2073,8 @@ public class DateTimePatternGenerator implements Freezable<DateTimePatternGenera
|
|||
};
|
||||
|
||||
private static final String[] CLDR_FIELD_NAME = {
|
||||
"era", "year", "*", "month", "week", "*", "weekday",
|
||||
"day", "*", "*", "dayperiod",
|
||||
"era", "year", "quarter", "month", "week", "weekOfMonth", "weekday",
|
||||
"day", "dayOfYear", "weekdayOfMonth", "dayperiod",
|
||||
"hour", "minute", "second", "*", "zone"
|
||||
};
|
||||
|
||||
|
@ -2068,10 +2087,30 @@ public class DateTimePatternGenerator implements Freezable<DateTimePatternGenera
|
|||
|
||||
private static final String[] CANONICAL_ITEMS = {
|
||||
"G", "y", "Q", "M", "w", "W", "E",
|
||||
"d", "D", "F",
|
||||
"d", "D", "F", "a",
|
||||
"H", "m", "s", "S", "v"
|
||||
};
|
||||
|
||||
// canon DateTimePatternGen CLDR fields
|
||||
// char field bundle key
|
||||
// ---- -------------------- ----------------
|
||||
// 'G', // 0 ERA "era"
|
||||
// 'y', // 1 YEAR "year"
|
||||
// 'Q', // 2 QUARTER "quarter"
|
||||
// 'M', // 3 MONTH "month"
|
||||
// 'w', // 4 WEEK_OF_YEAR, "week"
|
||||
// 'W', // 5 WEEK_OF_MONTH "weekOfMonth"
|
||||
// 'E', // 6 WEEKDAY "weekday"
|
||||
// 'd', // 7 DAY "day"
|
||||
// 'D', // 8 DAY_OF_YEAR "dayOfYear"
|
||||
// 'F', // 9 DAY_OF_WEEK_IN_MONTH "weekdayOfMonth"
|
||||
// 'a', // 10 DAYPERIOD "dayperiod"
|
||||
// 'H', // 11 HOUR "hour"
|
||||
// 'm', // 12 MINUTE "minute"
|
||||
// 's', // 13 SECOND "second"
|
||||
// 'S', // 14 FRACTIONAL_SECOND
|
||||
// 'v', // 15 ZONE "zone"
|
||||
|
||||
private static final Set<String> CANONICAL_SET = new HashSet<String>(Arrays.asList(CANONICAL_ITEMS));
|
||||
private Set<String> cldrAvailableFormatKeys = new HashSet<String>(20);
|
||||
|
||||
|
@ -2084,8 +2123,9 @@ public class DateTimePatternGenerator implements Freezable<DateTimePatternGenera
|
|||
NUMERIC = 0x100,
|
||||
NONE = 0,
|
||||
NARROW = -0x101,
|
||||
SHORT = -0x102,
|
||||
LONG = -0x103,
|
||||
SHORTER = -0x102,
|
||||
SHORT = -0x103,
|
||||
LONG = -0x104,
|
||||
EXTRA_FIELD = 0x10000,
|
||||
MISSING_FIELD = 0x1000;
|
||||
|
||||
|
@ -2156,6 +2196,7 @@ public class DateTimePatternGenerator implements Freezable<DateTimePatternGenera
|
|||
// pattern character, main type, weight, min length, weight
|
||||
{'G', ERA, SHORT, 1, 3},
|
||||
{'G', ERA, LONG, 4},
|
||||
{'G', ERA, NARROW, 5},
|
||||
|
||||
{'y', YEAR, NUMERIC, 1, 20},
|
||||
{'Y', YEAR, NUMERIC + DELTA, 1, 20},
|
||||
|
@ -2168,10 +2209,11 @@ public class DateTimePatternGenerator implements Freezable<DateTimePatternGenera
|
|||
{'Q', QUARTER, NUMERIC, 1, 2},
|
||||
{'Q', QUARTER, SHORT, 3},
|
||||
{'Q', QUARTER, LONG, 4},
|
||||
|
||||
{'Q', QUARTER, NARROW, 5},
|
||||
{'q', QUARTER, NUMERIC + DELTA, 1, 2},
|
||||
{'q', QUARTER, SHORT + DELTA, 3},
|
||||
{'q', QUARTER, LONG + DELTA, 4},
|
||||
{'q', QUARTER, SHORT - DELTA, 3},
|
||||
{'q', QUARTER, LONG - DELTA, 4},
|
||||
{'q', QUARTER, NARROW - DELTA, 5},
|
||||
|
||||
{'M', MONTH, NUMERIC, 1, 2},
|
||||
{'M', MONTH, SHORT, 3},
|
||||
|
@ -2181,30 +2223,44 @@ public class DateTimePatternGenerator implements Freezable<DateTimePatternGenera
|
|||
{'L', MONTH, SHORT - DELTA, 3},
|
||||
{'L', MONTH, LONG - DELTA, 4},
|
||||
{'L', MONTH, NARROW - DELTA, 5},
|
||||
|
||||
{'l', MONTH, NUMERIC + DELTA, 1, 1},
|
||||
|
||||
{'w', WEEK_OF_YEAR, NUMERIC, 1, 2},
|
||||
{'W', WEEK_OF_MONTH, NUMERIC + DELTA, 1},
|
||||
|
||||
{'W', WEEK_OF_MONTH, NUMERIC, 1},
|
||||
|
||||
{'E', WEEKDAY, SHORT, 1, 3},
|
||||
{'E', WEEKDAY, LONG, 4},
|
||||
{'E', WEEKDAY, NARROW, 5},
|
||||
{'E', WEEKDAY, SHORTER, 6},
|
||||
{'c', WEEKDAY, NUMERIC + 2*DELTA, 1, 2},
|
||||
{'c', WEEKDAY, SHORT - 2*DELTA, 3},
|
||||
{'c', WEEKDAY, LONG - 2*DELTA, 4},
|
||||
{'c', WEEKDAY, NARROW - 2*DELTA, 5},
|
||||
{'c', WEEKDAY, SHORTER - 2*DELTA, 6},
|
||||
{'e', WEEKDAY, NUMERIC + DELTA, 1, 2}, // 'e' is currently not used in CLDR data, should not be canonical
|
||||
{'e', WEEKDAY, SHORT - DELTA, 3},
|
||||
{'e', WEEKDAY, LONG - DELTA, 4},
|
||||
{'e', WEEKDAY, NARROW - DELTA, 5},
|
||||
{'e', WEEKDAY, SHORTER - DELTA, 6},
|
||||
|
||||
{'d', DAY, NUMERIC, 1, 2},
|
||||
{'D', DAY_OF_YEAR, NUMERIC + DELTA, 1, 3},
|
||||
{'F', DAY_OF_WEEK_IN_MONTH, NUMERIC + 2*DELTA, 1},
|
||||
{'g', DAY, NUMERIC + 3*DELTA, 1, 20}, // really internal use, so we don't care
|
||||
{'g', DAY, NUMERIC + DELTA, 1, 20}, // really internal use, so we don't care
|
||||
|
||||
{'a', DAYPERIOD, SHORT, 1},
|
||||
{'D', DAY_OF_YEAR, NUMERIC, 1, 3},
|
||||
|
||||
{'F', DAY_OF_WEEK_IN_MONTH, NUMERIC, 1},
|
||||
|
||||
{'a', DAYPERIOD, SHORT, 1, 3},
|
||||
{'a', DAYPERIOD, LONG, 4},
|
||||
{'a', DAYPERIOD, NARROW, 5},
|
||||
{'b', DAYPERIOD, SHORT - DELTA, 1, 3},
|
||||
{'b', DAYPERIOD, LONG - DELTA, 4},
|
||||
{'b', DAYPERIOD, NARROW - DELTA, 5},
|
||||
// b needs to be closer to a than to B, so we make this 3*DELTA
|
||||
{'B', DAYPERIOD, SHORT - 3*DELTA, 1, 3},
|
||||
{'B', DAYPERIOD, LONG - 3*DELTA, 4},
|
||||
{'B', DAYPERIOD, NARROW - 3*DELTA, 5},
|
||||
|
||||
{'H', HOUR, NUMERIC + 10*DELTA, 1, 2}, // 24 hour
|
||||
{'k', HOUR, NUMERIC + 11*DELTA, 1, 2},
|
||||
|
@ -2214,8 +2270,9 @@ public class DateTimePatternGenerator implements Freezable<DateTimePatternGenera
|
|||
{'m', MINUTE, NUMERIC, 1, 2},
|
||||
|
||||
{'s', SECOND, NUMERIC, 1, 2},
|
||||
{'S', FRACTIONAL_SECOND, NUMERIC + DELTA, 1, 1000},
|
||||
{'A', SECOND, NUMERIC + 2*DELTA, 1, 1000},
|
||||
{'A', SECOND, NUMERIC + DELTA, 1, 1000},
|
||||
|
||||
{'S', FRACTIONAL_SECOND, NUMERIC, 1, 1000},
|
||||
|
||||
{'v', ZONE, SHORT - 2*DELTA, 1},
|
||||
{'v', ZONE, LONG - 2*DELTA, 4},
|
||||
|
@ -2228,6 +2285,8 @@ public class DateTimePatternGenerator implements Freezable<DateTimePatternGenera
|
|||
{'O', ZONE, LONG - DELTA, 4},
|
||||
{'V', ZONE, SHORT - DELTA, 1},
|
||||
{'V', ZONE, LONG - DELTA, 2},
|
||||
{'V', ZONE, LONG-1 - DELTA, 3},
|
||||
{'V', ZONE, LONG-2 - DELTA, 4},
|
||||
{'X', ZONE, NARROW - DELTA, 1},
|
||||
{'X', ZONE, SHORT - DELTA, 2},
|
||||
{'X', ZONE, LONG - DELTA, 4},
|
||||
|
@ -2399,7 +2458,7 @@ public class DateTimePatternGenerator implements Freezable<DateTimePatternGenera
|
|||
}
|
||||
VariableField item = (VariableField)obj;
|
||||
String value = item.toString();
|
||||
if (value.charAt(0) == 'a') continue; // skip day period, special case
|
||||
// don't skip 'a' anymore, dayPeriod handled specially below
|
||||
int canonicalIndex = item.getCanonicalIndex();
|
||||
// if (canonicalIndex < 0) {
|
||||
// throw new IllegalArgumentException("Illegal field:\t"
|
||||
|
@ -2421,13 +2480,36 @@ public class DateTimePatternGenerator implements Freezable<DateTimePatternGenera
|
|||
original.populate(field, value);
|
||||
char repeatChar = (char)row[0];
|
||||
int repeatCount = row[3];
|
||||
// #7930 removes hack to cap repeatCount at 3
|
||||
if ("GEzvQ".indexOf(repeatChar) >= 0) repeatCount = 1;
|
||||
baseOriginal.populate(field, repeatChar, repeatCount);
|
||||
int subField = row[2];
|
||||
if (subField > 0) subField += value.length();
|
||||
type[field] = subField;
|
||||
}
|
||||
// #13183, handle special behavior for day period characters (a, b, B)
|
||||
if (!original.isFieldEmpty(HOUR)) {
|
||||
if (original.getFieldChar(HOUR)=='h' || original.getFieldChar(HOUR)=='K') {
|
||||
// We have a skeleton with 12-hour-cycle format
|
||||
if (original.isFieldEmpty(DAYPERIOD)) {
|
||||
// But we do not have a day period in the skeleton; add the default DAYPERIOD (currently "a")
|
||||
for (int i = 0; i < types.length; ++i) {
|
||||
int[] row = types[i];
|
||||
if (row[1] == DAYPERIOD) {
|
||||
// first entry for DAYPERIOD
|
||||
original.populate(DAYPERIOD, (char)row[0], row[3]);
|
||||
baseOriginal.populate(DAYPERIOD, (char)row[0], row[3]);
|
||||
type[DAYPERIOD] = row[2];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (!original.isFieldEmpty(DAYPERIOD)) {
|
||||
// Skeleton has 24-hour-cycle hour format and has dayPeriod, delete dayPeriod (i.e. ignore it)
|
||||
original.clearField(DAYPERIOD);
|
||||
baseOriginal.clearField(DAYPERIOD);
|
||||
type[DAYPERIOD] = NONE;
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:c01f234ada00f14dddc0130a2b5c537c6dd18037a0387b9bb0147bbbcbb0dbf4
|
||||
size 12152967
|
||||
oid sha256:f74f204e65aaf327708ebed125e17dc49b3e74d026e4bf762f48978ae8155d6a
|
||||
size 12153152
|
||||
|
|
|
@ -55,21 +55,93 @@ public class DateTimeGeneratorTest extends TestFmwk {
|
|||
@Test
|
||||
public void TestC() {
|
||||
String[][] tests = {
|
||||
{"zh", "Cm", "Bh:mm"},
|
||||
{"de", "Cm", "HH:mm"},
|
||||
{"en", "Cm", "h:mm a"},
|
||||
{"en-BN", "Cm", "h:mm b"},
|
||||
{"gu-IN", "Cm", "h:mm B"},
|
||||
{"und-IN", "Cm", "h:mm a"},
|
||||
// These may change with actual data for Bhmm/bhmm skeletons
|
||||
{"zh", "Cm", "Bh:mm"},
|
||||
{"zh", "CCm", "Bhh:mm"},
|
||||
{"zh", "CCCm", "BBBBh:mm"},
|
||||
{"zh", "CCCCm", "BBBBhh:mm"},
|
||||
{"zh", "CCCCCm", "BBBBBh:mm"},
|
||||
{"zh", "CCCCCCm", "BBBBBhh:mm"},
|
||||
{"de", "Cm", "HH:mm"},
|
||||
{"de", "CCm", "HH:mm"},
|
||||
{"de", "CCCm", "HH:mm"},
|
||||
{"de", "CCCCm", "HH:mm"},
|
||||
{"en", "Cm", "h:mm a"},
|
||||
{"en", "CCm", "hh:mm a"},
|
||||
{"en", "CCCm", "h:mm aaaa"},
|
||||
{"en", "CCCCm", "hh:mm aaaa"},
|
||||
{"en", "CCCCCm", "h:mm aaaaa"},
|
||||
{"en", "CCCCCCm", "hh:mm aaaaa"},
|
||||
{"en-BN", "Cm", "h:mm b"},
|
||||
{"gu-IN", "Cm", "h:mm B"},
|
||||
{"und-IN", "Cm", "h:mm a"},
|
||||
};
|
||||
for (String[] test : tests) {
|
||||
DateTimePatternGenerator gen = DateTimePatternGenerator.getInstance(ULocale.forLanguageTag(test[0]));
|
||||
String skeleton = test[1];
|
||||
String pattern = gen.getBestPattern(skeleton);
|
||||
int options = DateTimePatternGenerator.MATCH_HOUR_FIELD_LENGTH;
|
||||
String pattern = gen.getBestPattern(skeleton, options);
|
||||
assertEquals(test[0] + "/" + skeleton, test[2], pattern);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void TestSkeletonsWithDayPeriods() {
|
||||
String[][] dataItems = {
|
||||
// sample data in a locale (base is not in locale, just here for test)
|
||||
// skel (base) pattern
|
||||
{ "aH", "H", "H" }, // should ignore a
|
||||
{ "h", "ah", "h a"},
|
||||
{ "Bh", "Bh", "B h"},
|
||||
};
|
||||
String[][] testItems = {
|
||||
// sample requested skeletons and results
|
||||
// skel pattern
|
||||
{ "H", "H"},
|
||||
{ "HH", "HH"},
|
||||
{ "aH", "H"},
|
||||
{ "aHH", "HH"},
|
||||
{ "BH", "H"},
|
||||
{ "BHH", "HH"},
|
||||
{ "BBBBH", "H"},
|
||||
{ "h", "h a"},
|
||||
{ "hh", "hh a"},
|
||||
{ "ah", "h a"},
|
||||
{ "ahh", "hh a"},
|
||||
{ "aaaah", "h aaaa"},
|
||||
{ "aaaahh", "hh aaaa"},
|
||||
{ "bh", "h b"},
|
||||
{ "bhh", "hh b"},
|
||||
{ "bbbbh", "h bbbb"},
|
||||
{ "Bh", "B h"},
|
||||
{ "Bhh", "B hh"},
|
||||
{ "BBBBh", "BBBB h"},
|
||||
{ "BBBBhh", "BBBB hh"},
|
||||
{ "a", "a"},
|
||||
{ "aaaaa", "aaaaa"},
|
||||
{ "b", "b"},
|
||||
{ "bbbb", "bbbb"},
|
||||
{ "B", "B"},
|
||||
{ "BBBB", "BBBB"},
|
||||
};
|
||||
DateTimePatternGenerator gen = DateTimePatternGenerator.getEmptyInstance();
|
||||
DateTimePatternGenerator.PatternInfo returnInfo = new DateTimePatternGenerator.PatternInfo();
|
||||
for (String[] dataItem : dataItems) {
|
||||
gen.addPatternWithSkeleton(dataItem[2], dataItem[0], true, returnInfo);
|
||||
String base = gen.getBaseSkeleton(dataItem[0]);
|
||||
if (!base.equals(dataItem[1])) {
|
||||
errln("getBaseSkeleton for skeleton " + dataItem[0] + ", expected " + dataItem[1] + ", got " + base);
|
||||
}
|
||||
}
|
||||
for (String[] testItem : testItems) {
|
||||
int options = DateTimePatternGenerator.MATCH_HOUR_FIELD_LENGTH;
|
||||
String pattern = gen.getBestPattern(testItem[0], options);
|
||||
if (!pattern.equals(testItem[1])) {
|
||||
errln("getBestPattern for skeleton " + testItem[0] + ", expected " + testItem[1] + ", got " + pattern);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void TestSimple() {
|
||||
// some simple use cases
|
||||
|
@ -587,7 +659,7 @@ public class DateTimeGeneratorTest extends TestFmwk {
|
|||
|
||||
@Test
|
||||
public void TestVariableCharacters() {
|
||||
UnicodeSet valid = new UnicodeSet("[G y Y u U r Q q M L l w W d D F g E e c a h H K k m s S A z Z O v V X x]");
|
||||
UnicodeSet valid = new UnicodeSet("[G y Y u U r Q q M L l w W d D F g E e c a b B h H K k m s S A z Z O v V X x]");
|
||||
for (char c = 0; c < 0xFF; ++c) {
|
||||
boolean works = false;
|
||||
try {
|
||||
|
|
Loading…
Add table
Reference in a new issue