From f713c0687c94ddb9a38c98f19c9b816196303894 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?kaz=C3=A8de=20king?= Date: Thu, 25 Feb 2016 19:51:53 +0000 Subject: [PATCH] ICU-11872 new time formatting pattern chars b/B Merging from the branch. X-SVN-Rev: 38370 --- icu4c/source/common/unicode/localpointer.h | 4 +- icu4c/source/common/unicode/ures.h | 9 +- icu4c/source/common/uvector.h | 4 +- icu4c/source/i18n/Makefile.in | 2 +- icu4c/source/i18n/dayperiodrules.cpp | 547 ++++++++++++++ icu4c/source/i18n/dayperiodrules.h | 85 +++ icu4c/source/i18n/dtfmtsym.cpp | 193 +++-- icu4c/source/i18n/i18n.vcxproj | 2 + icu4c/source/i18n/i18n.vcxproj.filters | 6 + icu4c/source/i18n/smpdtfmt.cpp | 408 +++++++++-- icu4c/source/i18n/ucln_in.h | 3 +- icu4c/source/i18n/unicode/dtfmtsym.h | 48 +- icu4c/source/i18n/unicode/smpdtfmt.h | 50 +- icu4c/source/i18n/unicode/udat.h | 21 +- icu4c/source/test/cintltst/cdattst.c | 202 +++--- icu4c/source/test/intltest/dtfmttst.cpp | 807 +++++++++++++++++---- icu4c/source/test/intltest/dtfmttst.h | 49 +- icu4c/source/test/intltest/sdtfmtts.cpp | 10 +- 18 files changed, 2044 insertions(+), 406 deletions(-) create mode 100644 icu4c/source/i18n/dayperiodrules.cpp create mode 100644 icu4c/source/i18n/dayperiodrules.h diff --git a/icu4c/source/common/unicode/localpointer.h b/icu4c/source/common/unicode/localpointer.h index 6bb7ecbe7bf..7bd5068f353 100644 --- a/icu4c/source/common/unicode/localpointer.h +++ b/icu4c/source/common/unicode/localpointer.h @@ -18,7 +18,7 @@ #define __LOCALPOINTER_H__ /** - * \file + * \file * \brief C++ API: "Smart pointers" for use with and in ICU4C C++ code. * * These classes are inspired by @@ -52,7 +52,7 @@ U_NAMESPACE_BEGIN * Destructor and adoptInstead(). * * There is no operator T *() provided because the programmer must decide - * whether to use getAlias() (without transfer of ownership) or orpan() + * whether to use getAlias() (without transfer of ownership) or orphan() * (with transfer of ownership and NULLing of the pointer). * * @see LocalPointer diff --git a/icu4c/source/common/unicode/ures.h b/icu4c/source/common/unicode/ures.h index 03a01be22f5..e67204b5fbb 100644 --- a/icu4c/source/common/unicode/ures.h +++ b/icu4c/source/common/unicode/ures.h @@ -12,7 +12,7 @@ * 04/01/97 aliu Creation. * 02/22/99 damiba overhaul. * 04/04/99 helena Fixed internal header inclusion. -* 04/15/99 Madhu Updated Javadoc +* 04/15/99 Madhu Updated Javadoc * 06/14/99 stephen Removed functions taking a filename suffix. * 07/20/99 stephen Language-independent ypedef to void* * 11/09/99 weiv Added ures_getLocale() @@ -29,7 +29,7 @@ /** * \file - * \brief C API: Resource Bundle + * \brief C API: Resource Bundle * *

C API: Resource Bundle

* @@ -40,7 +40,7 @@ *

* Resource bundles in ICU4C are currently defined using text files which conform to the following * BNF definition. - * More on resource bundle concepts and syntax can be found in the + * More on resource bundle concepts and syntax can be found in the * Users Guide. *

*/ @@ -801,6 +801,7 @@ U_NAMESPACE_BEGIN */ inline UnicodeString ures_getUnicodeString(const UResourceBundle *resB, UErrorCode* status) { + UnicodeString result; UnicodeString result; int32_t len = 0; const UChar *r = ures_getString(resB, &len, status); @@ -848,6 +849,7 @@ ures_getNextUnicodeString(UResourceBundle *resB, const char ** key, UErrorCode* */ inline UnicodeString ures_getUnicodeStringByIndex(const UResourceBundle *resB, int32_t indexS, UErrorCode* status) { + UnicodeString result; UnicodeString result; int32_t len = 0; const UChar* r = ures_getStringByIndex(resB, indexS, &len, status); @@ -871,6 +873,7 @@ ures_getUnicodeStringByIndex(const UResourceBundle *resB, int32_t indexS, UError */ inline UnicodeString ures_getUnicodeStringByKey(const UResourceBundle *resB, const char* key, UErrorCode* status) { + UnicodeString result; UnicodeString result; int32_t len = 0; const UChar* r = ures_getStringByKey(resB, key, &len, status); diff --git a/icu4c/source/common/uvector.h b/icu4c/source/common/uvector.h index 29cda39f8f2..56e974b541e 100644 --- a/icu4c/source/common/uvector.h +++ b/icu4c/source/common/uvector.h @@ -1,6 +1,6 @@ /* ********************************************************************** -* Copyright (C) 1999-2013, International Business Machines +* Copyright (C) 1999-2016, International Business Machines * Corporation and others. All Rights Reserved. ********************************************************************** * Date Name Description @@ -59,7 +59,7 @@ U_NAMESPACE_BEGIN * *

In order to implement methods such as contains() and indexOf(), * UVector needs a way to compare objects for equality. To do so, it - * uses a comparison frunction, or "comparer." If the comparer is not + * uses a comparison function, or "comparer." If the comparer is not * set, or is set to zero, then all such methods will act as if the * vector contains no element. That is, indexOf() will always return * -1, contains() will always return FALSE, etc. diff --git a/icu4c/source/i18n/Makefile.in b/icu4c/source/i18n/Makefile.in index 22ccb904c41..5b705c091f9 100644 --- a/icu4c/source/i18n/Makefile.in +++ b/icu4c/source/i18n/Makefile.in @@ -99,7 +99,7 @@ sharedbreakiterator.o scientificnumberformatter.o digitgrouping.o \ digitinterval.o digitformatter.o digitaffix.o valueformatter.o \ digitaffixesandpadding.o pluralaffix.o precision.o \ affixpatternparser.o smallintformatter.o decimfmtimpl.o \ -visibledigits.o +visibledigits.o dayperiodrules.o ## Header files to install HEADERS = $(srcdir)/unicode/*.h diff --git a/icu4c/source/i18n/dayperiodrules.cpp b/icu4c/source/i18n/dayperiodrules.cpp new file mode 100644 index 00000000000..c8775da006a --- /dev/null +++ b/icu4c/source/i18n/dayperiodrules.cpp @@ -0,0 +1,547 @@ +/* +******************************************************************************* +* Copyright (C) 2016, International Business Machines +* Corporation and others. All Rights Reserved. +******************************************************************************* +* dayperiodrules.cpp +* +* created on: 2016-01-20 +* created by: kazede +*/ + +#include "dayperiodrules.h" + +#include "unicode/ures.h" +#include "cstr.h" +#include "cstring.h" +#include "ucln_in.h" +#include "uhash.h" +#include "umutex.h" +#include "uresimp.h" + + +U_NAMESPACE_BEGIN + +namespace { + +struct DayPeriodRulesData { + DayPeriodRulesData() : localeToRuleSetNumMap(NULL), rules(NULL), maxRuleSetNum(0) {} + + UHashtable *localeToRuleSetNumMap; + DayPeriodRules *rules; + int32_t maxRuleSetNum; +} *data = NULL; + +enum CutoffType { + CUTOFF_TYPE_UNKNOWN = -1, + CUTOFF_TYPE_BEFORE, + CUTOFF_TYPE_AFTER, // TODO: AFTER is deprecated in CLDR 29. Remove. + CUTOFF_TYPE_FROM, + CUTOFF_TYPE_AT, +}; + +} // namespace + +struct DayPeriodRulesDataSink : public ResourceTableSink { + // Initialize sub-sinks. + DayPeriodRulesDataSink() : + rulesSink(*this), ruleSetSink(*this), periodSink(*this), cutoffSink(*this) { + for (int32_t i = 0; i < UPRV_LENGTHOF(cutoffs); ++i) { cutoffs[i] = 0; } + } + virtual ~DayPeriodRulesDataSink(); + + // Entry point. + virtual ResourceTableSink *getOrCreateTableSink(const char *key, int32_t, UErrorCode &errorCode) { + if (U_FAILURE(errorCode)) { return NULL; } + + if (uprv_strcmp(key, "locales") == 0) { + return &localesSink; + } else if (uprv_strcmp(key, "rules") == 0) { + // Allocate one more than needed to skip [0]. See comment in parseSetNum(). + data->rules = new DayPeriodRules[data->maxRuleSetNum + 1]; + if (data->rules == NULL) { + errorCode = U_MEMORY_ALLOCATION_ERROR; + return NULL; + } else { + return &rulesSink; + } + } + return NULL; + } + + // Data root -> locales. + struct LocalesSink : public ResourceTableSink { + virtual ~LocalesSink(); + + virtual void put(const char *key, const ResourceValue &value, UErrorCode &errorCode) { + if (U_FAILURE(errorCode)) { return; } + + UnicodeString setNum_str = value.getUnicodeString(errorCode); + int32_t setNum = parseSetNum(setNum_str, errorCode); + uhash_puti(data->localeToRuleSetNumMap, const_cast(key), setNum, &errorCode); + } + } localesSink; + + // Data root -> rules. + struct RulesSink : public ResourceTableSink { + DayPeriodRulesDataSink &outer; + RulesSink(DayPeriodRulesDataSink &outer) : outer(outer) {} + virtual ~RulesSink(); + + virtual ResourceTableSink *getOrCreateTableSink(const char *key, int32_t, UErrorCode &errorCode) { + if (U_FAILURE(errorCode)) { return NULL; } + + outer.ruleSetNum = parseSetNum(key, errorCode); + return &outer.ruleSetSink; + } + } rulesSink; + + // Data root -> rules -> a rule set. + struct RuleSetSink : public ResourceTableSink { + DayPeriodRulesDataSink &outer; + RuleSetSink(DayPeriodRulesDataSink &outer) : outer(outer) {} + virtual ~RuleSetSink(); + + virtual ResourceTableSink *getOrCreateTableSink(const char *key, int32_t, UErrorCode &errorCode) { + if (U_FAILURE(errorCode)) { return NULL; } + + outer.period = DayPeriodRules::getDayPeriodFromString(key); + if (outer.period == DayPeriodRules::DAYPERIOD_UNKNOWN) { + errorCode = U_INVALID_FORMAT_ERROR; + return NULL; + } + + return &outer.periodSink; + } + + virtual void leave(UErrorCode &errorCode) { + if (U_FAILURE(errorCode)) { return; } + + if (!data->rules[outer.ruleSetNum].allHoursAreSet()) { + errorCode = U_INVALID_FORMAT_ERROR; + } + } + } ruleSetSink; + + // Data root -> rules -> a rule set -> a period (e.g. "morning1"). + // Key-value pairs (e.g. before{6:00}) will be captured here. + // Arrays (e.g. before{6:00, 24:00}) will be redirected to the next sink. + struct PeriodSink : public ResourceTableSink { + DayPeriodRulesDataSink &outer; + PeriodSink(DayPeriodRulesDataSink &outer) : outer(outer) {} + virtual ~PeriodSink(); + + virtual void put(const char *key, const ResourceValue &value, UErrorCode &errorCode) { + if (U_FAILURE(errorCode)) { return; } + + CutoffType type = getCutoffTypeFromString(key); + outer.addCutoff(type, value.getUnicodeString(errorCode), errorCode); + } + + virtual ResourceArraySink *getOrCreateArraySink(const char *key, int32_t, UErrorCode &errorCode) { + if (U_FAILURE(errorCode)) { return NULL; } + outer.cutoffType = getCutoffTypeFromString(key); + return &outer.cutoffSink; + } + + virtual void leave(UErrorCode &errorCode) { + if (U_FAILURE(errorCode)) { return; } + + outer.setDayPeriodForHoursFromCutoffs(errorCode); + for (int32_t i = 0; i < UPRV_LENGTHOF(outer.cutoffs); ++i) { + outer.cutoffs[i] = 0; + } + } + } periodSink; + + // Data root -> rules -> a rule set -> a period -> a cutoff type. + // Will enter this sink if 2+ times appear in a single cutoff type (e.g. before{6:00, 24:00}). + struct CutoffSink : public ResourceArraySink { + DayPeriodRulesDataSink &outer; + CutoffSink(DayPeriodRulesDataSink &outer) : outer(outer) {} + virtual ~CutoffSink(); + + virtual void put(int32_t, const ResourceValue &value, UErrorCode &errorCode) { + outer.addCutoff(outer.cutoffType, value.getUnicodeString(errorCode), errorCode); + } + } cutoffSink; + + // Members. + int32_t cutoffs[25]; // [0] thru [24]: 24 is allowed in "before 24". + + // "Path" to data. + int32_t ruleSetNum; + DayPeriodRules::DayPeriod period; + CutoffType cutoffType; + + // Helpers. + static int32_t parseSetNum(const UnicodeString &setNumStr, UErrorCode &errorCode) { + return parseSetNum(CStr(setNumStr)(), errorCode); + } + + static int32_t parseSetNum(const char *setNumStr, UErrorCode &errorCode) { + if (U_FAILURE(errorCode)) { return -1; } + + if (uprv_strncmp(setNumStr, "set", 3) != 0) { + errorCode = U_INVALID_FORMAT_ERROR; + return -1; + } + + int32_t i = 3; + int32_t setNum = 0; + while (setNumStr[i] != 0) { + int32_t digit = setNumStr[i] - '0'; + if (digit < 0 || 9 < digit) { + errorCode = U_INVALID_FORMAT_ERROR; + return -1; + } + setNum = 10 * setNum + digit; + ++i; + } + + // Rule set number must not be zero. (0 is used to indicate "not found" by hashmap.) + // Currently ICU data conveniently starts numbering rule sets from 1. + if (setNum == 0) { + errorCode = U_INVALID_FORMAT_ERROR; + return -1; + } else { + return setNum; + } + } + + void addCutoff(CutoffType type, UnicodeString hour_str, UErrorCode &errorCode) { + if (U_FAILURE(errorCode)) { return; } + + if (type == CUTOFF_TYPE_UNKNOWN) { + errorCode = U_INVALID_FORMAT_ERROR; + return; + } + + int32_t hour = parseHour(hour_str, errorCode); + if (U_FAILURE(errorCode)) { return; } + + cutoffs[hour] |= 1 << type; + } + + // Translate the cutoffs[] array to day period rules. + void setDayPeriodForHoursFromCutoffs(UErrorCode &errorCode) { + DayPeriodRules &rule = data->rules[ruleSetNum]; + + for (int32_t startHour = 0; startHour <= 24; ++startHour) { + // AT cutoffs must be either midnight or noon. + if (cutoffs[startHour] & (1 << CUTOFF_TYPE_AT)) { + if (startHour == 0 && period == DayPeriodRules::DAYPERIOD_MIDNIGHT) { + rule.fHasMidnight = TRUE; + } else if (startHour == 12 && period == DayPeriodRules::DAYPERIOD_NOON) { + rule.fHasNoon = TRUE; + } else { + errorCode = U_INVALID_FORMAT_ERROR; // Bad data. + return; + } + } + + // FROM/AFTER and BEFORE must come in a pair. + if (cutoffs[startHour] & (1 << CUTOFF_TYPE_FROM) || + cutoffs[startHour] & (1 << CUTOFF_TYPE_AFTER)) { + for (int32_t hour = startHour + 1;; ++hour) { + if (hour == startHour) { + // We've gone around the array once and can't find a BEFORE. + errorCode = U_INVALID_FORMAT_ERROR; + return; + } + if (hour == 25) { hour = 0; } + if (cutoffs[hour] & (1 << CUTOFF_TYPE_BEFORE)) { + rule.add(startHour, hour, period); + break; + } + } + } + } + } + + // Translate "before" to CUTOFF_TYPE_BEFORE, for example. + static CutoffType getCutoffTypeFromString(const char *type_str) { + if (uprv_strcmp(type_str, "from") == 0) { + return CUTOFF_TYPE_FROM; + } else if (uprv_strcmp(type_str, "before") == 0) { + return CUTOFF_TYPE_BEFORE; + } else if (uprv_strcmp(type_str, "after") == 0) { + return CUTOFF_TYPE_AFTER; + } else if (uprv_strcmp(type_str, "at") == 0) { + return CUTOFF_TYPE_AT; + } else { + return CUTOFF_TYPE_UNKNOWN; + } + } + + // Gets the numerical value of the hour from the Unicode string. + static int32_t parseHour(const UnicodeString &time, UErrorCode &errorCode) { + if (U_FAILURE(errorCode)) { + return 0; + } + + int32_t hourLimit = time.length() - 3; + // `time` must look like "x:00" or "xx:00". + // If length is wrong or `time` doesn't end with ":00", error out. + if ((hourLimit != 1 && hourLimit != 2) || + time[hourLimit] != 0x3A || time[hourLimit + 1] != 0x30 || + time[hourLimit + 2] != 0x30) { + errorCode = U_INVALID_FORMAT_ERROR; + return 0; + } + + // If `time` doesn't begin with a number in [0, 24], error out. + // Note: "24:00" is possible in "before 24:00". + int32_t hour = time[0] - 0x30; + if (hour < 0 || 9 < hour) { + errorCode = U_INVALID_FORMAT_ERROR; + return 0; + } + if (hourLimit == 2) { + int32_t hourDigit2 = time[1] - 0x30; + if (hourDigit2 < 0 || 9 < hourDigit2) { + errorCode = U_INVALID_FORMAT_ERROR; + return 0; + } + hour = hour * 10 + hourDigit2; + if (hour > 24) { + errorCode = U_INVALID_FORMAT_ERROR; + return 0; + } + } + + return hour; + } +}; // struct DayPeriodRulesDataSink + +struct DayPeriodRulesCountSink : public ResourceTableSink { + virtual ResourceTableSink *getOrCreateTableSink(const char *key, int32_t, UErrorCode &errorCode) { + if (U_FAILURE(errorCode)) { return NULL; } + + int32_t setNum = DayPeriodRulesDataSink::parseSetNum(key, errorCode); + if (setNum > data->maxRuleSetNum) { + data->maxRuleSetNum = setNum; + } + + return NULL; + } +}; + +// Out-of-line virtual destructors. +DayPeriodRulesDataSink::LocalesSink::~LocalesSink() {} +DayPeriodRulesDataSink::CutoffSink::~CutoffSink() {} +DayPeriodRulesDataSink::PeriodSink::~PeriodSink() {} +DayPeriodRulesDataSink::RuleSetSink::~RuleSetSink() {} +DayPeriodRulesDataSink::RulesSink::~RulesSink() {} +DayPeriodRulesDataSink::~DayPeriodRulesDataSink() {} + +namespace { + +UInitOnce initOnce = U_INITONCE_INITIALIZER; + +UBool dayPeriodRulesCleanup() { + delete[] data->rules; + uhash_close(data->localeToRuleSetNumMap); + delete data; + data = NULL; + return TRUE; +} + +} // namespace + +void DayPeriodRules::load(UErrorCode &errorCode) { + if (U_FAILURE(errorCode)) { + return; + } + + data = new DayPeriodRulesData(); + data->localeToRuleSetNumMap = uhash_open(uhash_hashChars, uhash_compareChars, NULL, &errorCode); + LocalUResourceBundlePointer rb_dayPeriods(ures_openDirect(NULL, "dayPeriods", &errorCode)); + + // Get the largest rule set number (so we allocate enough objects). + DayPeriodRulesCountSink countSink; + ures_getAllTableItemsWithFallback(rb_dayPeriods.getAlias(), "rules", countSink, errorCode); + + // Populate rules. + DayPeriodRulesDataSink sink; + ures_getAllTableItemsWithFallback(rb_dayPeriods.getAlias(), "", sink, errorCode); + + ucln_i18n_registerCleanup(UCLN_I18N_DAYPERIODRULES, dayPeriodRulesCleanup); +} + +const DayPeriodRules *DayPeriodRules::getInstance(const Locale &locale, UErrorCode &errorCode) { + umtx_initOnce(initOnce, DayPeriodRules::load, errorCode); + + // If the entire day period rules data doesn't conform to spec (even if the part we want + // does), return NULL. + if(U_FAILURE(errorCode)) { return NULL; } + + const char *localeCode = locale.getName(); + char name[ULOC_FULLNAME_CAPACITY]; + char parentName[ULOC_FULLNAME_CAPACITY]; + + if (uprv_strlen(localeCode) < ULOC_FULLNAME_CAPACITY) { + uprv_strcpy(name, localeCode); + + // Treat empty string as root. + if (*name == '\0') { + uprv_strcpy(name, "root"); + } + } else { + errorCode = U_BUFFER_OVERFLOW_ERROR; + return NULL; + } + + int32_t ruleSetNum = 0; // NB there is no rule set 0 and 0 is returned upon lookup failure. + while (*name != '\0') { + ruleSetNum = uhash_geti(data->localeToRuleSetNumMap, name); + if (ruleSetNum == 0) { + // name and parentName can't be the same pointer, so fill in parent then copy to child. + uloc_getParent(name, parentName, ULOC_FULLNAME_CAPACITY, &errorCode); + if (*parentName == '\0') { + // Saves a lookup in the hash table. + break; + } + uprv_strcpy(name, parentName); + } else { + break; + } + } + + if (ruleSetNum <= 0 || data->rules[ruleSetNum].getDayPeriodForHour(0) == DAYPERIOD_UNKNOWN) { + // If day period for hour 0 is UNKNOWN then day period for all hours are UNKNOWN. + // Data doesn't exist even with fallback. + return NULL; + } else { + return &data->rules[ruleSetNum]; + } +} + +DayPeriodRules::DayPeriodRules() : fHasMidnight(FALSE), fHasNoon(FALSE) { + for (int32_t i = 0; i < 24; ++i) { + fDayPeriodForHour[i] = DayPeriodRules::DAYPERIOD_UNKNOWN; + } +} + +double DayPeriodRules::getMidPointForDayPeriod( + DayPeriodRules::DayPeriod dayPeriod, UErrorCode &errorCode) const { + if (U_FAILURE(errorCode)) { return -1; } + + int32_t startHour = getStartHourForDayPeriod(dayPeriod, errorCode); + int32_t endHour = getEndHourForDayPeriod(dayPeriod, errorCode); + // Can't obtain startHour or endHour; bail out. + if (U_FAILURE(errorCode)) { return -1; } + + double midPoint = (startHour + endHour) / 2.0; + + if (startHour > endHour) { + // dayPeriod wraps around midnight. Shift midPoint by 12 hours, in the direction that + // lands it in [0, 24). + midPoint += 12; + if (midPoint >= 24) { + midPoint -= 24; + } + } + + return midPoint; +} + +int32_t DayPeriodRules::getStartHourForDayPeriod( + DayPeriodRules::DayPeriod dayPeriod, UErrorCode &errorCode) const { + if (U_FAILURE(errorCode)) { return -1; } + + if (dayPeriod == DAYPERIOD_MIDNIGHT) { return 0; } + if (dayPeriod == DAYPERIOD_NOON) { return 12; } + + if (fDayPeriodForHour[0] == dayPeriod && fDayPeriodForHour[23] == dayPeriod) { + // dayPeriod wraps around midnight. Start hour is later than end hour. + for (int32_t i = 22; i >= 1; --i) { + if (fDayPeriodForHour[i] != dayPeriod) { + return (i + 1); + } + } + } else { + for (int32_t i = 0; i <= 23; ++i) { + if (fDayPeriodForHour[i] == dayPeriod) { + return i; + } + } + } + + // dayPeriod doesn't exist in rule set; set error and exit. + errorCode = U_ILLEGAL_ARGUMENT_ERROR; + return -1; +} + +int32_t DayPeriodRules::getEndHourForDayPeriod( + DayPeriodRules::DayPeriod dayPeriod, UErrorCode &errorCode) const { + if (U_FAILURE(errorCode)) { return -1; } + + if (dayPeriod == DAYPERIOD_MIDNIGHT) { return 0; } + if (dayPeriod == DAYPERIOD_NOON) { return 12; } + + if (fDayPeriodForHour[0] == dayPeriod && fDayPeriodForHour[23] == dayPeriod) { + // dayPeriod wraps around midnight. End hour is before start hour. + for (int32_t i = 1; i <= 22; ++i) { + if (fDayPeriodForHour[i] != dayPeriod) { + // i o'clock is when a new period starts, therefore when the old period ends. + return i; + } + } + } else { + for (int32_t i = 23; i >= 0; --i) { + if (fDayPeriodForHour[i] == dayPeriod) { + return (i + 1); + } + } + } + + // dayPeriod doesn't exist in rule set; set error and exit. + errorCode = U_ILLEGAL_ARGUMENT_ERROR; + return -1; +} + +DayPeriodRules::DayPeriod DayPeriodRules::getDayPeriodFromString(const char *type_str) { + if (uprv_strcmp(type_str, "midnight") == 0) { + return DAYPERIOD_MIDNIGHT; + } else if (uprv_strcmp(type_str, "noon") == 0) { + return DAYPERIOD_NOON; + } else if (uprv_strcmp(type_str, "morning1") == 0) { + return DAYPERIOD_MORNING1; + } else if (uprv_strcmp(type_str, "afternoon1") == 0) { + return DAYPERIOD_AFTERNOON1; + } else if (uprv_strcmp(type_str, "evening1") == 0) { + return DAYPERIOD_EVENING1; + } else if (uprv_strcmp(type_str, "night1") == 0) { + return DAYPERIOD_NIGHT1; + } else if (uprv_strcmp(type_str, "morning2") == 0) { + return DAYPERIOD_MORNING2; + } else if (uprv_strcmp(type_str, "afternoon2") == 0) { + return DAYPERIOD_AFTERNOON2; + } else if (uprv_strcmp(type_str, "evening2") == 0) { + return DAYPERIOD_EVENING2; + } else if (uprv_strcmp(type_str, "night2") == 0) { + return DAYPERIOD_NIGHT2; + } else { + return DAYPERIOD_UNKNOWN; + } +} + +void DayPeriodRules::add(int32_t startHour, int32_t limitHour, DayPeriod period) { + for (int32_t i = startHour; i != limitHour; ++i) { + if (i == 24) { i = 0; } + fDayPeriodForHour[i] = period; + } +} + +UBool DayPeriodRules::allHoursAreSet() { + for (int32_t i = 0; i < 24; ++i) { + if (fDayPeriodForHour[i] == DAYPERIOD_UNKNOWN) { return FALSE; } + } + + return TRUE; +} + + + +U_NAMESPACE_END diff --git a/icu4c/source/i18n/dayperiodrules.h b/icu4c/source/i18n/dayperiodrules.h new file mode 100644 index 00000000000..d207732ff75 --- /dev/null +++ b/icu4c/source/i18n/dayperiodrules.h @@ -0,0 +1,85 @@ +/* +******************************************************************************* +* Copyright (C) 2016, International Business Machines +* Corporation and others. All Rights Reserved. +******************************************************************************* +* dayperiodrules.h +* +* created on: 2016-01-20 +* created by: kazede +*/ + +#ifndef DAYPERIODRULES_H +#define DAYPERIODRULES_H + +#include "unicode/locid.h" +#include "unicode/unistr.h" +#include "unicode/uobject.h" +#include "unicode/utypes.h" +#include "resource.h" +#include "uhash.h" + + + +U_NAMESPACE_BEGIN + +struct DayPeriodRulesDataSink; + +class DayPeriodRules : public UMemory { + friend struct DayPeriodRulesDataSink; +public: + enum DayPeriod { + DAYPERIOD_UNKNOWN = -1, + DAYPERIOD_MIDNIGHT, + DAYPERIOD_NOON, + DAYPERIOD_MORNING1, + DAYPERIOD_AFTERNOON1, + DAYPERIOD_EVENING1, + DAYPERIOD_NIGHT1, + DAYPERIOD_MORNING2, + DAYPERIOD_AFTERNOON2, + DAYPERIOD_EVENING2, + DAYPERIOD_NIGHT2 + }; + + static const DayPeriodRules *getInstance(const Locale &locale, UErrorCode &errorCode); + + UBool hasMidnight() const { return fHasMidnight; } + UBool hasNoon() const { return fHasNoon; } + DayPeriod getDayPeriodForHour(int32_t hour) const { return fDayPeriodForHour[hour]; } + + // Returns the center of dayPeriod. Half hours are indicated with a .5 . + double getMidPointForDayPeriod(DayPeriod dayPeriod, UErrorCode &errorCode) const; + +private: + DayPeriodRules(); + + // Translates "morning1" to DAYPERIOD_MORNING1, for example. + static DayPeriod getDayPeriodFromString(const char *type_str); + + static void load(UErrorCode &errorCode); + + // Sets period type for all hours in [startHour, limitHour). + void add(int32_t startHour, int32_t limitHour, DayPeriod period); + + // Returns TRUE if for all i, DayPeriodForHour[i] has a type other than UNKNOWN. + // Values of HasNoon and HasMidnight do not affect the return value. + UBool allHoursAreSet(); + + // Returns the hour that starts dayPeriod. Returns 0 for MIDNIGHT and 12 for NOON. + int32_t getStartHourForDayPeriod(DayPeriod dayPeriod, UErrorCode &errorCode) const; + + // Returns the hour that ends dayPeriod, i.e. that starts the next period. + // E.g. if fDayPeriodForHour[13] thru [16] are AFTERNOON1, then this function returns 17 if + // queried with AFTERNOON1. + // Returns 0 for MIDNIGHT and 12 for NOON. + int32_t getEndHourForDayPeriod(DayPeriod dayPeriod, UErrorCode &errorCode) const; + + UBool fHasMidnight; + UBool fHasNoon; + DayPeriod fDayPeriodForHour[24]; +}; + +U_NAMESPACE_END + +#endif /* DAYPERIODRULES_H */ diff --git a/icu4c/source/i18n/dtfmtsym.cpp b/icu4c/source/i18n/dtfmtsym.cpp index ba709ee76e9..206d7a26a9e 100644 --- a/icu4c/source/i18n/dtfmtsym.cpp +++ b/icu4c/source/i18n/dtfmtsym.cpp @@ -1,7 +1,7 @@ /* ******************************************************************************* -* Copyright (C) 1997-2016, International Business Machines Corporation and -* others. All Rights Reserved. +* Copyright (C) 1997-2016, International Business Machines Corporation and * +* others. All Rights Reserved. * ******************************************************************************* * * File DTFMTSYM.CPP @@ -52,9 +52,9 @@ */ #if UDAT_HAS_PATTERN_CHAR_FOR_TIME_SEPARATOR -#define PATTERN_CHARS_LEN 36 +#define PATTERN_CHARS_LEN 38 #else -#define PATTERN_CHARS_LEN 35 +#define PATTERN_CHARS_LEN 37 #endif /** @@ -62,19 +62,19 @@ * locales use the same these unlocalized pattern characters. */ static const UChar gPatternChars[] = { -#if UDAT_HAS_PATTERN_CHAR_FOR_TIME_SEPARATOR - // GyMdkHmsSEDFwWahKzYeugAZvcLQqVUOXxr: -#else - // GyMdkHmsSEDFwWahKzYeugAZvcLQqVUOXxr -#endif + // if UDAT_HAS_PATTERN_CHAR_FOR_TIME_SEPARATOR: + // GyMdkHmsSEDFwWahKzYeugAZvcLQqVUOXxrbB: + // else: + // GyMdkHmsSEDFwWahKzYeugAZvcLQqVUOXxrbB + 0x47, 0x79, 0x4D, 0x64, 0x6B, 0x48, 0x6D, 0x73, 0x53, 0x45, 0x44, 0x46, 0x77, 0x57, 0x61, 0x68, 0x4B, 0x7A, 0x59, 0x65, 0x75, 0x67, 0x41, 0x5A, 0x76, 0x63, 0x4c, 0x51, 0x71, 0x56, + 0x55, 0x4F, 0x58, 0x78, 0x72, 0x62, 0x42, #if UDAT_HAS_PATTERN_CHAR_FOR_TIME_SEPARATOR - 0x55, 0x4F, 0x58, 0x78, 0x72, 0x3a, 0 -#else - 0x55, 0x4F, 0x58, 0x78, 0x72, 0 + 0x3a, #endif + 0 }; //------------------------------------------------------ @@ -216,6 +216,7 @@ static const char gQuartersTag[]="quarters"; static const char gNumberElementsTag[]="NumberElements"; static const char gSymbolsTag[]="symbols"; static const char gTimeSeparatorTag[]="timeSeparator"; +static const char gDayPeriodTag[]="dayPeriod"; // static const char gZoneStringsTag[]="zoneStrings"; @@ -384,6 +385,18 @@ DateFormatSymbols::copyData(const DateFormatSymbols& other) { assignArray(fShortQuarters, fShortQuartersCount, other.fShortQuarters, other.fShortQuartersCount); assignArray(fStandaloneQuarters, fStandaloneQuartersCount, other.fStandaloneQuarters, other.fStandaloneQuartersCount); assignArray(fStandaloneShortQuarters, fStandaloneShortQuartersCount, other.fStandaloneShortQuarters, other.fStandaloneShortQuartersCount); + assignArray(fWideDayPeriods, fWideDayPeriodsCount, + other.fWideDayPeriods, other.fWideDayPeriodsCount); + assignArray(fNarrowDayPeriods, fNarrowDayPeriodsCount, + other.fNarrowDayPeriods, other.fNarrowDayPeriodsCount); + assignArray(fAbbreviatedDayPeriods, fAbbreviatedDayPeriodsCount, + other.fAbbreviatedDayPeriods, other.fAbbreviatedDayPeriodsCount); + assignArray(fStandaloneWideDayPeriods, fStandaloneWideDayPeriodsCount, + other.fStandaloneWideDayPeriods, other.fStandaloneWideDayPeriodsCount); + assignArray(fStandaloneNarrowDayPeriods, fStandaloneNarrowDayPeriodsCount, + other.fStandaloneNarrowDayPeriods, other.fStandaloneNarrowDayPeriodsCount); + assignArray(fStandaloneAbbreviatedDayPeriods, fStandaloneAbbreviatedDayPeriodsCount, + other.fStandaloneAbbreviatedDayPeriods, other.fStandaloneAbbreviatedDayPeriodsCount); if (other.fLeapMonthPatterns != NULL) { assignArray(fLeapMonthPatterns, fLeapMonthPatternsCount, other.fLeapMonthPatterns, other.fLeapMonthPatternsCount); } else { @@ -402,7 +415,7 @@ DateFormatSymbols::copyData(const DateFormatSymbols& other) { fShortZodiacNames = NULL; fShortZodiacNamesCount = 0; } - + if (other.fZoneStrings != NULL) { fZoneStringsColCount = other.fZoneStringsColCount; fZoneStringsRowCount = other.fZoneStringsRowCount; @@ -419,7 +432,7 @@ DateFormatSymbols::copyData(const DateFormatSymbols& other) { // fastCopyFrom() - see assignArray comments fLocalPatternChars.fastCopyFrom(other.fLocalPatternChars); - + uprv_memcpy(fCapitalization, other.fCapitalization, sizeof(fCapitalization)); } @@ -441,32 +454,38 @@ DateFormatSymbols::~DateFormatSymbols() void DateFormatSymbols::dispose() { - if (fEras) delete[] fEras; - if (fEraNames) delete[] fEraNames; - if (fNarrowEras) delete[] fNarrowEras; - if (fMonths) delete[] fMonths; - if (fShortMonths) delete[] fShortMonths; - if (fNarrowMonths) delete[] fNarrowMonths; - if (fStandaloneMonths) delete[] fStandaloneMonths; - if (fStandaloneShortMonths) delete[] fStandaloneShortMonths; - if (fStandaloneNarrowMonths) delete[] fStandaloneNarrowMonths; - if (fWeekdays) delete[] fWeekdays; - if (fShortWeekdays) delete[] fShortWeekdays; - if (fShorterWeekdays) delete[] fShorterWeekdays; - if (fNarrowWeekdays) delete[] fNarrowWeekdays; - if (fStandaloneWeekdays) delete[] fStandaloneWeekdays; - if (fStandaloneShortWeekdays) delete[] fStandaloneShortWeekdays; - if (fStandaloneShorterWeekdays) delete[] fStandaloneShorterWeekdays; - if (fStandaloneNarrowWeekdays) delete[] fStandaloneNarrowWeekdays; - if (fAmPms) delete[] fAmPms; - if (fNarrowAmPms) delete[] fNarrowAmPms; - if (fQuarters) delete[] fQuarters; - if (fShortQuarters) delete[] fShortQuarters; - if (fStandaloneQuarters) delete[] fStandaloneQuarters; - if (fStandaloneShortQuarters) delete[] fStandaloneShortQuarters; - if (fLeapMonthPatterns) delete[] fLeapMonthPatterns; - if (fShortYearNames) delete[] fShortYearNames; - if (fShortZodiacNames) delete[] fShortZodiacNames; + delete[] fEras; + delete[] fEraNames; + delete[] fNarrowEras; + delete[] fMonths; + delete[] fShortMonths; + delete[] fNarrowMonths; + delete[] fStandaloneMonths; + delete[] fStandaloneShortMonths; + delete[] fStandaloneNarrowMonths; + delete[] fWeekdays; + delete[] fShortWeekdays; + delete[] fShorterWeekdays; + delete[] fNarrowWeekdays; + delete[] fStandaloneWeekdays; + delete[] fStandaloneShortWeekdays; + delete[] fStandaloneShorterWeekdays; + delete[] fStandaloneNarrowWeekdays; + delete[] fAmPms; + delete[] fNarrowAmPms; + delete[] fQuarters; + delete[] fShortQuarters; + delete[] fStandaloneQuarters; + delete[] fStandaloneShortQuarters; + delete[] fLeapMonthPatterns; + delete[] fShortYearNames; + delete[] fShortZodiacNames; + delete[] fAbbreviatedDayPeriods; + delete[] fWideDayPeriods; + delete[] fNarrowDayPeriods; + delete[] fStandaloneAbbreviatedDayPeriods; + delete[] fStandaloneWideDayPeriods; + delete[] fStandaloneNarrowDayPeriods; disposeZoneStrings(); } @@ -539,6 +558,12 @@ DateFormatSymbols::operator==(const DateFormatSymbols& other) const fLeapMonthPatternsCount == other.fLeapMonthPatternsCount && fShortYearNamesCount == other.fShortYearNamesCount && fShortZodiacNamesCount == other.fShortZodiacNamesCount && + fAbbreviatedDayPeriodsCount == other.fAbbreviatedDayPeriodsCount && + fWideDayPeriodsCount == other.fWideDayPeriodsCount && + fNarrowDayPeriodsCount == other.fNarrowDayPeriodsCount && + fStandaloneAbbreviatedDayPeriodsCount == other.fStandaloneAbbreviatedDayPeriodsCount && + fStandaloneWideDayPeriodsCount == other.fStandaloneWideDayPeriodsCount && + fStandaloneNarrowDayPeriodsCount == other.fStandaloneNarrowDayPeriodsCount && (uprv_memcmp(fCapitalization, other.fCapitalization, sizeof(fCapitalization))==0)) { // Now compare the arrays themselves @@ -568,7 +593,16 @@ DateFormatSymbols::operator==(const DateFormatSymbols& other) const arrayCompare(fStandaloneShortQuarters, other.fStandaloneShortQuarters, fStandaloneShortQuartersCount) && arrayCompare(fLeapMonthPatterns, other.fLeapMonthPatterns, fLeapMonthPatternsCount) && arrayCompare(fShortYearNames, other.fShortYearNames, fShortYearNamesCount) && - arrayCompare(fShortZodiacNames, other.fShortZodiacNames, fShortZodiacNamesCount)) + arrayCompare(fShortZodiacNames, other.fShortZodiacNames, fShortZodiacNamesCount) && + arrayCompare(fAbbreviatedDayPeriods, other.fAbbreviatedDayPeriods, fAbbreviatedDayPeriodsCount) && + arrayCompare(fWideDayPeriods, other.fWideDayPeriods, fWideDayPeriodsCount) && + arrayCompare(fNarrowDayPeriods, other.fNarrowDayPeriods, fNarrowDayPeriodsCount) && + arrayCompare(fStandaloneAbbreviatedDayPeriods, other.fStandaloneAbbreviatedDayPeriods, + fStandaloneAbbreviatedDayPeriodsCount) && + arrayCompare(fStandaloneWideDayPeriods, other.fStandaloneWideDayPeriods, + fStandaloneWideDayPeriodsCount) && + arrayCompare(fStandaloneNarrowDayPeriods, other.fStandaloneNarrowDayPeriods, + fStandaloneWideDayPeriodsCount)) { // Compare the contents of fZoneStrings if (fZoneStrings == NULL && other.fZoneStrings == NULL) { @@ -964,7 +998,7 @@ DateFormatSymbols::setMonths(const UnicodeString* monthsArray, int32_t count, Dt fNarrowMonths = newUnicodeStringArray(count); uprv_arrayCopy( monthsArray,fNarrowMonths,count); fNarrowMonthsCount = count; - break; + break; default : break; } @@ -991,7 +1025,7 @@ DateFormatSymbols::setMonths(const UnicodeString* monthsArray, int32_t count, Dt fStandaloneNarrowMonths = newUnicodeStringArray(count); uprv_arrayCopy( monthsArray,fStandaloneNarrowMonths,count); fStandaloneNarrowMonthsCount = count; - break; + break; default : break; } @@ -1065,7 +1099,7 @@ DateFormatSymbols::setWeekdays(const UnicodeString* weekdaysArray, int32_t count fNarrowWeekdays = newUnicodeStringArray(count); uprv_arrayCopy(weekdaysArray, fNarrowWeekdays, count); fNarrowWeekdaysCount = count; - break; + break; case DT_WIDTH_COUNT : break; } @@ -1099,7 +1133,7 @@ DateFormatSymbols::setWeekdays(const UnicodeString* weekdaysArray, int32_t count fStandaloneNarrowWeekdays = newUnicodeStringArray(count); uprv_arrayCopy(weekdaysArray, fStandaloneNarrowWeekdays, count); fStandaloneNarrowWeekdaysCount = count; - break; + break; case DT_WIDTH_COUNT : break; } @@ -1141,7 +1175,7 @@ DateFormatSymbols::setQuarters(const UnicodeString* quartersArray, int32_t count uprv_arrayCopy( quartersArray,fNarrowQuarters,count); fNarrowQuartersCount = count; */ - break; + break; default : break; } @@ -1170,7 +1204,7 @@ DateFormatSymbols::setQuarters(const UnicodeString* quartersArray, int32_t count uprv_arrayCopy( quartersArray,fStandaloneNarrowQuarters,count); fStandaloneNarrowQuartersCount = count; */ - break; + break; default : break; } @@ -1472,6 +1506,42 @@ static const ContextUsageTypeNameToEnumValue contextUsageTypeMap[] = { { NULL, (DateFormatSymbols::ECapitalizationContextUsageType)0 }, }; +// Resource keys to look up localized strings for day periods. +// The first one must be midnight and the second must be noon, so that their indices coincide +// with the am/pm field. Formatting and parsing code for day periods relies on this coincidence. +static const char *dayPeriodKeys[] = {"midnight", "noon", + "morning1", "afternoon1", "evening1", "night1", + "morning2", "afternoon2", "evening2", "night2"}; + +UnicodeString* loadDayPeriodStrings(CalendarData &calData, const char *tag, UBool standalone, + int32_t &stringCount, UErrorCode &status) { + if (U_FAILURE(status)) { + return NULL; + } + + UResourceBundle *dayPeriodData; + + if (standalone) { + dayPeriodData = calData.getByKey3(gDayPeriodTag, gNamesStandaloneTag, tag, status); + } else { + dayPeriodData = calData.getByKey2(gDayPeriodTag, tag, status); + } + + stringCount = UPRV_LENGTHOF(dayPeriodKeys); + UnicodeString *strings = new UnicodeString[stringCount]; + for (int32_t i = 0; i < stringCount; ++i) { + //TODO: Check if there are fallbacks/aliases defined in the data; e.g., if there + //is no wide string, then use the narrow one? + strings[i].fastCopyFrom(ures_getUnicodeStringByKey(dayPeriodData, dayPeriodKeys[i], &status)); + if (U_FAILURE(status)) { + // string[i] will be bogus if ures_getUnicodeString() returns with an error, + // which is just the behavior we want. Simply reset the error code. + status = U_ZERO_ERROR; + } + } + return strings; +} + void DateFormatSymbols::initializeData(const Locale& locale, const char *type, UErrorCode& status, UBool useLastResortData) { @@ -1536,6 +1606,18 @@ DateFormatSymbols::initializeData(const Locale& locale, const char *type, UError fZoneStringsColCount = 0; fZoneStrings = NULL; fLocaleZoneStrings = NULL; + fAbbreviatedDayPeriods = NULL; + fAbbreviatedDayPeriodsCount = 0; + fWideDayPeriods = NULL; + fWideDayPeriodsCount = 0; + fNarrowDayPeriods = NULL; + fNarrowDayPeriodsCount = 0; + fStandaloneAbbreviatedDayPeriods = NULL; + fStandaloneAbbreviatedDayPeriodsCount = 0; + fStandaloneWideDayPeriods = NULL; + fStandaloneWideDayPeriodsCount = 0; + fStandaloneNarrowDayPeriods = NULL; + fStandaloneNarrowDayPeriodsCount = 0; uprv_memset(fCapitalization, 0, sizeof(fCapitalization)); // We need to preserve the requested locale for @@ -1543,7 +1625,7 @@ DateFormatSymbols::initializeData(const Locale& locale, const char *type, UError // is region sensitive, thus, bundle locale bundle's locale // is not sufficient. fZSFLocale = locale; - + if (U_FAILURE(status)) return; /** @@ -1693,6 +1775,19 @@ DateFormatSymbols::initializeData(const Locale& locale, const char *type, UError fTimeSeparator.setTo(DateFormatSymbols::DEFAULT_TIME_SEPARATOR); } + fWideDayPeriods = loadDayPeriodStrings(calData, gNamesWideTag, FALSE, + fWideDayPeriodsCount, status); + fNarrowDayPeriods = loadDayPeriodStrings(calData, gNamesNarrowTag, FALSE, + fNarrowDayPeriodsCount, status); + fAbbreviatedDayPeriods = loadDayPeriodStrings(calData, gNamesAbbrTag, FALSE, + fAbbreviatedDayPeriodsCount, status); + fStandaloneWideDayPeriods = loadDayPeriodStrings(calData, gNamesWideTag, TRUE, + fStandaloneWideDayPeriodsCount, status); + fStandaloneNarrowDayPeriods = loadDayPeriodStrings(calData, gNamesNarrowTag, TRUE, + fStandaloneNarrowDayPeriodsCount, status); + fStandaloneAbbreviatedDayPeriods = loadDayPeriodStrings(calData, gNamesAbbrTag, TRUE, + fStandaloneAbbreviatedDayPeriodsCount, status); + UResourceBundle *weekdaysData = NULL; // Data closed by calData UResourceBundle *abbrWeekdaysData = NULL; // Data closed by calData UResourceBundle *shorterWeekdaysData = NULL; // Data closed by calData @@ -1990,7 +2085,7 @@ cleanup: ures_close(narrowEras); } -Locale +Locale DateFormatSymbols::getLocale(ULocDataLocaleType type, UErrorCode& status) const { U_LOCALE_BASED(locBased, *this); return locBased.getLocale(type, status); diff --git a/icu4c/source/i18n/i18n.vcxproj b/icu4c/source/i18n/i18n.vcxproj index e192b69a855..6dcdb25a46e 100644 --- a/icu4c/source/i18n/i18n.vcxproj +++ b/icu4c/source/i18n/i18n.vcxproj @@ -310,6 +310,7 @@ + @@ -608,6 +609,7 @@ + diff --git a/icu4c/source/i18n/i18n.vcxproj.filters b/icu4c/source/i18n/i18n.vcxproj.filters index 74f29009486..4f618720862 100644 --- a/icu4c/source/i18n/i18n.vcxproj.filters +++ b/icu4c/source/i18n/i18n.vcxproj.filters @@ -57,6 +57,9 @@ formatting + + formatting + formatting @@ -691,6 +694,9 @@ formatting + + formatting + formatting diff --git a/icu4c/source/i18n/smpdtfmt.cpp b/icu4c/source/i18n/smpdtfmt.cpp index 48d6cf069ac..b7d93af90f0 100644 --- a/icu4c/source/i18n/smpdtfmt.cpp +++ b/icu4c/source/i18n/smpdtfmt.cpp @@ -1,7 +1,7 @@ /* ******************************************************************************* -* Copyright (C) 1997-2016, International Business Machines Corporation and -* others. All Rights Reserved. +* Copyright (C) 1997-2016, International Business Machines Corporation and * +* others. All Rights Reserved. * ******************************************************************************* * * File SMPDTFMT.CPP @@ -17,7 +17,7 @@ * Removed getZoneIndex (added in DateFormatSymbols) * Removed subParseLong * Removed chk -* 02/22/99 stephen Removed character literals for EBCDIC safety +* 02/22/99 stephen Removed character literals for EBCDIC safety * 10/14/99 aliu Updated 2-digit year parsing so that only "00" thru * "99" are recognized. {j28 4182066} * 11/15/99 weiv Added support for week of year/day of week format @@ -63,6 +63,10 @@ #include "smpdtfst.h" #include "sharednumberformat.h" #include "ustr_imp.h" +#include "charstr.h" +#include "uvector.h" +#include "cstr.h" +#include "dayperiodrules.h" #if defined( U_DEBUG_CALSVC ) || defined (U_DEBUG_CAL) #include @@ -622,6 +626,8 @@ SimpleDateFormat& SimpleDateFormat::operator=(const SimpleDateFormat& other) fHaveDefaultCentury = other.fHaveDefaultCentury; fPattern = other.fPattern; + fHasMinute = other.fHasMinute; + fHasSecond = other.fHasSecond; // TimeZoneFormat in ICU4C only depends on a locale for now if (fLocale != other.fLocale) { @@ -889,6 +895,8 @@ SimpleDateFormat::initialize(const Locale& locale, { status = U_MISSING_RESOURCE_ERROR; } + + parsePattern(); } /* Initialize the fields we use to disambiguate ambiguous years. Separate @@ -969,7 +977,7 @@ SimpleDateFormat::_format(Calendar& cal, UnicodeString& appendTo, FieldPositionHandler& handler, UErrorCode& status) const { if ( U_FAILURE(status) ) { - return appendTo; + return appendTo; } Calendar* workCal = &cal; Calendar* calClone = NULL; @@ -1161,6 +1169,7 @@ SimpleDateFormat::fgPatternIndexToCalendarField[] = /*O*/ UCAL_ZONE_OFFSET, /*Xx*/ UCAL_ZONE_OFFSET, UCAL_ZONE_OFFSET, /*r*/ UCAL_EXTENDED_YEAR, + /*bB*/ UCAL_FIELD_COUNT, UCAL_FIELD_COUNT, // no mappings to calendar fields #if UDAT_HAS_PATTERN_CHAR_FOR_TIME_SEPARATOR /*:*/ UCAL_FIELD_COUNT, /* => no useful mapping to any calendar field */ #else @@ -1189,6 +1198,7 @@ SimpleDateFormat::fgPatternIndexToDateFormatField[] = { /*O*/ UDAT_TIMEZONE_LOCALIZED_GMT_OFFSET_FIELD, /*Xx*/ UDAT_TIMEZONE_ISO_FIELD, UDAT_TIMEZONE_ISO_LOCAL_FIELD, /*r*/ UDAT_RELATED_YEAR_FIELD, + /*bB*/ UDAT_AM_PM_MIDNIGHT_NOON_FIELD, UDAT_FLEXIBLE_DAY_PERIOD_FIELD, #if UDAT_HAS_PATTERN_CHAR_FOR_TIME_SEPARATOR /*:*/ UDAT_TIME_SEPARATOR_FIELD, #else @@ -1419,7 +1429,7 @@ SimpleDateFormat::subFormat(UnicodeString &appendTo, return; } UnicodeString hebr("hebr", 4, US_INV); - + switch (patternCharIndex) { // for any "G" symbol, write out the appropriate era string @@ -1788,6 +1798,126 @@ SimpleDateFormat::subFormat(UnicodeString &appendTo, zeroPaddingNumber(currentNumberFormat,appendTo, (value/3) + 1, count, maxIntCount); break; + case UDAT_AM_PM_MIDNIGHT_NOON_FIELD: + { + const UnicodeString *toAppend = NULL; + int32_t hour = cal.get(UCAL_HOUR_OF_DAY, status); + + // For "midnight" and "noon": + // Time, as displayed, must be exactly noon or midnight. + // This means minutes and seconds, if present, must be zero. + if ((hour == 0 || hour == 12) && + (!fHasMinute || cal.get(UCAL_MINUTE, status) == 0) && + (!fHasSecond || cal.get(UCAL_SECOND, status) == 0)) { + // Stealing am/pm value to use as our array index. + // It works out: am/midnight are both 0, pm/noon are both 1, + // 12 am is 12 midnight, and 12 pm is 12 noon. + int32_t value = cal.get(UCAL_AM_PM, status); + + if (count <= 3) { + toAppend = &fSymbols->fAbbreviatedDayPeriods[value]; + } else if (count == 4 || count > 5) { + toAppend = &fSymbols->fWideDayPeriods[value]; + } else { // count == 5 + toAppend = &fSymbols->fNarrowDayPeriods[value]; + } + } + + // toAppend is NULL if time isn't exactly midnight or noon (as displayed). + // toAppend is bogus if time is midnight or noon, but no localized string exists. + // In either case, fall back to am/pm. + if (toAppend == NULL || toAppend->isBogus()) { + // Reformat with identical arguments except ch, now changed to 'a'. + subFormat(appendTo, 0x61, count, capitalizationContext, fieldNum, + handler, cal, mutableNFs, status); + } else { + appendTo += *toAppend; + } + + break; + } + + case UDAT_FLEXIBLE_DAY_PERIOD_FIELD: + { + // TODO: Maybe fetch the DayperiodRules during initialization (instead of at the first + // loading of an instance) if a relevant pattern character (b or B) is used. + const DayPeriodRules *ruleSet = DayPeriodRules::getInstance(this->getSmpFmtLocale(), status); + if (U_FAILURE(status)) { + // Data doesn't conform to spec, therefore loading failed. + break; + } + if (ruleSet == NULL) { + // Data doesn't exist for the locale we're looking for. + // Falling back to am/pm. + subFormat(appendTo, 0x61, count, capitalizationContext, fieldNum, + handler, cal, mutableNFs, status); + break; + } + + // Get current display time. + int32_t hour = cal.get(UCAL_HOUR_OF_DAY, status); + int32_t minute = 0; + if (fHasMinute) { + minute = cal.get(UCAL_MINUTE, status); + } + int32_t second = 0; + if (fHasSecond) { + second = cal.get(UCAL_SECOND, status); + } + + // Determine day period. + DayPeriodRules::DayPeriod periodType; + if (hour == 0 && minute == 0 && second == 0 && ruleSet->hasMidnight()) { + periodType = DayPeriodRules::DAYPERIOD_MIDNIGHT; + } else if (hour == 12 && minute == 0 && second == 0 && ruleSet->hasNoon()) { + periodType = DayPeriodRules::DAYPERIOD_NOON; + } else { + periodType = ruleSet->getDayPeriodForHour(hour); + } + + // Rule set exists, therefore periodType can't be UNKNOWN. + // Get localized string. + U_ASSERT(periodType != DayPeriodRules::DAYPERIOD_UNKNOWN); + UnicodeString *toAppend = NULL; + + int32_t index = (int32_t)periodType; + if (count <= 3) { + toAppend = &fSymbols->fAbbreviatedDayPeriods[index]; // i.e. short + } else if (count == 4 || count > 5) { + toAppend = &fSymbols->fWideDayPeriods[index]; + } else { // count == 5 + toAppend = &fSymbols->fNarrowDayPeriods[index]; + } + + // Fallback schedule: + // Midnight/Noon -> General Periods -> AM/PM. + + // Midnight/Noon -> General Periods. + if (toAppend->isBogus() && + (periodType == DayPeriodRules::DAYPERIOD_MIDNIGHT || periodType == DayPeriodRules::DAYPERIOD_NOON)) { + periodType = ruleSet->getDayPeriodForHour(hour); + index = (int32_t)periodType; + + if (count <= 3) { + toAppend = &fSymbols->fAbbreviatedDayPeriods[index]; // i.e. short + } else if (count == 4 || count > 5) { + toAppend = &fSymbols->fWideDayPeriods[index]; + } else { // count == 5 + toAppend = &fSymbols->fNarrowDayPeriods[index]; + } + } + + // General Periods -> AM/PM. + if (toAppend->isBogus()) { + subFormat(appendTo, 0x61, count, capitalizationContext, fieldNum, + handler, cal, mutableNFs, status); + } + else { + appendTo += *toAppend; + } + + break; + } // all of the other pattern symbols can be formatted as simple numbers with // appropriate zero padding @@ -1830,7 +1960,7 @@ void SimpleDateFormat::adoptNumberFormat(NumberFormat *formatToAdopt) { fixNumberFormatForDates(*formatToAdopt); delete fNumberFormat; fNumberFormat = formatToAdopt; - + // We successfully set the default number format. Now delete the overrides // (can't fail). if (fSharedNumberFormatters) { @@ -1956,6 +2086,9 @@ SimpleDateFormat::parse(const UnicodeString& text, Calendar& cal, ParsePosition& } int32_t start = pos; + // Hold the day period until everything else is parsed, because we need + // the hour to interpret time correctly. + int32_t dayPeriodInt = -1; UBool ambiguousYear[] = { FALSE }; int32_t saveHebrewMonth = -1; @@ -1994,7 +2127,7 @@ SimpleDateFormat::parse(const UnicodeString& text, Calendar& cal, ParsePosition& goto ExitParse; } } - + if (fSymbols->fLeapMonthPatterns != NULL && fSymbols->fLeapMonthPatternsCount >= DateFormatSymbols::kMonthPatternsCount) { numericLeapMonthFormatter = new MessageFormat(fSymbols->fLeapMonthPatterns[DateFormatSymbols::kLeapMonthPatternNumeric], fLocale, status); if (numericLeapMonthFormatter == NULL) { @@ -2070,7 +2203,7 @@ SimpleDateFormat::parse(const UnicodeString& text, Calendar& cal, ParsePosition& // fields. else if (ch != 0x6C) { // pattern char 'l' (SMALL LETTER L) just gets ignored int32_t s = subParse(text, pos, ch, count, - FALSE, TRUE, ambiguousYear, saveHebrewMonth, *workCal, i, numericLeapMonthFormatter, &tzTimeType, mutableNFs); + FALSE, TRUE, ambiguousYear, saveHebrewMonth, *workCal, i, numericLeapMonthFormatter, &tzTimeType, mutableNFs, &dayPeriodInt); if (s == -pos-1) { // era not present, in special cases allow this to continue @@ -2106,7 +2239,7 @@ SimpleDateFormat::parse(const UnicodeString& text, Calendar& cal, ParsePosition& else { abutPat = -1; // End of any abutting fields - + if (! matchLiterals(fPattern, i, text, pos, getBooleanAttribute(UDAT_PARSE_ALLOW_WHITESPACE, status), getBooleanAttribute(UDAT_PARSE_PARTIAL_LITERAL_MATCH, status), isLenient())) { status = U_PARSE_ERROR; goto ExitParse; @@ -2122,6 +2255,76 @@ SimpleDateFormat::parse(const UnicodeString& text, Calendar& cal, ParsePosition& } } + // If dayPeriod is set, use it in conjunction with hour-of-day to determine am/pm. + if (dayPeriodInt >= 0) { + DayPeriodRules::DayPeriod dayPeriod = (DayPeriodRules::DayPeriod)dayPeriodInt; + const DayPeriodRules *ruleSet = DayPeriodRules::getInstance(this->getSmpFmtLocale(), status); + + if (!cal.isSet(UCAL_HOUR) && !cal.isSet(UCAL_HOUR_OF_DAY)) { + // If hour is not set, set time to the midpoint of current day period, overwriting + // minutes if it's set. + double midPoint = ruleSet->getMidPointForDayPeriod(dayPeriod, status); + + // If we can't get midPoint we do nothing. + if (U_SUCCESS(status)) { + // Truncate midPoint toward zero to get the hour. + // Any leftover means it was a half-hour. + int32_t midPointHour = (int32_t) midPoint; + int32_t midPointMinute = (midPoint - midPointHour) > 0 ? 30 : 0; + + // No need to set am/pm because hour-of-day is set last therefore takes precedence. + cal.set(UCAL_HOUR_OF_DAY, midPointHour); + cal.set(UCAL_MINUTE, midPointMinute); + } + } else { + int hourOfDay; + + if (cal.isSet(UCAL_HOUR_OF_DAY)) { // Hour is parsed in 24-hour format. + hourOfDay = cal.get(UCAL_HOUR_OF_DAY, status); + } else { // Hour is parsed in 12-hour format. + hourOfDay = cal.get(UCAL_HOUR, status); + // cal.get() turns 12 to 0 for 12-hour time; change 0 to 12 + // so 0 unambiguously means a 24-hour time from above. + if (hourOfDay == 0) { hourOfDay = 12; } + } + assert(0 <= hourOfDay && hourOfDay <= 23); + + + // If hour-of-day is 0 or 13 thru 23 then input time in unambiguously in 24-hour format. + if (hourOfDay == 0 || (13 <= hourOfDay && hourOfDay <= 23)) { + // Make hour-of-day take precedence over (hour + am/pm) by setting it again. + cal.set(UCAL_HOUR_OF_DAY, hourOfDay); + } else { + // We have a 12-hour time and need to choose between am and pm. + // Behave as if dayPeriod spanned 6 hours each way from its center point. + // This will parse correctly for consistent time + period (e.g. 10 at night) as + // well as provide a reasonable recovery for inconsistent time + period (e.g. + // 9 in the afternoon). + + // Assume current time is in the AM. + // - Change 12 back to 0 for easier handling of 12am. + // - Append minutes as fractional hours because e.g. 8:15 and 8:45 could be parsed + // into different half-days if center of dayPeriod is at 14:30. + // - cal.get(MINUTE) will return 0 if MINUTE is unset, which works. + if (hourOfDay == 12) { hourOfDay = 0; } + double currentHour = hourOfDay + (cal.get(UCAL_MINUTE, status)) / 60.0; + double midPointHour = ruleSet->getMidPointForDayPeriod(dayPeriod, status); + + if (U_SUCCESS(status)) { + double hoursAheadMidPoint = currentHour - midPointHour; + + // Assume current time is in the AM. + if (-6 <= hoursAheadMidPoint && hoursAheadMidPoint < 6) { + // Assumption holds; set time as such. + cal.set(UCAL_AM_PM, 0); + } else { + cal.set(UCAL_AM_PM, 1); + } + } + } + } + } + // At this point the fields of Calendar have been set. Calendar // will fill in default values for missing fields when the time // is computed. @@ -2346,6 +2549,29 @@ int32_t SimpleDateFormat::matchQuarterString(const UnicodeString& text, return -start; } +int32_t SimpleDateFormat::matchDayPeriodStrings(const UnicodeString& text, int32_t start, + const UnicodeString* data, int32_t dataCount, + int32_t &dayPeriod) const +{ + + int32_t bestMatchLength = 0, bestMatch = -1; + + for (int32_t i = 0; i < dataCount; ++i) { + int32_t matchLength = 0; + if ((matchLength = matchStringWithOptionalDot(text, start, data[i])) > bestMatchLength) { + bestMatchLength = matchLength; + bestMatch = i; + } + } + + if (bestMatch >= 0) { + dayPeriod = bestMatch; + return start + bestMatchLength; + } + + return -start; +} + //---------------------------------------------------------------------- UBool SimpleDateFormat::matchLiterals(const UnicodeString &pattern, int32_t &patternOffset, @@ -2356,17 +2582,17 @@ UBool SimpleDateFormat::matchLiterals(const UnicodeString &pattern, UBool oldLeniency) { UBool inQuote = FALSE; - UnicodeString literal; + UnicodeString literal; int32_t i = patternOffset; // scan pattern looking for contiguous literal characters for ( ; i < pattern.length(); i += 1) { UChar ch = pattern.charAt(i); - + if (!inQuote && isSyntaxChar(ch)) { break; } - + if (ch == QUOTE) { // Match a quote literal ('') inside OR outside of quotes if ((i + 1) < pattern.length() && pattern.charAt(i + 1) == QUOTE) { @@ -2376,47 +2602,47 @@ UBool SimpleDateFormat::matchLiterals(const UnicodeString &pattern, continue; } } - + literal += ch; } - + // at this point, literal contains the literal text // and i is the index of the next non-literal pattern character. int32_t p; int32_t t = textOffset; - + if (whitespaceLenient) { // trim leading, trailing whitespace from // the literal text literal.trim(); - + // ignore any leading whitespace in the text while (t < text.length() && u_isWhitespace(text.charAt(t))) { t += 1; } } - + for (p = 0; p < literal.length() && t < text.length();) { UBool needWhitespace = FALSE; - + while (p < literal.length() && PatternProps::isWhiteSpace(literal.charAt(p))) { needWhitespace = TRUE; p += 1; } - + if (needWhitespace) { int32_t tStart = t; - + while (t < text.length()) { UChar tch = text.charAt(t); - + if (!u_isUWhiteSpace(tch) && !PatternProps::isWhiteSpace(tch)) { break; } - + t += 1; } - + // TODO: should we require internal spaces // in lenient mode? (There won't be any // leading or trailing spaces) @@ -2425,7 +2651,7 @@ UBool SimpleDateFormat::matchLiterals(const UnicodeString &pattern, // an error in strict mode return FALSE; } - + // In strict mode, this run of whitespace // may have been at the end. if (p >= literal.length()) { @@ -2443,26 +2669,26 @@ UBool SimpleDateFormat::matchLiterals(const UnicodeString &pattern, ++t; continue; // Do not update p. } - // if it is actual whitespace and we're whitespace lenient it's OK - + // if it is actual whitespace and we're whitespace lenient it's OK + UChar wsc = text.charAt(t); if(PatternProps::isWhiteSpace(wsc)) { // Lenient mode and it's just whitespace we skip it ++t; continue; // Do not update p. } - } + } // hack around oldleniency being a bit of a catch-all bucket and we're just adding support specifically for paritial matches - if(partialMatchLenient && oldLeniency) { + if(partialMatchLenient && oldLeniency) { break; } - + return FALSE; } ++p; ++t; } - + // At this point if we're in strict mode we have a complete match. // If we're in lenient mode we may have a partial match, or no // match at all. @@ -2474,20 +2700,20 @@ UBool SimpleDateFormat::matchLiterals(const UnicodeString &pattern, if (patternCharIndex != UDAT_FIELD_COUNT) { ignorables = SimpleDateFormatStaticSets::getIgnorables(patternCharIndex); } - + for (t = textOffset; t < text.length(); t += 1) { UChar ch = text.charAt(t); - + if (ignorables == NULL || !ignorables->contains(ch)) { break; } } } - + // if we get here, we've got a complete match. patternOffset = i - 1; textOffset = t; - + return TRUE; } @@ -2595,7 +2821,8 @@ SimpleDateFormat::set2DigitYearStart(UDate d, UErrorCode& status) */ int32_t SimpleDateFormat::subParse(const UnicodeString& text, int32_t& start, UChar ch, int32_t count, UBool obeyCount, UBool allowNegative, UBool ambiguousYear[], int32_t& saveHebrewMonth, Calendar& cal, - int32_t patLoc, MessageFormat * numericLeapMonthFormatter, UTimeZoneFormatTimeType *tzTimeType, SimpleDateFormatMutableNFs &mutableNFs) const + int32_t patLoc, MessageFormat * numericLeapMonthFormatter, UTimeZoneFormatTimeType *tzTimeType, SimpleDateFormatMutableNFs &mutableNFs, + int32_t *dayPeriod) const { Formattable number; int32_t value = 0; @@ -2702,7 +2929,7 @@ int32_t SimpleDateFormat::subParse(const UnicodeString& text, int32_t& start, UC if (txtLoc > parseStart) { value = number.getLong(); gotNumber = TRUE; - + // suffix processing if (value < 0 ) { txtLoc = checkIntSuffix(text, txtLoc, patLoc+1, TRUE); @@ -2725,7 +2952,7 @@ int32_t SimpleDateFormat::subParse(const UnicodeString& text, int32_t& start, UC pos.setIndex(txtLoc); } } - + // Make sure that we got a number if // we want one, and didn't get one // if we don't want one. @@ -2738,9 +2965,9 @@ int32_t SimpleDateFormat::subParse(const UnicodeString& text, int32_t& start, UC if (value < 0 || value > 24) { return -start; } - + // fall through to gotNumber check - + case UDAT_YEAR_FIELD: case UDAT_YEAR_WOY_FIELD: case UDAT_FRACTIONAL_SECOND_FIELD: @@ -2748,9 +2975,9 @@ int32_t SimpleDateFormat::subParse(const UnicodeString& text, int32_t& start, UC if (! gotNumber) { return -start; } - + break; - + default: // we check the rest of the fields below. break; @@ -2929,9 +3156,9 @@ int32_t SimpleDateFormat::subParse(const UnicodeString& text, int32_t& start, UC // [We computed 'value' above.] if (value == cal.getMaximum(UCAL_HOUR_OF_DAY) + 1) value = 0; - + // fall through to set field - + case UDAT_HOUR_OF_DAY0_FIELD: cal.set(UCAL_HOUR_OF_DAY, value); return pos.getIndex(); @@ -3054,9 +3281,9 @@ int32_t SimpleDateFormat::subParse(const UnicodeString& text, int32_t& start, UC // [We computed 'value' above.] if (value == cal.getLeastMaximum(UCAL_HOUR)+1) value = 0; - + // fall through to set field - + case UDAT_HOUR0_FIELD: cal.set(UCAL_HOUR, value); return pos.getIndex(); @@ -3246,7 +3473,7 @@ int32_t SimpleDateFormat::subParse(const UnicodeString& text, int32_t& start, UC } // currently no pattern character is defined for UDAT_TIME_SEPARATOR_FIELD // so we should not get here. Leave support in for future definition. - case UDAT_TIME_SEPARATOR_FIELD: // + case UDAT_TIME_SEPARATOR_FIELD: { static const UChar def_sep = DateFormatSymbols::DEFAULT_TIME_SEPARATOR; static const UChar alt_sep = DateFormatSymbols::ALTERNATE_TIME_SEPARATOR; @@ -3269,6 +3496,70 @@ int32_t SimpleDateFormat::subParse(const UnicodeString& text, int32_t& start, UC return matchString(text, start, UCAL_FIELD_COUNT /* => nothing to set */, data, count, NULL, cal); } + case UDAT_AM_PM_MIDNIGHT_NOON_FIELD: + { + U_ASSERT(dayPeriod != NULL); + int32_t ampmStart = subParse(text, start, 0x61, count, + obeyCount, allowNegative, ambiguousYear, saveHebrewMonth, cal, + patLoc, numericLeapMonthFormatter, tzTimeType, mutableNFs); + + if (ampmStart > 0) { + return ampmStart; + } else { + int32_t newStart = 0; + + // Only match the first two strings from the day period strings array. + if (getBooleanAttribute(UDAT_PARSE_MULTIPLE_PATTERNS_FOR_MATCH, status) || count == 3) { + if ((newStart = matchDayPeriodStrings(text, start, fSymbols->fAbbreviatedDayPeriods, + 2, *dayPeriod)) > 0) { + return newStart; + } + } + if (getBooleanAttribute(UDAT_PARSE_MULTIPLE_PATTERNS_FOR_MATCH, status) || count == 5) { + if ((newStart = matchDayPeriodStrings(text, start, fSymbols->fNarrowDayPeriods, + 2, *dayPeriod)) > 0) { + return newStart; + } + } + // count == 4, but allow other counts + if (getBooleanAttribute(UDAT_PARSE_MULTIPLE_PATTERNS_FOR_MATCH, status)) { + if ((newStart = matchDayPeriodStrings(text, start, fSymbols->fWideDayPeriods, + 2, *dayPeriod)) > 0) { + return newStart; + } + } + + return -start; + } + } + + case UDAT_FLEXIBLE_DAY_PERIOD_FIELD: + { + U_ASSERT(dayPeriod != NULL); + int32_t newStart = 0; + + if (getBooleanAttribute(UDAT_PARSE_MULTIPLE_PATTERNS_FOR_MATCH, status) || count == 3) { + if ((newStart = matchDayPeriodStrings(text, start, fSymbols->fAbbreviatedDayPeriods, + fSymbols->fAbbreviatedDayPeriodsCount, *dayPeriod)) > 0) { + return newStart; + } + } + if (getBooleanAttribute(UDAT_PARSE_MULTIPLE_PATTERNS_FOR_MATCH, status) || count == 5) { + if ((newStart = matchDayPeriodStrings(text, start, fSymbols->fNarrowDayPeriods, + fSymbols->fNarrowDayPeriodsCount, *dayPeriod)) > 0) { + return newStart; + } + } + if (getBooleanAttribute(UDAT_PARSE_MULTIPLE_PATTERNS_FOR_MATCH, status) || count == 4) { + if ((newStart = matchDayPeriodStrings(text, start, fSymbols->fWideDayPeriods, + fSymbols->fWideDayPeriodsCount, *dayPeriod)) > 0) { + return newStart; + } + } + + return -start; + } + default: // Handle "generic" fields // this is now handled below, outside the switch block @@ -3408,7 +3699,7 @@ void SimpleDateFormat::translatePattern(const UnicodeString& originalPattern, UErrorCode& status) { // run through the pattern and convert any pattern symbols from the version - // in "from" to the corresponding character ion "to". This code takes + // in "from" to the corresponding character in "to". This code takes // quoted strings into account (it doesn't try to translate them), and it signals // an error if a particular "pattern character" doesn't appear in "from". // Depending on the values of "from" and "to" this can convert from generic @@ -3472,6 +3763,7 @@ void SimpleDateFormat::applyPattern(const UnicodeString& pattern) { fPattern = pattern; + parsePattern(); } //---------------------------------------------------------------------- @@ -3799,6 +4091,28 @@ SimpleDateFormat::tzFormat() const { return fTimeZoneFormat; } +void SimpleDateFormat::parsePattern() { + fHasMinute = FALSE; + fHasSecond = FALSE; + + int len = fPattern.length(); + UBool inQuote = FALSE; + for (int32_t i = 0; i < len; ++i) { + UChar ch = fPattern[i]; + if (ch == QUOTE) { + inQuote = !inQuote; + } + if (!inQuote) { + if (ch == 0x6D) { // 0x6D == 'm' + fHasMinute = TRUE; + } + if (ch == 0x73) { // 0x73 == 's' + fHasSecond = TRUE; + } + } + } +} + U_NAMESPACE_END #endif /* #if !UCONFIG_NO_FORMATTING */ diff --git a/icu4c/source/i18n/ucln_in.h b/icu4c/source/i18n/ucln_in.h index 6fd94305ae9..2e520a2c6ce 100644 --- a/icu4c/source/i18n/ucln_in.h +++ b/icu4c/source/i18n/ucln_in.h @@ -3,7 +3,7 @@ * Copyright (C) 2001-2016, International Business Machines * Corporation and others. All Rights Reserved. ****************************************************************************** -* file name: ucln_cmn.h +* file name: ucln_in.h * encoding: US-ASCII * tab size: 8 (not used) * indentation:4 @@ -42,6 +42,7 @@ typedef enum ECleanupI18NType { UCLN_I18N_TIMEZONE, UCLN_I18N_DECFMT, UCLN_I18N_NUMFMT, + UCLN_I18N_DAYPERIODRULES, UCLN_I18N_SMPDTFMT, UCLN_I18N_USEARCH, UCLN_I18N_COLLATOR, diff --git a/icu4c/source/i18n/unicode/dtfmtsym.h b/icu4c/source/i18n/unicode/dtfmtsym.h index 6632030b72c..539ce4db0a1 100644 --- a/icu4c/source/i18n/unicode/dtfmtsym.h +++ b/icu4c/source/i18n/unicode/dtfmtsym.h @@ -1,4 +1,4 @@ -/* +/* ******************************************************************************** * Copyright (C) 1997-2016, International Business Machines * Corporation and others. All Rights Reserved. @@ -14,10 +14,10 @@ * Changed to match C++ conventions ******************************************************************************** */ - + #ifndef DTFMTSYM_H #define DTFMTSYM_H - + #include "unicode/utypes.h" #if !UCONFIG_NO_FORMATTING @@ -29,7 +29,7 @@ #include "unicode/ures.h" /** - * \file + * \file * \brief C++ API: Symbols for formatting dates. */ @@ -115,7 +115,7 @@ public: * data for the default locale, it will return a last-resort object * based on hard-coded strings. * - * @param type Type of calendar (as returned by Calendar::getType). + * @param type Type of calendar (as returned by Calendar::getType). * Will be used to access the correct set of strings. * (NULL or empty string defaults to "gregorian".) * @param status Status code. Failure @@ -130,7 +130,7 @@ public: * resources for the given locale, in the default calendar (Gregorian). * * @param locale Locale to load format data from. - * @param type Type of calendar (as returned by Calendar::getType). + * @param type Type of calendar (as returned by Calendar::getType). * Will be used to access the correct set of strings. * (NULL or empty string defaults to "gregorian".) * @param status Status code. Failure @@ -850,6 +850,42 @@ private: */ UBool fCapitalization[kCapContextUsageTypeCount][2]; + /** + * Abbreviated (== short) day period strings. + */ + UnicodeString *fAbbreviatedDayPeriods; + int32_t fAbbreviatedDayPeriodsCount; + + /** + * Wide day period strings. + */ + UnicodeString *fWideDayPeriods; + int32_t fWideDayPeriodsCount; + + /** + * Narrow day period strings. + */ + UnicodeString *fNarrowDayPeriods; + int32_t fNarrowDayPeriodsCount; + + /** + * Stand-alone abbreviated (== short) day period strings. + */ + UnicodeString *fStandaloneAbbreviatedDayPeriods; + int32_t fStandaloneAbbreviatedDayPeriodsCount; + + /** + * Stand-alone wide day period strings. + */ + UnicodeString *fStandaloneWideDayPeriods; + int32_t fStandaloneWideDayPeriodsCount; + + /** + * Stand-alone narrow day period strings. + */ + UnicodeString *fStandaloneNarrowDayPeriods; + int32_t fStandaloneNarrowDayPeriodsCount; + private: /** valid/actual locale information * these are always ICU locales, so the length should not be a problem diff --git a/icu4c/source/i18n/unicode/smpdtfmt.h b/icu4c/source/i18n/unicode/smpdtfmt.h index e6e96ae30d2..b7fa42054dc 100644 --- a/icu4c/source/i18n/unicode/smpdtfmt.h +++ b/icu4c/source/i18n/unicode/smpdtfmt.h @@ -1,5 +1,5 @@ /* -* Copyright (C) 1997-2015, International Business Machines Corporation and +* Copyright (C) 1997-2016, International Business Machines Corporation and * others. All Rights Reserved. ******************************************************************************* * @@ -89,7 +89,7 @@ class SimpleDateFormatMutableNFs; * G * 1..3 * AD - * Era - Replaced with the Era string for the current date. One to three letters for the + * Era - Replaced with the Era string for the current date. One to three letters for the * abbreviated form, four letters for the long (wide) form, five for the narrow form. * * @@ -218,7 +218,7 @@ class SimpleDateFormatMutableNFs; * q * 1..2 * 02 - * Stand-Alone Quarter - Use one or two for the numerical quarter, three for the abbreviation, + * Stand-Alone Quarter - Use one or two for the numerical quarter, three for the abbreviation, * or four for the full name (five for the narrow name is not yet supported). * * @@ -254,7 +254,7 @@ class SimpleDateFormatMutableNFs; * L * 1..2 * 09 - * Stand-Alone Month - Use one or two for the numerical month, three for the abbreviation, + * Stand-Alone Month - Use one or two for the numerical month, three for the abbreviation, * four for the full (wide) name, or 5 for the narrow name. With two ("LL"), the month number is zero-padded if * necessary (e.g. "08") * @@ -310,7 +310,7 @@ class SimpleDateFormatMutableNFs; * 2451334 * Modified Julian day. This is different from the conventional Julian day number in two regards. * First, it demarcates days at local zone midnight, rather than noon GMT. Second, it is a local number; - * that is, it depends on the local time zone. It can be thought of as a single number that encompasses + * that is, it depends on the local time zone. It can be thought of as a single number that encompasses * all the date-related fields. * * @@ -319,7 +319,7 @@ class SimpleDateFormatMutableNFs; * E * 1..3 * Tue - * Day of week - Use one through three letters for the short day, four for the full (wide) name, + * Day of week - Use one through three letters for the short day, four for the full (wide) name, * five for the narrow name, or six for the short name. * * @@ -539,7 +539,7 @@ class SimpleDateFormatMutableNFs; * The generic location format. * Where that is unavailable, falls back to the long localized GMT format ("OOOO"; * Note: Fallback is only necessary with a GMT-style Time Zone ID, like Etc/GMT-830.)
- * This is especially useful when presenting possible timezone choices for user selection, + * This is especially useful when presenting possible timezone choices for user selection, * since the naming is more uniform than the "v" format. * * @@ -1126,15 +1126,15 @@ public: * @param value The UDisplayContext value to set. * @param status Input/output status. If at entry this indicates a failure * status, the function will do nothing; otherwise this will be - * updated with any new status from the function. + * updated with any new status from the function. * @stable ICU 53 */ virtual void setContext(UDisplayContext value, UErrorCode& status); - + /** * Overrides base class method and - * This method clears per field NumberFormat instances - * previously set by {@see adoptNumberFormat(const UnicodeString&, NumberFormat*, UErrorCode)} + * This method clears per field NumberFormat instances + * previously set by {@see adoptNumberFormat(const UnicodeString&, NumberFormat*, UErrorCode)} * @param adoptNF the NumbeferFormat used * @stable ICU 54 */ @@ -1144,7 +1144,7 @@ public: * Allow the user to set the NumberFormat for several fields * It can be a single field like: "y"(year) or "M"(month) * It can be several field combined together: "yM"(year and month) - * Note: + * Note: * 1 symbol field is enough for multiple symbol field (so "y" will override "yy", "yyy") * If the field is not numeric, then override has no effect (like "MMM" will use abbreviation, not numerical field) * Per field NumberFormat can also be cleared in {@see DateFormat::setNumberFormat(const NumberFormat& newNumberFormat)} @@ -1269,7 +1269,7 @@ private: * having a number of digits between "minDigits" and * "maxDigits". Uses the DateFormat's NumberFormat. * - * @param currentNumberFormat + * @param currentNumberFormat * @param appendTo Output parameter to receive result. * Formatted number is appended to existing contents. * @param value Value to format. @@ -1357,7 +1357,14 @@ private: */ int32_t matchQuarterString(const UnicodeString& text, int32_t start, UCalendarDateFields field, const UnicodeString* stringArray, int32_t stringArrayCount, Calendar& cal) const; - + + /** + * Used by subParse() to match localized day period strings. + */ + int32_t matchDayPeriodStrings(const UnicodeString& text, int32_t start, + const UnicodeString* stringArray, int32_t stringArrayCount, + int32_t &dayPeriod) const; + /** * Private function used by subParse to match literal pattern text. * @@ -1374,9 +1381,9 @@ private: * @return TRUE if the literal text could be matched, FALSE otherwise. */ static UBool matchLiterals(const UnicodeString &pattern, int32_t &patternOffset, - const UnicodeString &text, int32_t &textOffset, + const UnicodeString &text, int32_t &textOffset, UBool whitespaceLenient, UBool partialMatchLenient, UBool oldLeniency); - + /** * Private member function that converts the parsed date strings into * timeFields. Returns -start (for ParsePosition) if failed. @@ -1399,7 +1406,8 @@ private: */ int32_t subParse(const UnicodeString& text, int32_t& start, UChar ch, int32_t count, UBool obeyCount, UBool allowNegative, UBool ambiguousYear[], int32_t& saveHebrewMonth, Calendar& cal, - int32_t patLoc, MessageFormat * numericLeapMonthFormatter, UTimeZoneFormatTimeType *tzTimeType, SimpleDateFormatMutableNFs &mutableNFs) const; + int32_t patLoc, MessageFormat * numericLeapMonthFormatter, UTimeZoneFormatTimeType *tzTimeType, SimpleDateFormatMutableNFs &mutableNFs, + int32_t *dayPeriod=NULL) const; void parseInt(const UnicodeString& text, Formattable& number, @@ -1556,6 +1564,14 @@ private: */ UDate fDefaultCenturyStart; + UBool fHasMinute; + UBool fHasSecond; + + /** + * Sets fHasMinutes and fHasSeconds. + */ + void parsePattern(); + /** * See documentation for defaultCenturyStart. */ diff --git a/icu4c/source/i18n/unicode/udat.h b/icu4c/source/i18n/unicode/udat.h index 632497460c4..806c90e3348 100644 --- a/icu4c/source/i18n/unicode/udat.h +++ b/icu4c/source/i18n/unicode/udat.h @@ -759,7 +759,21 @@ typedef enum UDateFormatField { * @internal ICU 53 */ UDAT_RELATED_YEAR_FIELD = 34, -#endif /* U_HIDE_INTERNAL_API */ +#endif /* U_HIDE_INTERNAL_API */ + + /** + * FieldPosition selector for 'b' field alignment. + * Displays midnight and noon for 12am and 12pm, respectively, if available; + * otherwise fall back to AM / PM. + * @draft ICU 57 + */ + UDAT_AM_PM_MIDNIGHT_NOON_FIELD = 35, + + /* FieldPosition selector for 'B' field alignment. + * Displays flexible day periods, such as "in the morning", if available. + * @draft ICU 57 + */ + UDAT_FLEXIBLE_DAY_PERIOD_FIELD = 36, /** * FieldPosition and UFieldPosition selector for time separator, @@ -767,7 +781,8 @@ typedef enum UDateFormatField { * defined for this. * @stable ICU 55 */ - UDAT_TIME_SEPARATOR_FIELD = 35, + UDAT_TIME_SEPARATOR_FIELD = 37, + /** * Number of FieldPosition and UFieldPosition selectors for @@ -777,7 +792,7 @@ typedef enum UDateFormatField { * in the future. * @stable ICU 3.0 */ - UDAT_FIELD_COUNT = 36 + UDAT_FIELD_COUNT = 38 } UDateFormatField; diff --git a/icu4c/source/test/cintltst/cdattst.c b/icu4c/source/test/cintltst/cdattst.c index 7e0f11230e2..fe07070a229 100644 --- a/icu4c/source/test/cintltst/cdattst.c +++ b/icu4c/source/test/cintltst/cdattst.c @@ -1,5 +1,5 @@ /******************************************************************** - * COPYRIGHT: + * COPYRIGHT: * Copyright (c) 1997-2016, International Business Machines Corporation and * others. All Rights Reserved. ********************************************************************/ @@ -8,7 +8,7 @@ * File CDATTST.C * * Modification History: -* Name Description +* Name Description * Madhu Katragadda Creation ********************************************************************************* */ @@ -90,24 +90,24 @@ static void TestDateFormat() fr = udat_open(UDAT_FULL, UDAT_DEFAULT, "fr_FR", NULL,0, NULL, 0,&status); if(U_FAILURE(status)) { - log_data_err("FAIL: error in creating the dateformat using full time style with french locale -> %s (Are you missing data?)\n", + log_data_err("FAIL: error in creating the dateformat using full time style with french locale -> %s (Are you missing data?)\n", myErrorName(status) ); return; } - /* this is supposed to open default date format, but later on it treats it like it is "en_US" + /* this is supposed to open default date format, but later on it treats it like it is "en_US" - very bad if you try to run the tests on machine where default locale is NOT "en_US" */ /* def = udat_open(UDAT_SHORT, UDAT_SHORT, NULL, NULL, 0, &status); */ def = udat_open(UDAT_SHORT, UDAT_SHORT, "en_US", NULL, 0,NULL, 0, &status); if(U_FAILURE(status)) { - log_err("FAIL: error in creating the dateformat using short date and time style\n %s\n", + log_err("FAIL: error in creating the dateformat using short date and time style\n %s\n", myErrorName(status) ); return; } it = udat_open(UDAT_DEFAULT, UDAT_MEDIUM, "it_IT", NULL, 0, NULL, 0,&status); if(U_FAILURE(status)) { - log_err("FAIL: error in creating the dateformat using medium date style with italian locale\n %s\n", + log_err("FAIL: error in creating the dateformat using medium date style with italian locale\n %s\n", myErrorName(status) ); return; } @@ -122,20 +122,20 @@ static void TestDateFormat() def1 = udat_open(UDAT_SHORT, UDAT_SHORT, NULL, NULL, 0,NULL, 0, &status); if(U_FAILURE(status)) { - log_err("FAIL: error in creating the dateformat using short date and time style\n %s\n", + log_err("FAIL: error in creating the dateformat using short date and time style\n %s\n", myErrorName(status) ); return; } - /*Testing udat_getAvailable() and udat_countAvailable()*/ + /*Testing udat_getAvailable() and udat_countAvailable()*/ log_verbose("\nTesting getAvailableLocales and countAvailable()\n"); numlocales=udat_countAvailable(); /* use something sensible w/o hardcoding the count */ if(numlocales < 0) log_data_err("FAIL: error in countAvailable\n"); log_verbose("The number of locales for which date/time formatting patterns are available is %d\n", numlocales); - + for(i=0;i %s (Are you missing data?)\n", + log_data_err("error in creating the dateformat using full time style with french locale -> %s (Are you missing data?)\n", myErrorName(status) ); return; } /*creating a default dateformat */ log_verbose("\ncreating a date format with default locale\n"); - /* this is supposed to open default date format, but later on it treats it like it is "en_US" + /* this is supposed to open default date format, but later on it treats it like it is "en_US" - very bad if you try to run the tests on machine where default locale is NOT "en_US" */ /* def = udat_open(UDAT_DEFAULT,UDAT_DEFAULT ,NULL, NULL, 0, &status); */ def = udat_open(UDAT_DEFAULT,UDAT_DEFAULT ,"en_US", NULL, 0, NULL, 0, &status); if(U_FAILURE(status)) { - log_err("error in creating the dateformat using short date and time style\n %s\n", + log_err("error in creating the dateformat using short date and time style\n %s\n", myErrorName(status) ); return; } @@ -603,16 +603,16 @@ static void TestSymbols() zhChiCal = udat_open(UDAT_NONE, UDAT_FULL, "zh@calendar=chinese", NULL, 0, NULL, 0, &status); if(U_FAILURE(status)) { - log_data_err("error in creating the dateformat using full date, no time, locale zh@calendar=chinese -> %s (Are you missing data?)\n", + log_data_err("error in creating the dateformat using full date, no time, locale zh@calendar=chinese -> %s (Are you missing data?)\n", myErrorName(status) ); return; } - - + + /*Testing countSymbols, getSymbols and setSymbols*/ log_verbose("\nTesting countSymbols\n"); /*since the month names has the last string empty and week names are 1 based 1.e first string in the weeknames array is empty */ - if(udat_countSymbols(def, UDAT_ERAS)!=2 || udat_countSymbols(def, UDAT_MONTHS)!=12 || + if(udat_countSymbols(def, UDAT_ERAS)!=2 || udat_countSymbols(def, UDAT_MONTHS)!=12 || udat_countSymbols(def, UDAT_SHORT_MONTHS)!=12 || udat_countSymbols(def, UDAT_WEEKDAYS)!=8 || udat_countSymbols(def, UDAT_SHORT_WEEKDAYS)!=8 || udat_countSymbols(def, UDAT_AM_PMS)!=2 || udat_countSymbols(def, UDAT_QUARTERS) != 4 || udat_countSymbols(def, UDAT_SHORT_QUARTERS) != 4 || @@ -640,7 +640,7 @@ static void TestSymbols() } result=(UChar*)malloc(sizeof(UChar) * resultlength); udat_getSymbols(fr, UDAT_WEEKDAYS, 5, result, resultlength, &status); - + } if(U_FAILURE(status)) { @@ -677,9 +677,9 @@ static void TestSymbols() VerifygetSymbols(zhChiCal, UDAT_ZODIAC_NAMES_ABBREVIATED, 0, "\\u9F20"); VerifygetSymbols(zhChiCal, UDAT_ZODIAC_NAMES_WIDE, 11, "\\u732A"); #if UDAT_HAS_PATTERN_CHAR_FOR_TIME_SEPARATOR - VerifygetSymbols(def,UDAT_LOCALIZED_CHARS, 0, "GyMdkHmsSEDFwWahKzYeugAZvcLQqVUOXxr:"); + VerifygetSymbols(def,UDAT_LOCALIZED_CHARS, 0, "GyMdkHmsSEDFwWahKzYeugAZvcLQqVUOXxrbB:"); #else - VerifygetSymbols(def,UDAT_LOCALIZED_CHARS, 0, "GyMdkHmsSEDFwWahKzYeugAZvcLQqVUOXxr"); + VerifygetSymbols(def,UDAT_LOCALIZED_CHARS, 0, "GyMdkHmsSEDFwWahKzYeugAZvcLQqVUOXxrbB"); #endif @@ -687,8 +687,8 @@ static void TestSymbols() free(result); result = NULL; } -free(pattern); - +free(pattern); + log_verbose("\nTesting setSymbols\n"); /*applying the pattern so that setSymbolss works */ resultlength=0; @@ -702,10 +702,10 @@ free(pattern); } if(U_FAILURE(status)) { - log_err("FAIL: error in extracting the pattern from UNumberFormat\n %s\n", + log_err("FAIL: error in extracting the pattern from UNumberFormat\n %s\n", myErrorName(status) ); } - + udat_applyPattern(def, FALSE, pattern, u_strlen(pattern)); resultlength=0; resultlengthout=udat_toPattern(def, FALSE, NULL, resultlength,&status); @@ -722,7 +722,7 @@ free(pattern); } if(U_FAILURE(status)) { - log_err("FAIL: error in extracting the pattern from UNumberFormat\n %s\n", + log_err("FAIL: error in extracting the pattern from UNumberFormat\n %s\n", myErrorName(status) ); } if(u_strcmp(result, pattern)==0) @@ -743,12 +743,12 @@ free(pattern); } result=(UChar*)malloc(sizeof(UChar) * resultlength); udat_getSymbols(fr, UDAT_MONTHS, 11, result, resultlength, &status); - + } if(U_FAILURE(status)) log_err("FAIL: error in getSymbols() %s\n", myErrorName(status) ); resultlength=resultlengthout+1; - + udat_setSymbols(def, UDAT_MONTHS, 11, result, resultlength, &status); if(U_FAILURE(status)) { @@ -756,7 +756,7 @@ free(pattern); } else log_verbose("PASS: SetSymbols successful\n"); - + resultlength=0; resultlengthout=udat_getSymbols(def, UDAT_MONTHS, 11, NULL, resultlength, &status); if(status==U_BUFFER_OVERFLOW_ERROR){ @@ -767,13 +767,13 @@ free(pattern); } if(U_FAILURE(status)) log_err("FAIL: error in retrieving the value using getSymbols i.e roundtrip\n"); - + if(u_strcmp(result, value)!=0) log_data_err("FAIL: Error in settting and getting symbols\n"); else log_verbose("PASS: setSymbols successful\n"); - - + + /*run series of tests to test setSymbols regressively*/ log_verbose("\nTesting setSymbols regressively\n"); VerifysetSymbols(def, UDAT_ERAS, 0, "BeforeChrist"); @@ -812,7 +812,7 @@ free(pattern); /*closing*/ - + udat_close(fr); udat_close(def); udat_close(zhChiCal); @@ -821,7 +821,7 @@ free(pattern); result = NULL; } free(value); - + } /** @@ -845,7 +845,7 @@ static void TestDateFormatCalendar() { /* Create a formatter for date fields. */ date = udat_open(UDAT_NONE, UDAT_SHORT, "en_US", NULL, 0, NULL, 0, &ec); if (U_FAILURE(ec)) { - log_data_err("FAIL: udat_open(NONE, SHORT, en_US) failed with %s (Are you missing data?)\n", + log_data_err("FAIL: udat_open(NONE, SHORT, en_US) failed with %s (Are you missing data?)\n", u_errorName(ec)); goto FAIL; } @@ -853,7 +853,7 @@ static void TestDateFormatCalendar() { /* Create a formatter for time fields. */ time = udat_open(UDAT_SHORT, UDAT_NONE, "en_US", NULL, 0, NULL, 0, &ec); if (U_FAILURE(ec)) { - log_err("FAIL: udat_open(SHORT, NONE, en_US) failed with %s\n", + log_err("FAIL: udat_open(SHORT, NONE, en_US) failed with %s\n", u_errorName(ec)); goto FAIL; } @@ -861,7 +861,7 @@ static void TestDateFormatCalendar() { /* Create a full format for output */ full = udat_open(UDAT_FULL, UDAT_FULL, "en_US", NULL, 0, NULL, 0, &ec); if (U_FAILURE(ec)) { - log_err("FAIL: udat_open(FULL, FULL, en_US) failed with %s\n", + log_err("FAIL: udat_open(FULL, FULL, en_US) failed with %s\n", u_errorName(ec)); goto FAIL; } @@ -869,7 +869,7 @@ static void TestDateFormatCalendar() { /* Create a calendar */ cal = ucal_open(NULL, 0, "en_US", UCAL_GREGORIAN, &ec); if (U_FAILURE(ec)) { - log_err("FAIL: ucal_open(en_US) failed with %s\n", + log_err("FAIL: ucal_open(en_US) failed with %s\n", u_errorName(ec)); goto FAIL; } @@ -940,7 +940,7 @@ static void TestDateFormatCalendar() { log_err("FAIL: Parsed result: %s, exp 4/5/2001 5:45 PM\n", cbuf); } - FAIL: + FAIL: udat_close(date); udat_close(time); udat_close(full); @@ -1057,7 +1057,7 @@ static void VerifygetSymbols(UDateFormat* datfor, UDateFormatSymbolType type, in UChar *result=NULL; int32_t resultlength, resultlengthout; int32_t patternSize = strlen(expected) + 1; - + pattern=(UChar*)malloc(sizeof(UChar) * patternSize); u_unescape(expected, pattern, patternSize); resultlength=0; @@ -1068,7 +1068,7 @@ static void VerifygetSymbols(UDateFormat* datfor, UDateFormatSymbolType type, in resultlength=resultlengthout+1; result=(UChar*)malloc(sizeof(UChar) * resultlength); udat_getSymbols(datfor, type, idx, result, resultlength, &status); - + } if(U_FAILURE(status)) { @@ -1078,7 +1078,7 @@ static void VerifygetSymbols(UDateFormat* datfor, UDateFormatSymbolType type, in if(u_strcmp(result, pattern)==0) log_verbose("PASS: getSymbols retrieved the right value\n"); else{ - log_data_err("FAIL: getSymbols retrieved the wrong value\n Expected %s Got %s\n", expected, + log_data_err("FAIL: getSymbols retrieved the wrong value\n Expected %s Got %s\n", expected, aescstrdup(result,-1) ); } free(result); @@ -1111,11 +1111,11 @@ static void VerifysetSymbols(UDateFormat* datfor, UDateFormatSymbolType type, in udat_getSymbols(datfor, type, idx, result, resultlength, &status); } if(U_FAILURE(status)){ - log_err("FAIL: error in retrieving the value using getSymbols after setting it previously\n %s\n", + log_err("FAIL: error in retrieving the value using getSymbols after setting it previously\n %s\n", myErrorName(status) ); return; } - + if(u_strcmp(result, value)!=0){ log_err("FAIL:Error in setting and then getting symbols\n Expected %s Got %s\n", expected, aescstrdup(result,-1) ); @@ -1134,7 +1134,7 @@ static void VerifygetsetSymbols(UDateFormat* from, UDateFormat* to, UDateFormatS UChar *value=NULL; int32_t resultlength, resultlengthout; UErrorCode status = U_ZERO_ERROR; - + resultlength=0; resultlengthout=udat_getSymbols(from, type, idx , NULL, resultlength, &status); if(status==U_BUFFER_OVERFLOW_ERROR){ @@ -1147,7 +1147,7 @@ static void VerifygetsetSymbols(UDateFormat* from, UDateFormat* to, UDateFormatS log_err("FAIL: error in getSymbols() %s\n", myErrorName(status) ); return; } - + resultlength=resultlengthout+1; udat_setSymbols(to, type, idx, result, resultlength, &status); if(U_FAILURE(status)) @@ -1165,11 +1165,11 @@ static void VerifygetsetSymbols(UDateFormat* from, UDateFormat* to, UDateFormatS udat_getSymbols(to, type, idx, value, resultlength, &status); } if(U_FAILURE(status)){ - log_err("FAIL: error in retrieving the value using getSymbols i.e roundtrip\n %s\n", + log_err("FAIL: error in retrieving the value using getSymbols i.e roundtrip\n %s\n", myErrorName(status) ); return; } - + if(u_strcmp(result, value)!=0){ log_data_err("FAIL:Error in setting and then getting symbols\n Expected %s Got %s\n", austrdup(result), austrdup(value) ); @@ -1187,7 +1187,7 @@ static UChar* myNumformat(const UNumberFormat* numfor, double d) UChar *result2=NULL; int32_t resultlength, resultlengthneeded; UErrorCode status = U_ZERO_ERROR; - + resultlength=0; resultlengthneeded=unum_formatDouble(numfor, d, NULL, resultlength, NULL, &status); if(status==U_BUFFER_OVERFLOW_ERROR) @@ -1203,7 +1203,7 @@ static UChar* myNumformat(const UNumberFormat* numfor, double d) log_err("FAIL: Error in formatting using unum_format(.....) %s\n", myErrorName(status) ); return 0; } - + return result2; } @@ -1277,7 +1277,7 @@ static void TestExtremeDates() { /* There is no need to test larger values from 1e+30 to 1e+300; the failures occur around 1e+27, and never above 1e+30. */ - + ec = U_ZERO_ERROR; fmt = udat_open(UDAT_LONG, UDAT_LONG, "en_US", 0, 0, 0, 0, &ec); @@ -1327,12 +1327,12 @@ static void TestRelativeCrash(void) { UErrorCode subStatus = U_ZERO_ERROR; what = "udat_set2DigitYearStart"; log_verbose("Trying %s on a relative date..\n", what); - udat_set2DigitYearStart(icudf, aDate, &subStatus); + udat_set2DigitYearStart(icudf, aDate, &subStatus); if(subStatus == expectStatus) { log_verbose("Success: did not crash on %s, but got %s.\n", what, u_errorName(subStatus)); } else { log_err("FAIL: didn't crash on %s, but got success %s instead of %s. \n", what, u_errorName(subStatus), u_errorName(expectStatus)); - } + } } { /* clone works polymorphically. try it anyways */ @@ -1346,42 +1346,42 @@ static void TestRelativeCrash(void) { udat_close(oth); /* ? */ } else { log_err("FAIL: didn't crash on %s, but got %s instead of %s. \n", what, u_errorName(subStatus), u_errorName(expectStatus)); - } + } } { UErrorCode subStatus = U_ZERO_ERROR; what = "udat_get2DigitYearStart"; log_verbose("Trying %s on a relative date..\n", what); - udat_get2DigitYearStart(icudf, &subStatus); + udat_get2DigitYearStart(icudf, &subStatus); if(subStatus == expectStatus) { log_verbose("Success: did not crash on %s, but got %s.\n", what, u_errorName(subStatus)); } else { log_err("FAIL: didn't crash on %s, but got success %s instead of %s. \n", what, u_errorName(subStatus), u_errorName(expectStatus)); - } + } } { /* Now udat_toPattern works for relative date formatters, unless localized is TRUE */ UErrorCode subStatus = U_ZERO_ERROR; what = "udat_toPattern"; log_verbose("Trying %s on a relative date..\n", what); - udat_toPattern(icudf, TRUE,NULL,0, &subStatus); + udat_toPattern(icudf, TRUE,NULL,0, &subStatus); if(subStatus == expectStatus) { log_verbose("Success: did not crash on %s, but got %s.\n", what, u_errorName(subStatus)); } else { log_err("FAIL: didn't crash on %s, but got success %s instead of %s. \n", what, u_errorName(subStatus), u_errorName(expectStatus)); - } + } } { UErrorCode subStatus = U_ZERO_ERROR; what = "udat_applyPattern"; log_verbose("Trying %s on a relative date..\n", what); - udat_applyPattern(icudf, FALSE,tzName,-1); + udat_applyPattern(icudf, FALSE,tzName,-1); subStatus = U_ILLEGAL_ARGUMENT_ERROR; /* what it should be, if this took an errorcode. */ if(subStatus == expectStatus) { log_verbose("Success: did not crash on %s, but got %s.\n", what, u_errorName(subStatus)); } else { log_err("FAIL: didn't crash on %s, but got success %s instead of %s. \n", what, u_errorName(subStatus), u_errorName(expectStatus)); - } + } } { UChar erabuf[32]; @@ -1393,7 +1393,7 @@ static void TestRelativeCrash(void) { log_verbose("Success: %s returned %s.\n", what, u_errorName(subStatus)); } else { log_err("FAIL: didn't crash on %s, but got %s instead of U_ZERO_ERROR.\n", what, u_errorName(subStatus)); - } + } } { UErrorCode subStatus = U_ZERO_ERROR; @@ -1405,21 +1405,21 @@ static void TestRelativeCrash(void) { log_verbose("Success: did not crash on %s, but got %s.\n", what, u_errorName(subStatus)); } else { log_err("FAIL: didn't crash on %s, but got success %s instead of %s. \n", what, u_errorName(subStatus), u_errorName(expectStatus)); - } + } } { UErrorCode subStatus = U_ZERO_ERROR; what = "udat_countSymbols"; log_verbose("Trying %s on a relative date..\n", what); - udat_countSymbols(icudf, UDAT_ERAS); + udat_countSymbols(icudf, UDAT_ERAS); subStatus = U_ILLEGAL_ARGUMENT_ERROR; /* should have an errorcode. */ if(subStatus == expectStatus) { log_verbose("Success: did not crash on %s, but got %s.\n", what, u_errorName(subStatus)); } else { log_err("FAIL: didn't crash on %s, but got success %s instead of %s. \n", what, u_errorName(subStatus), u_errorName(expectStatus)); - } + } } - + udat_close(icudf); } else { log_data_err("FAIL: err calling udat_open() ->%s (Are you missing data?)\n", u_errorName(status)); @@ -1599,9 +1599,9 @@ static void TestContext(void) { } -// overrideNumberFormat[i][0] is to tell which field to set, +// overrideNumberFormat[i][0] is to tell which field to set, // overrideNumberFormat[i][1] is the expected result -static const char * overrideNumberFormat[][2] = { +static const char * overrideNumberFormat[][2] = { {"", "\\u521D\\u4E03 \\u521D\\u4E8C"}, {"d", "07 \\u521D\\u4E8C"}, {"do", "07 \\u521D\\u4E8C"}, @@ -1652,7 +1652,7 @@ static void TestOverrideNumberFormat(void) { unum_close(overrideFmt); } udat_close(fmt); - + for (i=0; iparse(in, status); - failure(status, "fmt->parse", TRUE); - - // format date back to string - UnicodeString out; - out = fmt1->format(dt1, out); - logln(out); - - // check that roundtrip worked as expected - UnicodeString expected = data[i+1]; - if (out != expected) { - dataerrln((UnicodeString)"FAIL: " + in + " -> " + out + " expected -> " + expected); - } - } - - delete fmt1; -} -void DateFormatTest::TestFormalChineseDate() { - - UErrorCode status = U_ZERO_ERROR; + for(int i=0; i < numData; i+=2) { + // create input string + UnicodeString in = data[i]; + + // parse string to date + UDate dt1 = fmt1->parse(in, status); + failure(status, "fmt->parse", TRUE); + + // format date back to string + UnicodeString out; + out = fmt1->format(dt1, out); + logln(out); + + // check that roundtrip worked as expected + UnicodeString expected = data[i+1]; + if (out != expected) { + dataerrln((UnicodeString)"FAIL: " + in + " -> " + out + " expected -> " + expected); + } + } + + delete fmt1; +} +void DateFormatTest::TestFormalChineseDate() { + + UErrorCode status = U_ZERO_ERROR; UnicodeString pattern ("y\\u5e74M\\u6708d\\u65e5", -1, US_INV ); pattern = pattern.unescape(); UnicodeString override ("y=hanidec;M=hans;d=hans", -1, US_INV ); - - // create formatter + + // create formatter SimpleDateFormat *sdf = new SimpleDateFormat(pattern,override,Locale::getChina(),status); if (failure(status, "new SimpleDateFormat with override", TRUE)) { return; @@ -3931,13 +3941,13 @@ void DateFormatTest::TestFormalChineseDate() { FieldPosition pos(0); UnicodeString result; sdf->format(thedate,result,pos); - - UnicodeString expected = "\\u4e8c\\u3007\\u3007\\u4e5d\\u5e74\\u4e03\\u6708\\u4e8c\\u5341\\u516b\\u65e5"; + + UnicodeString expected = "\\u4e8c\\u3007\\u3007\\u4e5d\\u5e74\\u4e03\\u6708\\u4e8c\\u5341\\u516b\\u65e5"; expected = expected.unescape(); - if (result != expected) { - dataerrln((UnicodeString)"FAIL: -> " + result + " expected -> " + expected); - } - + if (result != expected) { + dataerrln((UnicodeString)"FAIL: -> " + result + " expected -> " + expected); + } + UDate parsedate = sdf->parse(expected,status); if ( parsedate != thedate ) { UnicodeString pat1 ("yyyy-MM-dd'T'HH:mm:ss'Z'", -1, US_INV ); @@ -3945,7 +3955,7 @@ void DateFormatTest::TestFormalChineseDate() { UnicodeString parsedres,expres; usf->format(parsedate,parsedres,pos); usf->format(thedate,expres,pos); - dataerrln((UnicodeString)"FAIL: parsed -> " + parsedres + " expected -> " + expres); + dataerrln((UnicodeString)"FAIL: parsed -> " + parsedres + " expected -> " + expres); delete usf; } delete sdf; @@ -3956,7 +3966,7 @@ void DateFormatTest::TestFormalChineseDate() { void DateFormatTest::TestStandAloneGMTParse() { UErrorCode status = U_ZERO_ERROR; SimpleDateFormat *sdf = new SimpleDateFormat("ZZZZ", Locale(""), status); - + if (U_SUCCESS(status)) { UnicodeString inText("GMT$$$"); @@ -4093,7 +4103,7 @@ void DateFormatTest::TestMonthPatterns() // terminator { NULL, 0, { UnicodeString(""), UnicodeString(""), UnicodeString("") } } }; - + //. style: -1 -2 -3 -4 const UnicodeString customPatterns[] = { "y-Ml-d", "G'x'y-Ml-d", "U-M-d", "U MMM d" }; // like old root pattern, using 'l' @@ -4423,7 +4433,7 @@ typedef struct { void DateFormatTest::TestDateFormatLeniency() { // For details see http://bugs.icu-project.org/trac/ticket/10261 - + const UDate july022008 = 1215000001979.0; const TestDateFormatLeniencyItem items[] = { //locale leniency parse String pattern expected result @@ -4434,7 +4444,7 @@ void DateFormatTest::TestDateFormatLeniency() { { "en", true, UnicodeString("2008-Jan--02"), UnicodeString("yyyy-MMM' -- 'dd"), UnicodeString("2008-Jan -- 02") }, { "en", false, UnicodeString("2008-Jan--02"), UnicodeString("yyyy-MMM' -- 'dd"), UnicodeString("") }, // terminator - { NULL, true, UnicodeString(""), UnicodeString(""), UnicodeString("") } + { NULL, true, UnicodeString(""), UnicodeString(""), UnicodeString("") } }; UErrorCode status = U_ZERO_ERROR; LocalPointer cal(Calendar::createInstance(status)); @@ -4446,7 +4456,7 @@ void DateFormatTest::TestDateFormatLeniency() { const TestDateFormatLeniencyItem * itemPtr; LocalPointer sdmft; for (itemPtr = items; itemPtr->locale != NULL; itemPtr++ ) { - + Locale locale = Locale::createFromName(itemPtr->locale); status = U_ZERO_ERROR; ParsePosition pos(0); @@ -4458,36 +4468,36 @@ void DateFormatTest::TestDateFormatLeniency() { sdmft->setBooleanAttribute(UDAT_PARSE_ALLOW_WHITESPACE, itemPtr->leniency, status). setBooleanAttribute(UDAT_PARSE_ALLOW_NUMERIC, itemPtr->leniency, status). setBooleanAttribute(UDAT_PARSE_PARTIAL_LITERAL_MATCH, itemPtr->leniency, status); - UDate d = sdmft->parse(itemPtr->parseString, pos); + UDate d = sdmft->parse(itemPtr->parseString, pos); if(itemPtr->expectedResult.length() == 0) { if(pos.getErrorIndex() != -1) { continue; } else { - errln("error: unexpected parse success - " + itemPtr->parseString + - " - pattern " + itemPtr->pattern + - " - error index " + pos.getErrorIndex() + + errln("error: unexpected parse success - " + itemPtr->parseString + + " - pattern " + itemPtr->pattern + + " - error index " + pos.getErrorIndex() + " - leniency " + itemPtr->leniency); continue; } } - if(pos.getErrorIndex() != -1) { - errln("error: parse error for string - " + itemPtr->parseString + - " - pattern " + itemPtr->pattern + - " - idx " + pos.getIndex() + - " - error index "+pos.getErrorIndex() + - " - leniency " + itemPtr->leniency); - continue; + if(pos.getErrorIndex() != -1) { + errln("error: parse error for string - " + itemPtr->parseString + + " - pattern " + itemPtr->pattern + + " - idx " + pos.getIndex() + + " - error index "+pos.getErrorIndex() + + " - leniency " + itemPtr->leniency); + continue; } - UnicodeString formatResult(""); + UnicodeString formatResult(""); sdmft->format(d, formatResult); - if(formatResult.compare(itemPtr->expectedResult) != 0) { - errln("error: unexpected format result. pattern["+itemPtr->pattern+"] expected[" + itemPtr->expectedResult + "] but result was[" + formatResult + "]"); + if(formatResult.compare(itemPtr->expectedResult) != 0) { + errln("error: unexpected format result. pattern["+itemPtr->pattern+"] expected[" + itemPtr->expectedResult + "] but result was[" + formatResult + "]"); continue; - } else { - logln("formatted results match! - " + formatResult); - } + } else { + logln("formatted results match! - " + formatResult); + } } } @@ -4501,36 +4511,36 @@ typedef struct { } TestMultiPatternMatchItem; void DateFormatTest::TestParseMultiPatternMatch() { - // For details see http://bugs.icu-project.org/trac/ticket/10336 + // For details see http://bugs.icu-project.org/trac/ticket/10336 const TestMultiPatternMatchItem items[] = { - // leniency parse String pattern expected result - {true, UnicodeString("2013-Sep 13"), UnicodeString("yyyy-MMM dd"), UnicodeString("2013-Sep 13")}, - {true, UnicodeString("2013-September 14"), UnicodeString("yyyy-MMM dd"), UnicodeString("2013-Sep 14")}, - {false, UnicodeString("2013-September 15"), UnicodeString("yyyy-MMM dd"), UnicodeString("")}, - {false, UnicodeString("2013-September 16"), UnicodeString("yyyy-MMMM dd"), UnicodeString("2013-September 16")}, - {true, UnicodeString("2013-Sep 17"), UnicodeString("yyyy-LLL dd"), UnicodeString("2013-Sep 17")}, - {true, UnicodeString("2013-September 18"), UnicodeString("yyyy-LLL dd"), UnicodeString("2013-Sep 18")}, - {false, UnicodeString("2013-September 19"), UnicodeString("yyyy-LLL dd"), UnicodeString("")}, - {false, UnicodeString("2013-September 20"), UnicodeString("yyyy-LLLL dd"), UnicodeString("2013-September 20")}, - {true, UnicodeString("2013 Sat Sep 21"), UnicodeString("yyyy EEE MMM dd"), UnicodeString("2013 Sat Sep 21")}, - {true, UnicodeString("2013 Sunday Sep 22"), UnicodeString("yyyy EEE MMM dd"), UnicodeString("2013 Sun Sep 22")}, - {false, UnicodeString("2013 Monday Sep 23"), UnicodeString("yyyy EEE MMM dd"), UnicodeString("")}, - {false, UnicodeString("2013 Tuesday Sep 24"), UnicodeString("yyyy EEEE MMM dd"), UnicodeString("2013 Tuesday Sep 24")}, - {true, UnicodeString("2013 Wed Sep 25"), UnicodeString("yyyy eee MMM dd"), UnicodeString("2013 Wed Sep 25")}, - {true, UnicodeString("2013 Thu Sep 26"), UnicodeString("yyyy eee MMM dd"), UnicodeString("2013 Thu Sep 26")}, - {false, UnicodeString("2013 Friday Sep 27"), UnicodeString("yyyy eee MMM dd"), UnicodeString("")}, - {false, UnicodeString("2013 Saturday Sep 28"), UnicodeString("yyyy eeee MMM dd"), UnicodeString("2013 Saturday Sep 28")}, - {true, UnicodeString("2013 Sun Sep 29"), UnicodeString("yyyy ccc MMM dd"), UnicodeString("2013 Sun Sep 29")}, - {true, UnicodeString("2013 Monday Sep 30"), UnicodeString("yyyy ccc MMM dd"), UnicodeString("2013 Mon Sep 30")}, - {false, UnicodeString("2013 Sunday Oct 13"), UnicodeString("yyyy ccc MMM dd"), UnicodeString("")}, - {false, UnicodeString("2013 Monday Oct 14"), UnicodeString("yyyy cccc MMM dd"), UnicodeString("2013 Monday Oct 14")}, - {true, UnicodeString("2013 Oct 15 Q4"), UnicodeString("yyyy MMM dd QQQ"), UnicodeString("2013 Oct 15 Q4")}, - {true, UnicodeString("2013 Oct 16 4th quarter"), UnicodeString("yyyy MMM dd QQQ"), UnicodeString("2013 Oct 16 Q4")}, - {false, UnicodeString("2013 Oct 17 4th quarter"), UnicodeString("yyyy MMM dd QQQ"), UnicodeString("")}, - {false, UnicodeString("2013 Oct 18 Q4"), UnicodeString("yyyy MMM dd QQQ"), UnicodeString("2013 Oct 18 Q4")}, - {true, UnicodeString("2013 Oct 19 Q4"), UnicodeString("yyyy MMM dd qqqq"), UnicodeString("2013 Oct 19 4th quarter")}, - {true, UnicodeString("2013 Oct 20 4th quarter"), UnicodeString("yyyy MMM dd qqqq"), UnicodeString("2013 Oct 20 4th quarter")}, - {false, UnicodeString("2013 Oct 21 Q4"), UnicodeString("yyyy MMM dd qqqq"), UnicodeString("")}, + // leniency parse String pattern expected result + {true, UnicodeString("2013-Sep 13"), UnicodeString("yyyy-MMM dd"), UnicodeString("2013-Sep 13")}, + {true, UnicodeString("2013-September 14"), UnicodeString("yyyy-MMM dd"), UnicodeString("2013-Sep 14")}, + {false, UnicodeString("2013-September 15"), UnicodeString("yyyy-MMM dd"), UnicodeString("")}, + {false, UnicodeString("2013-September 16"), UnicodeString("yyyy-MMMM dd"), UnicodeString("2013-September 16")}, + {true, UnicodeString("2013-Sep 17"), UnicodeString("yyyy-LLL dd"), UnicodeString("2013-Sep 17")}, + {true, UnicodeString("2013-September 18"), UnicodeString("yyyy-LLL dd"), UnicodeString("2013-Sep 18")}, + {false, UnicodeString("2013-September 19"), UnicodeString("yyyy-LLL dd"), UnicodeString("")}, + {false, UnicodeString("2013-September 20"), UnicodeString("yyyy-LLLL dd"), UnicodeString("2013-September 20")}, + {true, UnicodeString("2013 Sat Sep 21"), UnicodeString("yyyy EEE MMM dd"), UnicodeString("2013 Sat Sep 21")}, + {true, UnicodeString("2013 Sunday Sep 22"), UnicodeString("yyyy EEE MMM dd"), UnicodeString("2013 Sun Sep 22")}, + {false, UnicodeString("2013 Monday Sep 23"), UnicodeString("yyyy EEE MMM dd"), UnicodeString("")}, + {false, UnicodeString("2013 Tuesday Sep 24"), UnicodeString("yyyy EEEE MMM dd"), UnicodeString("2013 Tuesday Sep 24")}, + {true, UnicodeString("2013 Wed Sep 25"), UnicodeString("yyyy eee MMM dd"), UnicodeString("2013 Wed Sep 25")}, + {true, UnicodeString("2013 Thu Sep 26"), UnicodeString("yyyy eee MMM dd"), UnicodeString("2013 Thu Sep 26")}, + {false, UnicodeString("2013 Friday Sep 27"), UnicodeString("yyyy eee MMM dd"), UnicodeString("")}, + {false, UnicodeString("2013 Saturday Sep 28"), UnicodeString("yyyy eeee MMM dd"), UnicodeString("2013 Saturday Sep 28")}, + {true, UnicodeString("2013 Sun Sep 29"), UnicodeString("yyyy ccc MMM dd"), UnicodeString("2013 Sun Sep 29")}, + {true, UnicodeString("2013 Monday Sep 30"), UnicodeString("yyyy ccc MMM dd"), UnicodeString("2013 Mon Sep 30")}, + {false, UnicodeString("2013 Sunday Oct 13"), UnicodeString("yyyy ccc MMM dd"), UnicodeString("")}, + {false, UnicodeString("2013 Monday Oct 14"), UnicodeString("yyyy cccc MMM dd"), UnicodeString("2013 Monday Oct 14")}, + {true, UnicodeString("2013 Oct 15 Q4"), UnicodeString("yyyy MMM dd QQQ"), UnicodeString("2013 Oct 15 Q4")}, + {true, UnicodeString("2013 Oct 16 4th quarter"), UnicodeString("yyyy MMM dd QQQ"), UnicodeString("2013 Oct 16 Q4")}, + {false, UnicodeString("2013 Oct 17 4th quarter"), UnicodeString("yyyy MMM dd QQQ"), UnicodeString("")}, + {false, UnicodeString("2013 Oct 18 Q4"), UnicodeString("yyyy MMM dd QQQ"), UnicodeString("2013 Oct 18 Q4")}, + {true, UnicodeString("2013 Oct 19 Q4"), UnicodeString("yyyy MMM dd qqqq"), UnicodeString("2013 Oct 19 4th quarter")}, + {true, UnicodeString("2013 Oct 20 4th quarter"), UnicodeString("yyyy MMM dd qqqq"), UnicodeString("2013 Oct 20 4th quarter")}, + {false, UnicodeString("2013 Oct 21 Q4"), UnicodeString("yyyy MMM dd qqqq"), UnicodeString("")}, {false, UnicodeString("2013 Oct 22 4th quarter"), UnicodeString("yyyy MMM dd qqqq"), UnicodeString("2013 Oct 22 4th quarter")}, {false, UnicodeString("--end--"), UnicodeString(""), UnicodeString("")}, }; @@ -4556,30 +4566,30 @@ void DateFormatTest::TestParseMultiPatternMatch() { continue; } sdmft->setBooleanAttribute(UDAT_PARSE_MULTIPLE_PATTERNS_FOR_MATCH, itemPtr->leniency, status); - UDate d = sdmft->parse(itemPtr->parseString, pos); - + UDate d = sdmft->parse(itemPtr->parseString, pos); + if(itemPtr->expectedResult.length() == 0) { if(pos.getErrorIndex() != -1) { continue; } else { - errln("error: unexpected parse success - " + itemPtr->parseString + - " - error index " + pos.getErrorIndex() + + errln("error: unexpected parse success - " + itemPtr->parseString + + " - error index " + pos.getErrorIndex() + " - leniency " + itemPtr->leniency); continue; } } - if(pos.getErrorIndex() != -1) { - errln("error: parse error for string - " +itemPtr->parseString + " -- idx["+pos.getIndex()+"] errIdx["+pos.getErrorIndex()+"]"); - continue; + if(pos.getErrorIndex() != -1) { + errln("error: parse error for string - " +itemPtr->parseString + " -- idx["+pos.getIndex()+"] errIdx["+pos.getErrorIndex()+"]"); + continue; } - UnicodeString formatResult(""); + UnicodeString formatResult(""); sdmft->format(d, formatResult); - if(formatResult.compare(itemPtr->expectedResult) != 0) { - errln("error: unexpected format result. expected[" + itemPtr->expectedResult + "] but result was[" + formatResult + "]"); - } else { - logln("formatted results match! - " + formatResult); - } + if(formatResult.compare(itemPtr->expectedResult) != 0) { + errln("error: unexpected format result. expected[" + itemPtr->expectedResult + "] but result was[" + formatResult + "]"); + } else { + logln("formatted results match! - " + formatResult); + } } delete sdmft; } @@ -4668,12 +4678,12 @@ void DateFormatTest::TestNumberFormatOverride() { { "MdMMd", "\\u521D\\u516D \\u5341\\u4E94"}, { "mixed", "\\u521D\\u516D \\u5341\\u4E94"} }; - + UDate test_date = date(97, 6 - 1, 15); for(int i=0; i < UPRV_LENGTHOF(DATA); i++){ fields = DATA[i][0]; - + LocalPointer fmt; fmt.adoptInsteadAndCheckErrorCode(new SimpleDateFormat((UnicodeString)"MM d", status), status); assertSuccess("SimpleDateFormat with pattern MM d", status); @@ -4689,7 +4699,7 @@ void DateFormatTest::TestNumberFormatOverride() { fields = (UnicodeString) "M"; fmt->adoptNumberFormat(fields, singleOverrideNF, status); assertSuccess("adoptNumberFormat singleOverrideNF", status); - + fmt->adoptNumberFormat(overrideNF); } else if (fields == (UnicodeString) "Mo"){ // o is invlid field fmt->adoptNumberFormat(fields, overrideNF, status); @@ -4708,7 +4718,7 @@ void DateFormatTest::TestNumberFormatOverride() { UnicodeString expected = ((UnicodeString)DATA[i][1]).unescape();; - if (result != expected) + if (result != expected) errln("FAIL: Expected " + expected + " get: " + result); } } @@ -4862,6 +4872,507 @@ void DateFormatTest::TestPatternFromSkeleton() { } } +void DateFormatTest::TestAmPmMidnightNoon() { + // Some times on 2015-11-13 (UTC+0). + UDate k000000 = 1447372800000.0; + UDate k000030 = 1447372830000.0; + UDate k003000 = 1447374600000.0; + UDate k060000 = 1447394400000.0; + UDate k120000 = 1447416000000.0; + UDate k180000 = 1447437600000.0; + + UErrorCode errorCode = U_ZERO_ERROR; + SimpleDateFormat sdf(UnicodeString(), errorCode); + const TimeZone *tz = TimeZone::getGMT(); + sdf.setTimeZone(*tz); + UnicodeString out; + + // Short. + sdf.applyPattern(UnicodeString("hh:mm:ss bbb")); + + assertEquals("hh:mm:ss bbb | 00:00:00", "12:00:00 midnight", sdf.format(k000000, out.remove())); + assertEquals("hh:mm:ss bbb | 00:00:30", "12:00:30 AM", sdf.format(k000030, out.remove())); + assertEquals("hh:mm:ss bbb | 00:30:00", "12:30:00 AM", sdf.format(k003000, out.remove())); + assertEquals("hh:mm:ss bbb | 06:00:00", "06:00:00 AM", sdf.format(k060000, out.remove())); + assertEquals("hh:mm:ss bbb | 12:00:00", "12:00:00 noon", sdf.format(k120000, out.remove())); + assertEquals("hh:mm:ss bbb | 18:00:00", "06:00:00 PM", sdf.format(k180000, out.remove())); + + sdf.applyPattern(UnicodeString("hh:mm bbb")); + + assertEquals("hh:mm bbb | 00:00:00", "12:00 midnight", sdf.format(k000000, out.remove())); + assertEquals("hh:mm bbb | 00:00:30", "12:00 midnight", sdf.format(k000030, out.remove())); + assertEquals("hh:mm bbb | 00:30:00", "12:30 AM", sdf.format(k003000, out.remove())); + + sdf.applyPattern(UnicodeString("hh bbb")); + + assertEquals("hh bbb | 00:00:00", "12 midnight", sdf.format(k000000, out.remove())); + assertEquals("hh bbb | 00:00:30", "12 midnight", sdf.format(k000030, out.remove())); + assertEquals("hh bbb | 00:30:00", "12 midnight", sdf.format(k003000, out.remove())); + + // Wide. + sdf.applyPattern(UnicodeString("hh:mm:ss bbbb")); + + assertEquals("hh:mm:ss bbbb | 00:00:00", "12:00:00 midnight", sdf.format(k000000, out.remove())); + assertEquals("hh:mm:ss bbbb | 00:00:30", "12:00:30 AM", sdf.format(k000030, out.remove())); + assertEquals("hh:mm:ss bbbb | 00:30:00", "12:30:00 AM", sdf.format(k003000, out.remove())); + assertEquals("hh:mm:ss bbbb | 06:00:00", "06:00:00 AM", sdf.format(k060000, out.remove())); + assertEquals("hh:mm:ss bbbb | 12:00:00", "12:00:00 noon", sdf.format(k120000, out.remove())); + assertEquals("hh:mm:ss bbbb | 18:00:00", "06:00:00 PM", sdf.format(k180000, out.remove())); + + sdf.applyPattern(UnicodeString("hh:mm bbbb")); + + assertEquals("hh:mm bbbb | 00:00:00", "12:00 midnight", sdf.format(k000000, out.remove())); + assertEquals("hh:mm bbbb | 00:00:30", "12:00 midnight", sdf.format(k000030, out.remove())); + assertEquals("hh:mm bbbb | 00:30:00", "12:30 AM", sdf.format(k003000, out.remove())); + + sdf.applyPattern(UnicodeString("hh bbbb")); + + assertEquals("hh bbbb | 00:00:00", "12 midnight", sdf.format(k000000, out.remove())); + assertEquals("hh bbbb | 00:00:30", "12 midnight", sdf.format(k000030, out.remove())); + assertEquals("hh bbbb | 00:30:00", "12 midnight", sdf.format(k003000, out.remove())); + + // Narrow. + sdf.applyPattern(UnicodeString("hh:mm:ss bbbbb")); + + assertEquals("hh:mm:ss bbbbb | 00:00:00", "12:00:00 mi", sdf.format(k000000, out.remove())); + assertEquals("hh:mm:ss bbbbb | 00:00:30", "12:00:30 a", sdf.format(k000030, out.remove())); + assertEquals("hh:mm:ss bbbbb | 00:30:00", "12:30:00 a", sdf.format(k003000, out.remove())); + assertEquals("hh:mm:ss bbbbb | 06:00:00", "06:00:00 a", sdf.format(k060000, out.remove())); + assertEquals("hh:mm:ss bbbbb | 12:00:00", "12:00:00 n", sdf.format(k120000, out.remove())); + assertEquals("hh:mm:ss bbbbb | 18:00:00", "06:00:00 p", sdf.format(k180000, out.remove())); + + sdf.applyPattern(UnicodeString("hh:mm bbbbb")); + + assertEquals("hh:mm bbbbb | 00:00:00", "12:00 mi", sdf.format(k000000, out.remove())); + assertEquals("hh:mm bbbbb | 00:00:30", "12:00 mi", sdf.format(k000030, out.remove())); + assertEquals("hh:mm bbbbb | 00:30:00", "12:30 a", sdf.format(k003000, out.remove())); + + sdf.applyPattern(UnicodeString("hh bbbbb")); + + assertEquals("hh bbbbb | 00:00:00", "12 mi", sdf.format(k000000, out.remove())); + assertEquals("hh bbbbb | 00:00:30", "12 mi", sdf.format(k000030, out.remove())); + assertEquals("hh bbbbb | 00:30:00", "12 mi", sdf.format(k003000, out.remove())); +} + +void DateFormatTest::TestFlexibleDayPeriod() { + // Some times on 2015-11-13 (UTC+0). + UDate k000000 = 1447372800000.0; + UDate k000030 = 1447372830000.0; + UDate k003000 = 1447374600000.0; + UDate k060000 = 1447394400000.0; + UDate k120000 = 1447416000000.0; + UDate k180000 = 1447437600000.0; + + UErrorCode errorCode = U_ZERO_ERROR; + SimpleDateFormat sdf(UnicodeString(), errorCode); + const TimeZone *tz = TimeZone::getGMT(); + sdf.setTimeZone(*tz); + UnicodeString out; + + // Short. + sdf.applyPattern(UnicodeString("hh:mm:ss BBB")); + + assertEquals("hh:mm:ss BBB | 00:00:00", "12:00:00 midnight", sdf.format(k000000, out.remove())); + assertEquals("hh:mm:ss BBB | 00:00:30", "12:00:30 at night", sdf.format(k000030, out.remove())); + assertEquals("hh:mm:ss BBB | 00:30:00", "12:30:00 at night", sdf.format(k003000, out.remove())); + assertEquals("hh:mm:ss BBB | 06:00:00", "06:00:00 in the morning", sdf.format(k060000, out.remove())); + assertEquals("hh:mm:ss BBB | 12:00:00", "12:00:00 noon", sdf.format(k120000, out.remove())); + assertEquals("hh:mm:ss BBB | 18:00:00", "06:00:00 in the evening", sdf.format(k180000, out.remove())); + + sdf.applyPattern(UnicodeString("hh:mm BBB")); + + assertEquals("hh:mm BBB | 00:00:00", "12:00 midnight", sdf.format(k000000, out.remove())); + assertEquals("hh:mm BBB | 00:00:30", "12:00 midnight", sdf.format(k000030, out.remove())); + assertEquals("hh:mm BBB | 00:30:00", "12:30 at night", sdf.format(k003000, out.remove())); + + sdf.applyPattern(UnicodeString("hh BBB")); + + assertEquals("hh BBB | 00:00:00", "12 midnight", sdf.format(k000000, out.remove())); + assertEquals("hh BBB | 00:00:30", "12 midnight", sdf.format(k000030, out.remove())); + assertEquals("hh BBB | 00:30:00", "12 midnight", sdf.format(k003000, out.remove())); + + // Wide. + sdf.applyPattern(UnicodeString("hh:mm:ss BBBB")); + + assertEquals("hh:mm:ss BBBB | 00:00:00", "12:00:00 midnight", sdf.format(k000000, out.remove())); + assertEquals("hh:mm:ss BBBB | 00:00:30", "12:00:30 at night", sdf.format(k000030, out.remove())); + assertEquals("hh:mm:ss BBBB | 00:30:00", "12:30:00 at night", sdf.format(k003000, out.remove())); + assertEquals("hh:mm:ss BBBB | 06:00:00", "06:00:00 in the morning", sdf.format(k060000, out.remove())); + assertEquals("hh:mm:ss BBBB | 12:00:00", "12:00:00 noon", sdf.format(k120000, out.remove())); + assertEquals("hh:mm:ss BBBB | 18:00:00", "06:00:00 in the evening", sdf.format(k180000, out.remove())); + + sdf.applyPattern(UnicodeString("hh:mm BBBB")); + + assertEquals("hh:mm BBBB | 00:00:00", "12:00 midnight", sdf.format(k000000, out.remove())); + assertEquals("hh:mm BBBB | 00:00:30", "12:00 midnight", sdf.format(k000030, out.remove())); + assertEquals("hh:mm BBBB | 00:30:00", "12:30 at night", sdf.format(k003000, out.remove())); + + sdf.applyPattern(UnicodeString("hh BBBB")); + + assertEquals("hh BBBB | 00:00:00", "12 midnight", sdf.format(k000000, out.remove())); + assertEquals("hh BBBB | 00:00:30", "12 midnight", sdf.format(k000030, out.remove())); + assertEquals("hh BBBB | 00:80:00", "12 midnight", sdf.format(k003000, out.remove())); + + // Narrow. + sdf.applyPattern(UnicodeString("hh:mm:ss BBBBB")); + + assertEquals("hh:mm:ss BBBBB | 00:00:00", "12:00:00 mi", sdf.format(k000000, out.remove())); + assertEquals("hh:mm:ss BBBBB | 00:00:30", "12:00:30 at night", sdf.format(k000030, out.remove())); + assertEquals("hh:mm:ss BBBBB | 00:30:00", "12:30:00 at night", sdf.format(k003000, out.remove())); + assertEquals("hh:mm:ss BBBBB | 06:00:00", "06:00:00 in the morning", sdf.format(k060000, out.remove())); + assertEquals("hh:mm:ss BBBBB | 12:00:00", "12:00:00 n", sdf.format(k120000, out.remove())); + assertEquals("hh:mm:ss BBBBB | 18:00:00", "06:00:00 in the evening", sdf.format(k180000, out.remove())); + + sdf.applyPattern(UnicodeString("hh:mm BBBBB")); + + assertEquals("hh:mm BBBBB | 00:00:00", "12:00 mi", sdf.format(k000000, out.remove())); + assertEquals("hh:mm BBBBB | 00:00:30", "12:00 mi", sdf.format(k000030, out.remove())); + assertEquals("hh:mm BBBBB | 00:30:00", "12:30 at night", sdf.format(k003000, out.remove())); + + sdf.applyPattern(UnicodeString("hh BBBBB")); + + assertEquals("hh BBBBB | 00:00:00", "12 mi", sdf.format(k000000, out.remove())); + assertEquals("hh BBBBB | 00:00:30", "12 mi", sdf.format(k000030, out.remove())); + assertEquals("hh BBBBB | 00:30:00", "12 mi", sdf.format(k003000, out.remove())); +} + +void DateFormatTest::TestDayPeriodWithLocales() { + // Some times on 2015-11-13 (UTC+0). + UDate k000000 = 1447372800000.0; + UDate k010000 = 1447376400000.0; + UDate k120000 = 1447416000000.0; + UDate k220000 = 1447452000000.0; + + UErrorCode errorCode = U_ZERO_ERROR; + const TimeZone *tz = TimeZone::getGMT(); + UnicodeString out; + + // Locale de has a word for midnight, but not noon. + SimpleDateFormat sdf(UnicodeString(), Locale::getGermany(), errorCode); + sdf.setTimeZone(*tz); + + sdf.applyPattern(UnicodeString("hh:mm:ss bbbb")); + + assertEquals("hh:mm:ss bbbb | 00:00:00 | de", "12:00:00 Mitternacht", + sdf.format(k000000, out.remove())); + assertEquals("hh:mm:ss bbbb | 12:00:00 | de", "12:00:00 nachm.", + sdf.format(k120000, out.remove())); + + // Locale ee has a rule that wraps around midnight (21h - 4h). + sdf = SimpleDateFormat(UnicodeString(), Locale("ee"), errorCode); + sdf.setTimeZone(*tz); + + sdf.applyPattern(UnicodeString("hh:mm:ss BBBB")); + + assertEquals("hh:mm:ss BBBB | 22:00:00 | ee", "10:00:00 zã", + sdf.format(k220000, out.remove())); + assertEquals("hh:mm:ss BBBB | 00:00:00 | ee", "12:00:00 zã", + sdf.format(k000000, out.remove())); + assertEquals("hh:mm:ss BBBB | 01:00:00 | ee", "01:00:00 zã", + sdf.format(k010000, out.remove())); + + // Locale root has rules for AM/PM only. + sdf = SimpleDateFormat(UnicodeString(), Locale("root"), errorCode); + sdf.setTimeZone(*tz); + + sdf.applyPattern(UnicodeString("hh:mm:ss BBBB")); + + assertEquals("hh:mm:ss BBBB | 00:00:00 | root", "12:00:00 AM", + sdf.format(k000000, out.remove())); + assertEquals("hh:mm:ss BBBB | 12:00:00 | root", "12:00:00 PM", + sdf.format(k120000, out.remove())); + + // Empty string should behave exactly as root. + sdf = SimpleDateFormat(UnicodeString(), Locale(""), errorCode); + sdf.setTimeZone(*tz); + + sdf.applyPattern(UnicodeString("hh:mm:ss BBBB")); + + assertEquals("hh:mm:ss BBBB | 00:00:00 | \"\" (root)", "12:00:00 AM", + sdf.format(k000000, out.remove())); + assertEquals("hh:mm:ss BBBB | 12:00:00 | \"\" (root)", "12:00:00 PM", + sdf.format(k120000, out.remove())); + + // Locale en_US should fall back to en. + sdf = SimpleDateFormat(UnicodeString(), Locale("en_US"), errorCode); + sdf.setTimeZone(*tz); + + sdf.applyPattern(UnicodeString("hh:mm:ss BBBB")); + + assertEquals("hh:mm:ss BBBB | 00:00:00 | en_US", "12:00:00 midnight", + sdf.format(k000000, out.remove())); + assertEquals("hh:mm:ss BBBB | 01:00:00 | en_US", "01:00:00 at night", + sdf.format(k010000, out.remove())); + assertEquals("hh:mm:ss BBBB | 12:00:00 | en_US", "12:00:00 noon", + sdf.format(k120000, out.remove())); + + // Locale es_CO should not fall back to es and should have a + // different string for 1 in the morning. + // (es_CO: "de la mañana" vs. es: "de la madrugada") + sdf = SimpleDateFormat(UnicodeString(), Locale("es_CO"), errorCode); + sdf.setTimeZone(*tz); + + sdf.applyPattern(UnicodeString("hh:mm:ss BBBB")); + assertEquals("hh:mm:ss BBBB | 01:00:00 | es_CO", "01:00:00 de la mañana", + sdf.format(k010000, out.remove())); + + sdf = SimpleDateFormat(UnicodeString(), Locale("es"), errorCode); + sdf.setTimeZone(*tz); + + sdf.applyPattern(UnicodeString("hh:mm:ss BBBB")); + assertEquals("hh:mm:ss BBBB | 01:00:00 | es", "01:00:00 de la madrugada", + sdf.format(k010000, out.remove())); +} + +void DateFormatTest::TestMinuteSecondFieldsInOddPlaces() { + // Some times on 2015-11-13 (UTC+0). + UDate k000000 = 1447372800000.0; + UDate k000030 = 1447372830000.0; + UDate k003000 = 1447374600000.0; + UDate k060030 = 1447394430000.0; + UDate k063000 = 1447396200000.0; + + UErrorCode errorCode = U_ZERO_ERROR; + const TimeZone *tz = TimeZone::getGMT(); + UnicodeString out; + + // Seconds field is not present. + + // Apply pattern through constructor to make sure parsePattern() is called during initialization. + SimpleDateFormat sdf(UnicodeString("hh:mm 'ss' bbbb"), errorCode); + sdf.setTimeZone(*tz); + + assertEquals("hh:mm 'ss' bbbb | 00:00:30", "12:00 ss midnight", + sdf.format(k000030, out.remove())); + assertEquals("hh:mm 'ss' bbbb | 06:00:30", "06:00 ss AM", + sdf.format(k060030, out.remove())); + + sdf.applyPattern(UnicodeString("hh:mm 'ss' BBBB")); + + assertEquals("hh:mm 'ss' BBBB | 00:00:30", "12:00 ss midnight", + sdf.format(k000030, out.remove())); + assertEquals("hh:mm 'ss' BBBB | 06:00:30", "06:00 ss in the morning", + sdf.format(k060030, out.remove())); + + // Minutes field is not present. + sdf.applyPattern(UnicodeString("hh 'mm ss' bbbb")); + + assertEquals("hh 'mm ss' bbbb | 00:30:00", "12 mm ss midnight", + sdf.format(k003000, out.remove())); + assertEquals("hh 'mm ss' bbbb | 06:30:00", "06 mm ss AM", + sdf.format(k063000, out.remove())); + + sdf.applyPattern(UnicodeString("hh 'mm ss' BBBB")); + + assertEquals("hh 'mm ss' BBBB | 00:30:00", "12 mm ss midnight", + sdf.format(k003000, out.remove())); + assertEquals("hh 'mm ss' BBBB | 06:30:00", "06 mm ss in the morning", + sdf.format(k063000, out.remove())); + + // Minutes and seconds fields appear after day periods. + sdf.applyPattern(UnicodeString("bbbb hh:mm:ss")); + + assertEquals("bbbb hh:mm:ss | 00:00:00", "midnight 12:00:00", + sdf.format(k000000, out.remove())); + assertEquals("bbbb hh:mm:ss | 00:00:30", "AM 12:00:30", + sdf.format(k000030, out.remove())); + assertEquals("bbbb hh:mm:ss | 00:30:00", "AM 12:30:00", + sdf.format(k003000, out.remove())); + + sdf.applyPattern(UnicodeString("BBBB hh:mm:ss")); + + assertEquals("BBBB hh:mm:ss | 00:00:00", "midnight 12:00:00", + sdf.format(k000000, out.remove())); + assertEquals("BBBB hh:mm:ss | 00:00:30", "at night 12:00:30", + sdf.format(k000030, out.remove())); + assertEquals("BBBB hh:mm:ss | 00:30:00", "at night 12:30:00", + sdf.format(k003000, out.remove())); + + // Confirm applyPattern() reparses the pattern string. + sdf.applyPattern(UnicodeString("BBBB hh")); + assertEquals("BBBB hh | 00:00:30", "midnight 12", + sdf.format(k000030, out.remove())); + + sdf.applyPattern(UnicodeString("BBBB hh:mm:'ss'")); + assertEquals("BBBB hh:mm:'ss' | 00:00:30", "midnight 12:00:ss", + sdf.format(k000030, out.remove())); + + sdf.applyPattern(UnicodeString("BBBB hh:mm:ss")); + assertEquals("BBBB hh:mm:ss | 00:00:30", "at night 12:00:30", + sdf.format(k000030, out.remove())); +} + +void DateFormatTest::TestDayPeriodParsing() { + // Some times on 2015-11-13 (UTC+0). + UDate k000000 = 1447372800000.0; + UDate k003700 = 1447375020000.0; + UDate k010000 = 1447376400000.0; + UDate k013000 = 1447378200000.0; + UDate k030000 = 1447383600000.0; + UDate k090000 = 1447405200000.0; + UDate k120000 = 1447416000000.0; + UDate k130000 = 1447419600000.0; + UDate k133700 = 1447421820000.0; + UDate k150000 = 1447426800000.0; + UDate k190000 = 1447441200000.0; + UDate k193000 = 1447443000000.0; + UDate k200000 = 1447444800000.0; + UDate k210000 = 1447448400000.0; + + UErrorCode errorCode = U_ZERO_ERROR; + SimpleDateFormat sdf(UnicodeString(), errorCode); + const TimeZone *tz = TimeZone::getGMT(); + sdf.setTimeZone(*tz); + UnicodeString out; + + // 'B' -- flexible day periods + // A day period on its own parses to the center of that period. + sdf.applyPattern(UnicodeString("yyyy-MM-dd B")); + assertEquals("yyyy-MM-dd B | 2015-11-13 midnight", + k000000, sdf.parse(UnicodeString("2015-11-13 midnight"), errorCode)); + assertEquals("yyyy-MM-dd B | 2015-11-13 noon", + k120000, sdf.parse(UnicodeString("2015-11-13 noon"), errorCode)); + assertEquals("yyyy-MM-dd B | 2015-11-13 in the afternoon", + k150000, sdf.parse(UnicodeString("2015-11-13 in the afternoon"), errorCode)); + assertEquals("yyyy-MM-dd B | 2015-11-13 in the evening", + k193000, sdf.parse(UnicodeString("2015-11-13 in the evening"), errorCode)); + assertEquals("yyyy-MM-dd B | 2015-11-13 at night", + k013000, sdf.parse(UnicodeString("2015-11-13 at night"), errorCode)); + + // If time and day period are consistent with each other then time is parsed accordingly. + sdf.applyPattern(UnicodeString("yyyy-MM-dd hh:mm B")); + assertEquals("yyyy-MM-dd hh:mm B | 2015-11-13 12:00 midnight", + k000000, sdf.parse(UnicodeString("2015-11-13 12:00 midnight"), errorCode)); + assertEquals("yyyy-MM-dd hh:mm B | 2015-11-13 12:00 noon", + k120000, sdf.parse(UnicodeString("2015-11-13 12:00 noon"), errorCode)); + assertEquals("yyyy-MM-dd hh:mm B | 2015-11-13 01:00 at night", + k010000, sdf.parse(UnicodeString("2015-11-13 01:00 at night"), errorCode)); + assertEquals("yyyy-MM-dd hh:mm B | 2015-11-13 01:00 in the afternoon", + k130000, sdf.parse(UnicodeString("2015-11-13 01:00 in the afternoon"), errorCode)); + assertEquals("yyyy-MM-dd hh:mm B | 2015-11-13 09:00 in the morning", + k090000, sdf.parse(UnicodeString("2015-11-13 09:00 in the morning"), errorCode)); + assertEquals("yyyy-MM-dd hh:mm B | 2015-11-13 09:00 at night", + k210000, sdf.parse(UnicodeString("2015-11-13 09:00 at night"), errorCode)); + + // If the hour is 13 thru 23 then day period has no effect on time (since time is assumed + // to be in 24-hour format). + sdf.applyPattern(UnicodeString("yyyy-MM-dd HH:mm B")); + assertEquals("yyyy-MM-dd HH:mm B | 2015-11-13 13:37 midnight", + k133700, sdf.parse(UnicodeString("2015-11-13 13:37 midnight"), errorCode)); + assertEquals("yyyy-MM-dd HH:mm B | 2015-11-13 13:37 noon", + k133700, sdf.parse(UnicodeString("2015-11-13 13:37 noon"), errorCode)); + assertEquals("yyyy-MM-dd HH:mm B | 2015-11-13 13:37 at night", + k133700, sdf.parse(UnicodeString("2015-11-13 13:37 at night"), errorCode)); + assertEquals("yyyy-MM-dd HH:mm B | 2015-11-13 13:37 in the afternoon", + k133700, sdf.parse(UnicodeString("2015-11-13 13:37 in the afternoon"), errorCode)); + assertEquals("yyyy-MM-dd HH:mm B | 2015-11-13 13:37 in the morning", + k133700, sdf.parse(UnicodeString("2015-11-13 13:37 in the morning"), errorCode)); + assertEquals("yyyy-MM-dd HH:mm B | 2015-11-13 13:37 at night", + k133700, sdf.parse(UnicodeString("2015-11-13 13:37 at night"), errorCode)); + + // Hour 0 is synonymous with hour 12 when parsed with 'h'. + // This unfortunately means we have to tolerate "0 noon" as it's synonymous with "12 noon". + sdf.applyPattern(UnicodeString("yyyy-MM-dd hh:mm B")); + assertEquals("yyyy-MM-dd hh:mm B | 2015-11-13 00:00 midnight", + k000000, sdf.parse(UnicodeString("2015-11-13 00:00 midnight"), errorCode)); + assertEquals("yyyy-MM-dd hh:mm B | 2015-11-13 00:00 noon", + k120000, sdf.parse(UnicodeString("2015-11-13 00:00 noon"), errorCode)); + + // But when parsed with 'H', 0 indicates a 24-hour time, therefore we disregard the day period. + sdf.applyPattern(UnicodeString("yyyy-MM-dd HH:mm B")); + assertEquals("yyyy-MM-dd HH:mm B | 2015-11-13 00:37 midnight", + k003700, sdf.parse(UnicodeString("2015-11-13 00:37 midnight"), errorCode)); + assertEquals("yyyy-MM-dd HH:mm B | 2015-11-13 00:37 noon", + k003700, sdf.parse(UnicodeString("2015-11-13 00:37 noon"), errorCode)); + assertEquals("yyyy-MM-dd HH:mm B | 2015-11-13 00:37 at night", + k003700, sdf.parse(UnicodeString("2015-11-13 00:37 at night"), errorCode)); + assertEquals("yyyy-MM-dd HH:mm B | 2015-11-13 00:37 in the afternoon", + k003700, sdf.parse(UnicodeString("2015-11-13 00:37 in the afternoon"), errorCode)); + assertEquals("yyyy-MM-dd HH:mm B | 2015-11-13 00:37 in the morning", + k003700, sdf.parse(UnicodeString("2015-11-13 00:37 in the morning"), errorCode)); + assertEquals("yyyy-MM-dd HH:mm B | 2015-11-13 00:37 at night", + k003700, sdf.parse(UnicodeString("2015-11-13 00:37 at night"), errorCode)); + + // Even when parsed with 'H', hours 1 thru 12 are considered 12-hour time and takes + // day period into account in parsing. + sdf.applyPattern(UnicodeString("yyyy-MM-dd HH:mm B")); + assertEquals("yyyy-MM-dd HH:mm B | 2015-11-13 12:00 midnight", + k000000, sdf.parse(UnicodeString("2015-11-13 12:00 midnight"), errorCode)); + assertEquals("yyyy-MM-dd HH:mm B | 2015-11-13 12:00 noon", + k120000, sdf.parse(UnicodeString("2015-11-13 12:00 noon"), errorCode)); + assertEquals("yyyy-MM-dd HH:mm B | 2015-11-13 01:00 at night", + k010000, sdf.parse(UnicodeString("2015-11-13 01:00 at night"), errorCode)); + assertEquals("yyyy-MM-dd HH:mm B | 2015-11-13 01:00 in the afternoon", + k130000, sdf.parse(UnicodeString("2015-11-13 01:00 in the afternoon"), errorCode)); + assertEquals("yyyy-MM-dd HH:mm B | 2015-11-13 09:00 in the morning", + k090000, sdf.parse(UnicodeString("2015-11-13 09:00 in the morning"), errorCode)); + assertEquals("yyyy-MM-dd HH:mm B | 2015-11-13 09:00 at night", + k210000, sdf.parse(UnicodeString("2015-11-13 09:00 at night"), errorCode)); + + // If a 12-hour time and the day period don't agree with each other, time is parsed as close + // to the given day period as possible. + sdf.applyPattern(UnicodeString("yyyy-MM-dd hh:mm B")); + + // AFTERNOON1 is [12, 18), but "7 in the afternoon" parses to 19:00. + assertEquals("yyyy-MM-dd hh:mm B | 2015-11-13 07:00 in the afternoon", + k190000, sdf.parse(UnicodeString("2015-11-13 07:00 in the afternoon"), errorCode)); + // NIGHT1 is [21, 6), but "8 at night" parses to 20:00. + assertEquals("yyyy-MM-dd hh:mm B | 2015-11-13 08:00 at night", + k200000, sdf.parse(UnicodeString("2015-11-13 08:00 at night"), errorCode)); + + // 'b' -- fixed day periods (AM, PM, midnight, noon) + // On their own, "midnight" parses to 00:00 and "noon" parses to 12:00. + // AM and PM are handled by the 'a' parser (which doesn't handle this case well). + sdf.applyPattern(UnicodeString("yyyy-MM-dd b")); + assertEquals("yyyy-MM-dd b | 2015-11-13 midnight", + k000000, sdf.parse(UnicodeString("2015-11-13 midnight"), errorCode)); + assertEquals("yyyy-MM-dd b | 2015-11-13 noon", + k120000, sdf.parse(UnicodeString("2015-11-13 noon"), errorCode)); + + // For 12-hour times, AM and PM should be parsed as if with pattern character 'a'. + sdf.applyPattern(UnicodeString("yyyy-MM-dd hh:mm b")); + assertEquals("yyyy-MM-dd hh:mm b | 2015-11-13 01:00 AM", + k010000, sdf.parse(UnicodeString("2015-11-13 01:00 AM"), errorCode)); + assertEquals("yyyy-MM-dd hh:mm b | 2015-11-13 01:00 PM", + k130000, sdf.parse(UnicodeString("2015-11-13 01:00 PM"), errorCode)); + + // 12 midnight parses to 00:00, and 12 noon parses to 12:00. + assertEquals("yyyy-MM-dd hh:mm b | 2015-11-13 12:00 midnight", + k000000, sdf.parse(UnicodeString("2015-11-13 12:00 midnight"), errorCode)); + assertEquals("yyyy-MM-dd hh:mm b | 2015-11-13 12:00 noon", + k120000, sdf.parse(UnicodeString("2015-11-13 12:00 noon"), errorCode)); + + // Hours 13-23 indicate 24-hour time so we disregard "midnight" or "noon". + // Again, AM and PM are handled by the 'a' parser which doesn't handle this case well. + sdf.applyPattern(UnicodeString("yyyy-MM-dd HH:mm b")); + assertEquals("yyyy-MM-dd HH:mm b | 2015-11-13 13:37 midnight", + k133700, sdf.parse(UnicodeString("2015-11-13 13:37 midnight"), errorCode)); + assertEquals("yyyy-MM-dd HH:mm b | 2015-11-13 13:37 noon", + k133700, sdf.parse(UnicodeString("2015-11-13 13:37 noon"), errorCode)); + + // Hour 0 is synonymous with hour 12 when parsed with 'h'. + // Again, this means we have to tolerate "0 noon" as it's synonymous with "12 noon". + sdf.applyPattern(UnicodeString("yyyy-MM-dd hh:mm b")); + assertEquals("yyyy-MM-dd hh:mm b | 2015-11-13 00:00 midnight", + k000000, sdf.parse(UnicodeString("2015-11-13 00:00 midnight"), errorCode)); + assertEquals("yyyy-MM-dd hh:mm b | 2015-11-13 00:00 noon", + k120000, sdf.parse(UnicodeString("2015-11-13 00:00 noon"), errorCode)); + + // With 'H' though 0 indicates a 24-hour time, therefore we disregard the day period. + sdf.applyPattern(UnicodeString("yyyy-MM-dd HH:mm b")); + assertEquals("yyyy-MM-dd HH:mm b | 2015-11-13 00:37 midnight", + k003700, sdf.parse(UnicodeString("2015-11-13 00:37 midnight"), errorCode)); + assertEquals("yyyy-MM-dd HH:mm b | 2015-11-13 00:37 noon", + k003700, sdf.parse(UnicodeString("2015-11-13 00:37 noon"), errorCode)); + + // If "midnight" or "noon" is parsed with a 12-hour time other than 12:00, choose + // the version that's closer to the period given. + sdf.applyPattern(UnicodeString("yyyy-MM-dd hh:mm b")); + assertEquals("yyyy-MM-dd hh:mm b | 2015-11-13 03:00 midnight", + k030000, sdf.parse(UnicodeString("2015-11-13 03:00 midnight"), errorCode)); + assertEquals("yyyy-MM-dd hh:mm b | 2015-11-13 03:00 noon", + k150000, sdf.parse(UnicodeString("2015-11-13 03:00 noon"), errorCode)); +} #endif /* #if !UCONFIG_NO_FORMATTING */ diff --git a/icu4c/source/test/intltest/dtfmttst.h b/icu4c/source/test/intltest/dtfmttst.h index f8b97820930..6ce10598497 100644 --- a/icu4c/source/test/intltest/dtfmttst.h +++ b/icu4c/source/test/intltest/dtfmttst.h @@ -1,12 +1,12 @@ /******************************************************************** - * COPYRIGHT: - * Copyright (c) 1997-2015, International Business Machines Corporation and + * COPYRIGHT: + * Copyright (c) 1997-2016, International Business Machines Corporation and * others. All Rights Reserved. ********************************************************************/ #ifndef _DATEFORMATTEST_ #define _DATEFORMATTEST_ - + #include "unicode/utypes.h" #if !UCONFIG_NO_FORMATTING @@ -15,7 +15,7 @@ #include "unicode/smpdtfmt.h" #include "caltztst.h" -/** +/** * Performs many different tests for DateFormat and SimpleDateFormat **/ class DateFormatTest: public CalendarTimeZoneTest { @@ -40,23 +40,23 @@ public: * Test the parsing of 2-digit years. */ virtual void TestTwoDigitYearDSTParse(void); - + public: // package // internal utility routine (genrates escape sequences for characters) static UnicodeString& escape(UnicodeString& s); - + public: /** * Verify that returned field position indices are correct. */ void TestFieldPosition(void); - + void TestGeneral(); public: // package // internal utility function static void getFieldText(DateFormat* df, int32_t field, UDate date, UnicodeString& str); - + public: /** * Verify that strings which contain incomplete specifications are parsed @@ -64,11 +64,11 @@ public: * returning an appropriate error. */ virtual void TestPartialParse994(void); - + public: // package // internal test subroutine, used by TestPartialParse994 virtual void tryPat994(SimpleDateFormat* format, const char* pat, const char* str, UDate expected); - + public: /** * Verify the behavior of patterns in which digits for different fields run together @@ -80,11 +80,11 @@ public: * without intervening separators. */ virtual void TestRunTogetherPattern917(void); - + public: // package // internal test subroutine, used by TestRunTogetherPattern917 virtual void testIt917(SimpleDateFormat* fmt, UnicodeString& str, UDate expected); - + public: /** * Verify the handling of Czech June and July, which have the unique attribute that @@ -99,11 +99,11 @@ public: * Test the day of year pattern. */ virtual void TestDayOfYearPattern195(void); - + public: // package // interl test subroutine, used by TestDayOfYearPattern195 virtual void tryPattern(SimpleDateFormat& sdf, UDate d, const char* pattern, UDate expected); - + public: /** * Test the handling of single quotes in patterns. @@ -113,7 +113,7 @@ public: * Verify the correct behavior when handling invalid input strings. */ virtual void TestBadInput135(void); - + public: /** * Verify the correct behavior when parsing an array of inputs against an @@ -125,11 +125,11 @@ public: * Test the parsing of two-digit years. */ virtual void TestTwoDigitYear(void); - + public: // package // internal test subroutine, used by TestTwoDigitYear virtual void parse2DigitYear(DateFormat& fmt, const char* str, UDate expected); - + public: /** * Test the formatting of time zones. @@ -209,11 +209,11 @@ public: void TestStandAloneMonths(void); void TestQuarters(void); - + void TestZTimeZoneParsing(void); void TestRelativeClone(void); - + void TestHostClone(void); void TestHebrewClone(void); @@ -252,6 +252,12 @@ public: void TestPatternFromSkeleton(); + void TestAmPmMidnightNoon(); + void TestFlexibleDayPeriod(); + void TestDayPeriodWithLocales(); + void TestMinuteSecondFieldsInOddPlaces(); + void TestDayPeriodParsing(); + private: UBool showParse(DateFormat &format, const UnicodeString &formattedString); @@ -262,7 +268,7 @@ public: void TestNumberAsStringParsing(void); private: - void TestRelative(int daysdelta, + void TestRelative(int daysdelta, const Locale& loc, const char *expectChars); @@ -275,9 +281,10 @@ public: void expectFormat(const char **data, int32_t data_length, const Locale &locale); + }; #endif /* #if !UCONFIG_NO_FORMATTING */ - + #endif // _DATEFORMATTEST_ //eof diff --git a/icu4c/source/test/intltest/sdtfmtts.cpp b/icu4c/source/test/intltest/sdtfmtts.cpp index 546b43ccac9..9d2193a345e 100644 --- a/icu4c/source/test/intltest/sdtfmtts.cpp +++ b/icu4c/source/test/intltest/sdtfmtts.cpp @@ -1,7 +1,7 @@ /******************************************************************** - * COPYRIGHT: - * Copyright (c) 1997-2014, International Business Machines Corporation and + * COPYRIGHT: + * Copyright (c) 1997-2016, International Business Machines Corporation and * others. All Rights Reserved. ********************************************************************/ @@ -22,7 +22,7 @@ void IntlTestSimpleDateFormatAPI::runIndexedTest( int32_t index, UBool exec, con { if (exec) logln("TestSuite SimpleDateFormatAPI"); switch (index) { - case 0: name = "SimpleDateFormat API test"; + case 0: name = "SimpleDateFormat API test"; if (exec) { logln("SimpleDateFormat API test---"); logln(""); UErrorCode status = U_ZERO_ERROR; @@ -271,8 +271,8 @@ void IntlTestSimpleDateFormatAPI::testAPI(/*char *par*/) } // ====== Test ticket 11295 getNumberFormatForField returns wild pointer - if (object.getNumberFormatForField('B') != NULL) { - errln("B is not a valid field, " + if (object.getNumberFormatForField('N') != NULL) { + errln("N is not a valid field, " "getNumberFormatForField should return NULL"); } }