ICU-20436 Add getDefaultHourCycle to DateTimePatternGenerator

See #901
This commit is contained in:
Frank Tang 2019-12-11 20:24:50 +00:00 committed by Frank Yung-Fong Tang
parent 8fda72f6d8
commit 923ec1ad30
10 changed files with 182 additions and 9 deletions

View File

@ -687,6 +687,22 @@ void DateTimePatternGenerator::getAllowedHourFormats(const Locale &locale, UErro
}
}
UDateFormatHourCycle
DateTimePatternGenerator::getDefaultHourCycle(UErrorCode& /*status*/) const {
switch(fDefaultHourFormatChar) {
case CAP_K:
return UDAT_HOUR_CYCLE_11;
case LOW_H:
return UDAT_HOUR_CYCLE_12;
case CAP_H:
return UDAT_HOUR_CYCLE_23;
case LOW_K:
return UDAT_HOUR_CYCLE_24;
default:
UPRV_UNREACHABLE;
}
}
UnicodeString
DateTimePatternGenerator::getSkeleton(const UnicodeString& pattern, UErrorCode&
/*status*/) {

View File

@ -291,4 +291,9 @@ udatpg_getPatternForSkeleton(const UDateTimePatternGenerator *dtpg,
return result.getBuffer();
}
U_CAPI UDateFormatHourCycle U_EXPORT2
udatpg_getDefaultHourCycle(const UDateTimePatternGenerator *dtpg, UErrorCode* pErrorCode) {
return ((const DateTimePatternGenerator *)dtpg)->getDefaultHourCycle(*pErrorCode);
}
#endif

View File

@ -483,6 +483,17 @@ public:
*/
const UnicodeString& getDecimal() const;
#ifndef U_HIDE_DRAFT_API
/**
* Get the default hour cycle.
* @param status Output param set to success/failure code on exit,
* which must not indicate a failure before the function call.
* @return the default hour cycle.
* @draft ICU 67
*/
UDateFormatHourCycle getDefaultHourCycle(UErrorCode& status) const;
#endif /* U_HIDE_DRAFT_API */
/**
* ICU "poor man's RTTI", returns a UClassID for the actual class.
*

View File

@ -958,7 +958,37 @@ udat_getBooleanAttribute(const UDateFormat* fmt, UDateFormatBooleanAttribute att
U_CAPI void U_EXPORT2
udat_setBooleanAttribute(UDateFormat *fmt, UDateFormatBooleanAttribute attr, UBool newValue, UErrorCode* status);
#ifndef U_HIDE_DRAFT_API
/**
* Hour Cycle.
* @draft ICU 67
*/
typedef enum UDateFormatHourCycle {
/**
* Hour in am/pm (0~11)
* @draft ICU 67
*/
UDAT_HOUR_CYCLE_11,
/**
* Hour in am/pm (1~12)
* @draft ICU 67
*/
UDAT_HOUR_CYCLE_12,
/**
* Hour in day (0~23)
* @draft ICU 67
*/
UDAT_HOUR_CYCLE_23,
/**
* Hour in day (1~24)
* @draft ICU 67
*/
UDAT_HOUR_CYCLE_24
} UDateFormatHourCycle;
#endif /* U_HIDE_DRAFT_API */
#if U_SHOW_CPLUSPLUS_API

View File

@ -20,6 +20,7 @@
#define __UDATPG_H__
#include "unicode/utypes.h"
#include "unicode/udat.h"
#include "unicode/uenum.h"
#include "unicode/localpointer.h"
@ -651,4 +652,18 @@ udatpg_getPatternForSkeleton(const UDateTimePatternGenerator *dtpg,
const UChar *skeleton, int32_t skeletonLength,
int32_t *pLength);
#ifndef U_HIDE_DRAFT_API
/**
* Return the default hour cycle.
*
* @param dtpg a pointer to UDateTimePatternGenerator.
* @param pErrorCode a pointer to the UErrorCode which must not indicate a
* failure before the function call.
* @return the default hour cycle.
* @draft ICU 67
*/
U_DRAFT UDateFormatHourCycle U_EXPORT2
udatpg_getDefaultHourCycle(const UDateTimePatternGenerator *dtpg, UErrorCode* pErrorCode);
#endif /* U_HIDE_DRAFT_API */
#endif

View File

@ -43,6 +43,7 @@ static void TestUsage(void);
static void TestBuilder(void);
static void TestOptions(void);
static void TestGetFieldDisplayNames(void);
static void TestGetDefaultHourCycle(void);
void addDateTimePatternGeneratorTest(TestNode** root) {
TESTCASE(TestOpenClose);
@ -50,6 +51,7 @@ void addDateTimePatternGeneratorTest(TestNode** root) {
TESTCASE(TestBuilder);
TESTCASE(TestOptions);
TESTCASE(TestGetFieldDisplayNames);
TESTCASE(TestGetDefaultHourCycle);
}
/*
@ -510,4 +512,46 @@ static void TestGetFieldDisplayNames() {
}
}
typedef struct HourCycleData {
const char * locale;
UDateFormatHourCycle expected;
} HourCycleData;
static void TestGetDefaultHourCycle() {
const HourCycleData testData[] = {
/*loc expected */
{ "ar_EG", UDAT_HOUR_CYCLE_12 },
{ "de_DE", UDAT_HOUR_CYCLE_23 },
{ "en_AU", UDAT_HOUR_CYCLE_12 },
{ "en_CA", UDAT_HOUR_CYCLE_12 },
{ "en_US", UDAT_HOUR_CYCLE_12 },
{ "es_ES", UDAT_HOUR_CYCLE_23 },
{ "fi", UDAT_HOUR_CYCLE_23 },
{ "fr", UDAT_HOUR_CYCLE_23 },
{ "ja_JP", UDAT_HOUR_CYCLE_23 },
{ "zh_CN", UDAT_HOUR_CYCLE_12 },
{ "zh_HK", UDAT_HOUR_CYCLE_12 },
{ "zh_TW", UDAT_HOUR_CYCLE_12 },
{ "ko_KR", UDAT_HOUR_CYCLE_12 },
};
int count = UPRV_LENGTHOF(testData);
const HourCycleData * testDataPtr = testData;
for (; count-- > 0; ++testDataPtr) {
UErrorCode status = U_ZERO_ERROR;
UDateTimePatternGenerator * dtpgen =
udatpg_open(testDataPtr->locale, &status);
if ( U_FAILURE(status) ) {
log_data_err( "ERROR udatpg_open failed for locale %s : %s - (Are you missing data?)\n",
testDataPtr->locale, myErrorName(status));
} else {
UDateFormatHourCycle actual = udatpg_getDefaultHourCycle(dtpgen, &status);
if (U_FAILURE(status) || testDataPtr->expected != actual) {
log_err("ERROR dtpgen locale %s udatpg_getDefaultHourCycle expecte to get %d but get %d\n",
testDataPtr->locale, testDataPtr->expected, actual);
}
udatpg_close(dtpgen);
}
}
}
#endif

View File

@ -1410,18 +1410,19 @@ void IntlTestDateTimePatternGeneratorAPI::test20640_HourCyclArsEnNH() {
const char* localeName;
const char16_t* expectedDtpgPattern;
const char16_t* expectedTimePattern;
UDateFormatHourCycle expectedDefaultHourCycle;
} cases[] = {
// ars is interesting because it does not have a region, but it aliases
// to ar_SA, which has a region.
{"ars", u"h a", u"h:mm a"},
{"ars", u"h a", u"h:mm a", UDAT_HOUR_CYCLE_12},
// en_NH is interesting because NH is a deprecated region code;
// formerly New Hebrides, now Vanuatu => VU => h.
{"en_NH", u"h a", u"h:mm a"},
{"en_NH", u"h a", u"h:mm a", UDAT_HOUR_CYCLE_12},
// ch_ZH is a typo (should be zh_CN), but we should fail gracefully.
// {"cn_ZH", u"HH", u"H:mm"}, // TODO(ICU-20653): Desired behavior
{"cn_ZH", u"HH", u"h:mm a"}, // Actual behavior
{"cn_ZH", u"HH", u"h:mm a", UDAT_HOUR_CYCLE_23 }, // Actual behavior
// a non-BCP47 locale without a country code should not fail
{"ja_TRADITIONAL", u"H時", u"H:mm"},
{"ja_TRADITIONAL", u"H時", u"H:mm", UDAT_HOUR_CYCLE_23},
};
for (auto& cas : cases) {
@ -1440,11 +1441,17 @@ void IntlTestDateTimePatternGeneratorAPI::test20640_HourCyclArsEnNH() {
if (status.errIfFailureAndReset()) {
return;
}
UDateFormatHourCycle defaultHourCycle = dtpg->getDefaultHourCycle(status);
if (status.errIfFailureAndReset()) {
return;
}
assertEquals(UnicodeString("dtpgPattern ") + cas.localeName,
cas.expectedDtpgPattern, dtpgPattern);
assertEquals(UnicodeString("timePattern ") + cas.localeName,
cas.expectedTimePattern, timePattern);
assertEquals(UnicodeString("defaultHour ") + cas.localeName,
cas.expectedDefaultHourCycle, defaultHourCycle);
}
}

View File

@ -535,6 +535,36 @@ public abstract class DateFormat extends UFormat {
*/
private EnumSet<BooleanAttribute> booleanAttributes = EnumSet.allOf(BooleanAttribute.class);
/**
* Hour Cycle
* @draft ICU 67
*/
public enum HourCycle {
/**
* hour in am/pm (0~11)
* @draft ICU 67
*/
HOUR_CYCLE_11,
/**
* hour in am/pm (1~12)
* @draft ICU 67
*/
HOUR_CYCLE_12,
/**
* hour in day (0~23)
* @draft ICU 67
*/
HOUR_CYCLE_23,
/**
* hour in day (1~24)
* @draft ICU 67
*/
HOUR_CYCLE_24;
};
/*
* Capitalization setting, hoisted to DateFormat ICU 53
* Note that SimpleDateFormat serialization may call getContext/setContext to read/write

View File

@ -1205,7 +1205,6 @@ public class DateTimePatternGenerator implements Freezable<DateTimePatternGenera
private static final int APPENDITEM_WIDTH_INT = APPENDITEM_WIDTH.ordinal();
private static final DisplayWidth[] CLDR_FIELD_WIDTH = DisplayWidth.values();
// Option masks for getBestPattern, replaceFieldTypes (individual masks may be ORed together)
/**
@ -1313,6 +1312,20 @@ public class DateTimePatternGenerator implements Freezable<DateTimePatternGenera
return getFieldDisplayName(field, APPENDITEM_WIDTH);
}
/**
* Return the default hour cycle.
* @draft ICU 67
*/
public DateFormat.HourCycle getDefaultHourCycle() {
switch(getDefaultHourFormatChar()) {
case 'h': return DateFormat.HourCycle.HOUR_CYCLE_12;
case 'H': return DateFormat.HourCycle.HOUR_CYCLE_23;
case 'k': return DateFormat.HourCycle.HOUR_CYCLE_24;
case 'K': return DateFormat.HourCycle.HOUR_CYCLE_11;
default: throw new AssertionError("should be unreachable");
}
}
/**
* The private interface to set a display name for a particular date/time field,
* in one of several possible display widths.

View File

@ -1733,14 +1733,14 @@ public class DateTimeGeneratorTest extends TestFmwk {
String[][] cases = new String[][]{
// ars is interesting because it does not have a region, but it aliases
// to ar_SA, which has a region.
{"ars", "h a", "h:mm a"},
{"ars", "h a", "h:mm a", "HOUR_CYCLE_12"},
// en_NH is interesting because NH is a depregated region code.
{"en_NH", "h a", "h:mm a"},
{"en_NH", "h a", "h:mm a", "HOUR_CYCLE_12"},
// ch_ZH is a typo (should be zh_CN), but we should fail gracefully.
// {"cn_ZH", "HH", "H:mm"}, // TODO(ICU-20653): Desired behavior
{"cn_ZH", "HH", "h:mm a"}, // Actual behavior
{"cn_ZH", "HH", "h:mm a", "HOUR_CYCLE_23"}, // Actual behavior
// a non-BCP47 locale without a country code should not fail
{"ja_TRADITIONAL", "H時", "H:mm"},
{"ja_TRADITIONAL", "H時", "H:mm", "HOUR_CYCLE_23"},
};
for (String[] cas : cases) {
@ -1755,6 +1755,8 @@ public class DateTimeGeneratorTest extends TestFmwk {
cas[1], dtpgPattern);
assertEquals("timePattern " + cas[1],
cas[2], timePattern);
assertEquals("default hour cycle " + cas[3],
cas[3], dtpg.getDefaultHourCycle().toString());
}
}
}