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:
Peter Edberg 2017-05-23 03:18:28 +00:00
commit 1b2cc7d1fb
11 changed files with 544 additions and 180 deletions

View file

@ -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"}

View file

@ -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"}

View file

@ -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"}

View file

@ -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);
}

View file

@ -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

View file

@ -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);

View file

@ -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 */

View file

@ -30,6 +30,7 @@ private:
void testAllFieldPatterns(/* char* par */);
void testStaticGetSkeleton(/* char* par */);
void testC();
void testSkeletonsWithDayPeriods();
};
#endif /* #if !UCONFIG_NO_FORMATTING */

View file

@ -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;
}

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:c01f234ada00f14dddc0130a2b5c537c6dd18037a0387b9bb0147bbbcbb0dbf4
size 12152967
oid sha256:f74f204e65aaf327708ebed125e17dc49b3e74d026e4bf762f48978ae8155d6a
size 12153152

View file

@ -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 {