mirror of
https://github.com/unicode-org/icu.git
synced 2025-04-06 22:15:31 +00:00
ICU-20442 Adding support for hour-cycle on DateTimePatternGenerator
DateTimePatternGenerator needs to consider the hour-cycle preferred by Locale. This means that we need to to override the hour-cycle when a locale contains "hc" keyword. This patch is adding such functionality. In addition, "DateTimePatternGenerator::adjustFieldTypes" should adjust hour field to properly follow tr35 spec(https://www.unicode.org/reports/tr35/tr35-dates.html#dfst-hour).
This commit is contained in:
parent
d88fd11a34
commit
09d409f5f4
5 changed files with 187 additions and 19 deletions
|
@ -654,6 +654,23 @@ void DateTimePatternGenerator::getAllowedHourFormats(const Locale &locale, UErro
|
|||
|
||||
int32_t* allowedFormats = getAllowedHourFormatsLangCountry(language, country, status);
|
||||
|
||||
// We need to check if there is an hour cycle on locale
|
||||
char buffer[8];
|
||||
int32_t count = locale.getKeywordValue("hours", buffer, sizeof(buffer), status);
|
||||
|
||||
fDefaultHourFormatChar = 0;
|
||||
if (U_SUCCESS(status) && count > 0) {
|
||||
if(uprv_strcmp(buffer, "h24") == 0) {
|
||||
fDefaultHourFormatChar = LOW_K;
|
||||
} else if(uprv_strcmp(buffer, "h23") == 0) {
|
||||
fDefaultHourFormatChar = CAP_H;
|
||||
} else if(uprv_strcmp(buffer, "h12") == 0) {
|
||||
fDefaultHourFormatChar = LOW_H;
|
||||
} else if(uprv_strcmp(buffer, "h11") == 0) {
|
||||
fDefaultHourFormatChar = CAP_K;
|
||||
}
|
||||
}
|
||||
|
||||
// Check if the region has an alias
|
||||
if (allowedFormats == nullptr) {
|
||||
UErrorCode localStatus = U_ZERO_ERROR;
|
||||
|
@ -667,13 +684,16 @@ void DateTimePatternGenerator::getAllowedHourFormats(const Locale &locale, UErro
|
|||
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.
|
||||
switch (allowedFormats[0]) {
|
||||
case ALLOWED_HOUR_FORMAT_h: fDefaultHourFormatChar = LOW_H; break;
|
||||
case ALLOWED_HOUR_FORMAT_H: fDefaultHourFormatChar = CAP_H; break;
|
||||
case ALLOWED_HOUR_FORMAT_K: fDefaultHourFormatChar = CAP_K; break;
|
||||
case ALLOWED_HOUR_FORMAT_k: fDefaultHourFormatChar = LOW_K; break;
|
||||
default: fDefaultHourFormatChar = CAP_H; break;
|
||||
if (!fDefaultHourFormatChar) {
|
||||
switch (allowedFormats[0]) {
|
||||
case ALLOWED_HOUR_FORMAT_h: fDefaultHourFormatChar = LOW_H; break;
|
||||
case ALLOWED_HOUR_FORMAT_H: fDefaultHourFormatChar = CAP_H; break;
|
||||
case ALLOWED_HOUR_FORMAT_K: fDefaultHourFormatChar = CAP_K; break;
|
||||
case ALLOWED_HOUR_FORMAT_k: fDefaultHourFormatChar = LOW_K; break;
|
||||
default: fDefaultHourFormatChar = CAP_H; break;
|
||||
}
|
||||
}
|
||||
|
||||
for (int32_t i = 0; i < UPRV_LENGTHOF(fAllowedHourFormats); ++i) {
|
||||
fAllowedHourFormats[i] = allowedFormats[i + 1];
|
||||
if (fAllowedHourFormats[i] == ALLOWED_HOUR_FORMAT_UNKNOWN) {
|
||||
|
@ -681,7 +701,9 @@ void DateTimePatternGenerator::getAllowedHourFormats(const Locale &locale, UErro
|
|||
}
|
||||
}
|
||||
} else { // Lookup failed, twice
|
||||
fDefaultHourFormatChar = CAP_H;
|
||||
if (!fDefaultHourFormatChar) {
|
||||
fDefaultHourFormatChar = CAP_H;
|
||||
}
|
||||
fAllowedHourFormats[0] = ALLOWED_HOUR_FORMAT_H;
|
||||
fAllowedHourFormats[1] = ALLOWED_HOUR_FORMAT_UNKNOWN;
|
||||
}
|
||||
|
@ -1562,14 +1584,16 @@ DateTimePatternGenerator::adjustFieldTypes(const UnicodeString& pattern,
|
|||
dtMatcher->skeleton.original.appendFieldTo(UDATPG_FRACTIONAL_SECOND_FIELD, field);
|
||||
} else if (dtMatcher->skeleton.type[typeValue]!=0) {
|
||||
// Here:
|
||||
// - "reqField" is the field from the originally requested skeleton, with length
|
||||
// "reqFieldLen".
|
||||
// - "reqField" is the field from the originally requested skeleton after replacement
|
||||
// of metacharacters 'j', 'C' and 'J', with length "reqFieldLen".
|
||||
// - "field" is the field from the found pattern.
|
||||
//
|
||||
// The adjusted field should consist of characters from the originally requested
|
||||
// skeleton, except in the case of UDATPG_HOUR_FIELD or UDATPG_MONTH_FIELD or
|
||||
// skeleton, except in the case of UDATPG_MONTH_FIELD or
|
||||
// UDATPG_WEEKDAY_FIELD or UDATPG_YEAR_FIELD, in which case it should consist
|
||||
// of characters from the found pattern.
|
||||
// of characters from the found pattern. In some cases of UDATPG_HOUR_FIELD,
|
||||
// there is adjustment following the "defaultHourFormatChar". There is explanation
|
||||
// how it is done below.
|
||||
//
|
||||
// The length of the adjusted field (adjFieldLen) should match that in the originally
|
||||
// requested skeleton, except that in the following cases the length of the adjusted field
|
||||
|
@ -1607,9 +1631,28 @@ DateTimePatternGenerator::adjustFieldTypes(const UnicodeString& pattern,
|
|||
&& (typeValue!= UDATPG_YEAR_FIELD || reqFieldChar==CAP_Y))
|
||||
? reqFieldChar
|
||||
: field.charAt(0);
|
||||
if (typeValue == UDATPG_HOUR_FIELD && (flags & kDTPGSkeletonUsesCapJ) != 0) {
|
||||
c = fDefaultHourFormatChar;
|
||||
if (typeValue == UDATPG_HOUR_FIELD) {
|
||||
// The adjustment here is required to match spec (https://www.unicode.org/reports/tr35/tr35-dates.html#dfst-hour).
|
||||
// It is necessary to match the hour-cycle preferred by the Locale.
|
||||
// Given that, we need to do the following adjustments:
|
||||
// 1. When hour-cycle is h11 it should replace 'h' by 'K'.
|
||||
// 2. When hour-cycle is h23 it should replace 'H' by 'k'.
|
||||
// 3. When hour-cycle is h24 it should replace 'k' by 'H'.
|
||||
// 4. When hour-cycle is h12 it should replace 'K' by 'h'.
|
||||
|
||||
if ((flags & kDTPGSkeletonUsesCapJ) != 0 || reqFieldChar == fDefaultHourFormatChar) {
|
||||
c = fDefaultHourFormatChar;
|
||||
} else if (reqFieldChar == LOW_H && fDefaultHourFormatChar == CAP_K) {
|
||||
c = CAP_K;
|
||||
} else if (reqFieldChar == CAP_H && fDefaultHourFormatChar == LOW_K) {
|
||||
c = LOW_K;
|
||||
} else if (reqFieldChar == LOW_K && fDefaultHourFormatChar == CAP_H) {
|
||||
c = CAP_H;
|
||||
} else if (reqFieldChar == CAP_K && fDefaultHourFormatChar == LOW_H) {
|
||||
c = LOW_H;
|
||||
}
|
||||
}
|
||||
|
||||
field.remove();
|
||||
for (int32_t j=adjFieldLen; j>0; --j) {
|
||||
field += c;
|
||||
|
|
|
@ -58,6 +58,7 @@ void DateIntervalFormatTest::runIndexedTest( int32_t index, UBool exec, const ch
|
|||
TESTCASE(9, testTicket12065);
|
||||
TESTCASE(10, testFormattedDateInterval);
|
||||
TESTCASE(11, testCreateInstanceForAllLocales);
|
||||
TESTCASE(12, testTicket20707);
|
||||
default: name = ""; break;
|
||||
}
|
||||
}
|
||||
|
@ -1805,4 +1806,43 @@ void DateIntervalFormatTest::testCreateInstanceForAllLocales() {
|
|||
}
|
||||
}
|
||||
|
||||
void DateIntervalFormatTest::testTicket20707() {
|
||||
IcuTestErrorCode status(*this, "testTicket20707");
|
||||
|
||||
const char16_t timeZone[] = u"UTC";
|
||||
Locale locales[] = {"en-u-hc-h24", "en-u-hc-h23", "en-u-hc-h12", "en-u-hc-h11", "en", "en-u-hc-h25", "hi-IN-u-hc-h11"};
|
||||
|
||||
// Clomuns: hh, HH, kk, KK, jj, JJs, CC
|
||||
UnicodeString expected[][7] = {
|
||||
// Hour-cycle: k
|
||||
{u"12 AM", u"24", u"24", u"12 AM", u"24", u"0 (hour: 24)", u"12 AM"},
|
||||
// Hour-cycle: H
|
||||
{u"12 AM", u"00", u"00", u"12 AM", u"00", u"0 (hour: 00)", u"12 AM"},
|
||||
// Hour-cycle: h
|
||||
{u"12 AM", u"00", u"00", u"12 AM", u"12 AM", u"0 (hour: 12)", u"12 AM"},
|
||||
// Hour-cycle: K
|
||||
{u"0 AM", u"00", u"00", u"0 AM", u"0 AM", u"0 (hour: 00)", u"0 AM"},
|
||||
{u"12 AM", u"00", u"00", u"12 AM", u"12 AM", u"0 (hour: 12)", u"12 AM"},
|
||||
{u"12 AM", u"00", u"00", u"12 AM", u"12 AM", u"0 (hour: 12)", u"12 AM"},
|
||||
// Hour-cycle: K
|
||||
{u"0 am", u"00", u"00", u"0 am", u"0 am", u"0 (\u0918\u0902\u091F\u093E: 00)", u"\u0930\u093E\u0924 0"}
|
||||
};
|
||||
|
||||
int32_t i = 0;
|
||||
for (Locale locale : locales) {
|
||||
int32_t j = 0;
|
||||
for (const UnicodeString skeleton : {u"hh", u"HH", u"kk", u"KK", u"jj", u"JJs", u"CC"}) {
|
||||
LocalPointer<DateIntervalFormat> dtifmt(DateIntervalFormat::createInstance(skeleton, locale, status));
|
||||
FieldPosition fposition;
|
||||
UnicodeString result;
|
||||
LocalPointer<Calendar> calendar(Calendar::createInstance(TimeZone::createTimeZone(timeZone), status));
|
||||
calendar->setTime(UDate(1563235200000), status);
|
||||
dtifmt->format(*calendar, *calendar, result, fposition, status);
|
||||
|
||||
assertEquals("Formatted result", expected[i][j++], result);
|
||||
}
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* #if !UCONFIG_NO_FORMATTING */
|
||||
|
|
|
@ -67,6 +67,8 @@ public:
|
|||
void testFormattedDateInterval();
|
||||
void testCreateInstanceForAllLocales();
|
||||
|
||||
void testTicket20707();
|
||||
|
||||
private:
|
||||
/**
|
||||
* Test formatting against expected result
|
||||
|
|
|
@ -368,6 +368,26 @@ public class DateTimePatternGenerator implements Freezable<DateTimePatternGenera
|
|||
|
||||
String[] list = getAllowedHourFormatsLangCountry(language, country);
|
||||
|
||||
// We need to check if there is an hour cycle on locale
|
||||
Character defaultCharFromLocale = null;
|
||||
String hourCycle = uLocale.getKeywordValue("hours");
|
||||
if (hourCycle != null) {
|
||||
switch(hourCycle) {
|
||||
case "h24":
|
||||
defaultCharFromLocale = 'k';
|
||||
break;
|
||||
case "h23":
|
||||
defaultCharFromLocale = 'H';
|
||||
break;
|
||||
case "h12":
|
||||
defaultCharFromLocale = 'h';
|
||||
break;
|
||||
case "h11":
|
||||
defaultCharFromLocale = 'K';
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Check if the region has an alias
|
||||
if (list == null) {
|
||||
try {
|
||||
|
@ -380,11 +400,11 @@ public class DateTimePatternGenerator implements Freezable<DateTimePatternGenera
|
|||
}
|
||||
|
||||
if (list != null) {
|
||||
defaultHourFormatChar = list[0].charAt(0);
|
||||
defaultHourFormatChar = defaultCharFromLocale != null ? defaultCharFromLocale : list[0].charAt(0);
|
||||
allowedHourFormats = Arrays.copyOfRange(list, 1, list.length - 1);
|
||||
} else {
|
||||
allowedHourFormats = LAST_RESORT_ALLOWED_HOUR_FORMAT;
|
||||
defaultHourFormatChar = allowedHourFormats[0].charAt(0);
|
||||
defaultHourFormatChar = (defaultCharFromLocale != null) ? defaultCharFromLocale : allowedHourFormats[0].charAt(0);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2117,8 +2137,10 @@ public class DateTimePatternGenerator implements Freezable<DateTimePatternGenera
|
|||
// - "field" is the field from the found pattern.
|
||||
//
|
||||
// The adjusted field should consist of characters from the originally requested
|
||||
// skeleton, except in the case of HOUR or MONTH or WEEKDAY or YEAR, in which case it
|
||||
// should consist of characters from the found pattern.
|
||||
// skeleton, except in the case of MONTH or WEEKDAY or YEAR, in which case it
|
||||
// should consist of characters from the found pattern. There is some adjustment
|
||||
// in some cases of HOUR to "defaultHourFormatChar". There is explanation
|
||||
// how it is done below.
|
||||
//
|
||||
// The length of the adjusted field (adjFieldLen) should match that in the originally
|
||||
// requested skeleton, except that in the following cases the length of the adjusted field
|
||||
|
@ -2163,8 +2185,25 @@ public class DateTimePatternGenerator implements Freezable<DateTimePatternGenera
|
|||
&& (type != YEAR || reqFieldChar=='Y'))
|
||||
? reqFieldChar
|
||||
: fieldBuilder.charAt(0);
|
||||
if (type == HOUR && flags.contains(DTPGflags.SKELETON_USES_CAP_J)) {
|
||||
c = defaultHourFormatChar;
|
||||
if (type == HOUR) {
|
||||
// The adjustment here is required to match spec (https://www.unicode.org/reports/tr35/tr35-dates.html#dfst-hour).
|
||||
// It is necessary to match the hour-cycle preferred by the Locale.
|
||||
// Given that, we need to do the following adjustments:
|
||||
// 1. When hour-cycle is h11 it should replace 'h' by 'K'.
|
||||
// 2. When hour-cycle is h23 it should replace 'H' by 'k'.
|
||||
// 3. When hour-cycle is h24 it should replace 'k' by 'H'.
|
||||
// 4. When hour-cycle is h12 it should replace 'K' by 'h'.
|
||||
if (flags.contains(DTPGflags.SKELETON_USES_CAP_J) || reqFieldChar == defaultHourFormatChar) {
|
||||
c = defaultHourFormatChar;
|
||||
} else if (reqFieldChar == 'h' && defaultHourFormatChar == 'K') {
|
||||
c = 'K';
|
||||
} else if (reqFieldChar == 'H' && defaultHourFormatChar == 'k') {
|
||||
c = 'k';
|
||||
} else if (reqFieldChar == 'k' && defaultHourFormatChar == 'H') {
|
||||
c = 'H';
|
||||
} else if (reqFieldChar == 'K' && defaultHourFormatChar == 'h') {
|
||||
c = 'h';
|
||||
}
|
||||
}
|
||||
fieldBuilder = new StringBuilder();
|
||||
for (int i = adjFieldLen; i > 0; --i) fieldBuilder.append(c);
|
||||
|
|
|
@ -2050,4 +2050,48 @@ public class DateIntervalFormatTest extends TestFmwk {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTicket20707() {
|
||||
TimeZone tz = TimeZone.getTimeZone("UTC");
|
||||
Locale locales[] = {
|
||||
new Locale("en-u-hc-h24"),
|
||||
new Locale("en-u-hc-h23"),
|
||||
new Locale("en-u-hc-h12"),
|
||||
new Locale("en-u-hc-h11"),
|
||||
new Locale("en"),
|
||||
new Locale("en-u-hc-h25"),
|
||||
new Locale("hi-IN-u-hc-h11")
|
||||
};
|
||||
|
||||
// Clomuns: hh, HH, kk, KK, jj, JJs, CC
|
||||
String expected[][] = {
|
||||
// Hour-cycle: k
|
||||
{"12 AM", "24", "24", "12 AM", "24", "0 (hour: 24)", "12 AM"},
|
||||
// Hour-cycle: H
|
||||
{"12 AM", "00", "00", "12 AM", "00", "0 (hour: 00)", "12 AM"},
|
||||
// Hour-cycle: h
|
||||
{"12 AM", "00", "00", "12 AM", "12 AM", "0 (hour: 12)", "12 AM"},
|
||||
// Hour-cycle: K
|
||||
{"0 AM", "00", "00", "0 AM", "0 AM", "0 (hour: 00)", "0 AM"},
|
||||
{"12 AM", "00", "00", "12 AM", "12 AM", "0 (hour: 12)", "12 AM"},
|
||||
{"12 AM", "00", "00", "12 AM", "12 AM", "0 (hour: 12)", "12 AM"},
|
||||
{"0 am", "00", "00", "0 am", "0 am", "0 (\u0918\u0902\u091F\u093E: 00)", "\u0930\u093E\u0924 0"}
|
||||
};
|
||||
|
||||
int i = 0;
|
||||
for (Locale locale : locales) {
|
||||
int j = 0;
|
||||
String skeletons[] = {"hh", "HH", "kk", "KK", "jj", "JJs", "CC"};
|
||||
for (String skeleton : skeletons) {
|
||||
DateIntervalFormat dateFormat = DateIntervalFormat.getInstance(skeleton, locale);
|
||||
Calendar calendar = Calendar.getInstance(tz);
|
||||
calendar.setTime(new Date(1563235200000L));
|
||||
StringBuffer resultBuffer = dateFormat.format(calendar, calendar, new StringBuffer(""), new FieldPosition(0));
|
||||
|
||||
assertEquals("Formatted result for " + skeleton + " locale: " + locale.getDisplayName(), expected[i][j++], resultBuffer.toString());
|
||||
}
|
||||
i++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue