Index: source/i18n/smpdtfmt.cpp |
diff --git a/source/i18n/smpdtfmt.cpp b/source/i18n/smpdtfmt.cpp |
index 01c9f7c7981b40e3e62b06091baa559c8c2909a5..85cc162a11f39f5e76732bf1140a09e7a3687953 100644 |
--- a/source/i18n/smpdtfmt.cpp |
+++ b/source/i18n/smpdtfmt.cpp |
@@ -1,6 +1,8 @@ |
+// Copyright (C) 2016 and later: Unicode, Inc. and others. |
+// License & terms of use: http://www.unicode.org/copyright.html |
/* |
******************************************************************************* |
-* Copyright (C) 1997-2015, International Business Machines Corporation and * |
+* Copyright (C) 1997-2016, International Business Machines Corporation and * |
* others. All Rights Reserved. * |
******************************************************************************* |
* |
@@ -17,7 +19,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 |
@@ -42,6 +44,7 @@ |
#include "unicode/uniset.h" |
#include "unicode/ustring.h" |
#include "unicode/basictz.h" |
+#include "unicode/simpleformatter.h" |
#include "unicode/simpletz.h" |
#include "unicode/rbtz.h" |
#include "unicode/tzfmt.h" |
@@ -49,10 +52,10 @@ |
#include "unicode/vtzone.h" |
#include "unicode/udisplaycontext.h" |
#include "unicode/brkiter.h" |
+#include "uresimp.h" |
#include "olsontz.h" |
#include "patternprops.h" |
#include "fphdlimp.h" |
-#include "gregoimp.h" |
#include "hebrwcal.h" |
#include "cstring.h" |
#include "uassert.h" |
@@ -62,6 +65,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 <stdio.h> |
@@ -159,9 +166,6 @@ static const UChar SUPPRESS_NEGATIVE_PREFIX[] = {0xAB00, 0}; |
* These are the tags we expect to see in normal resource bundle files associated |
* with a locale. |
*/ |
-static const char gDateTimePatternsTag[]="DateTimePatterns"; |
- |
-//static const UChar gEtcUTC[] = {0x45, 0x74, 0x63, 0x2F, 0x55, 0x54, 0x43, 0x00}; // "Etc/UTC" |
static const UChar QUOTE = 0x27; // Single quote |
/* |
@@ -621,6 +625,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) { |
@@ -695,20 +701,42 @@ void SimpleDateFormat::construct(EStyle timeStyle, |
initializeCalendar(NULL, locale, status); |
if (U_FAILURE(status)) return; |
- CalendarData calData(locale, fCalendar?fCalendar->getType():NULL, status); |
- UResourceBundle *dateTimePatterns = calData.getByKey(gDateTimePatternsTag, status); |
- UResourceBundle *currentBundle; |
+ // Load date time patterns directly from resources. |
+ const char* cType = fCalendar ? fCalendar->getType() : NULL; |
+ LocalUResourceBundlePointer bundle(ures_open(NULL, locale.getBaseName(), &status)); |
+ if (U_FAILURE(status)) return; |
+ |
+ UBool cTypeIsGregorian = TRUE; |
+ LocalUResourceBundlePointer dateTimePatterns; |
+ if (cType != NULL && uprv_strcmp(cType, "gregorian") != 0) { |
+ CharString resourcePath("calendar/", status); |
+ resourcePath.append(cType, status).append("/DateTimePatterns", status); |
+ dateTimePatterns.adoptInstead( |
+ ures_getByKeyWithFallback(bundle.getAlias(), resourcePath.data(), |
+ (UResourceBundle*)NULL, &status)); |
+ cTypeIsGregorian = FALSE; |
+ } |
+ // Check for "gregorian" fallback. |
+ if (cTypeIsGregorian || status == U_MISSING_RESOURCE_ERROR) { |
+ status = U_ZERO_ERROR; |
+ dateTimePatterns.adoptInstead( |
+ ures_getByKeyWithFallback(bundle.getAlias(), |
+ "calendar/gregorian/DateTimePatterns", |
+ (UResourceBundle*)NULL, &status)); |
+ } |
if (U_FAILURE(status)) return; |
- if (ures_getSize(dateTimePatterns) <= kDateTime) |
+ LocalUResourceBundlePointer currentBundle; |
+ |
+ if (ures_getSize(dateTimePatterns.getAlias()) <= kDateTime) |
{ |
status = U_INVALID_FORMAT_ERROR; |
return; |
} |
- setLocaleIDs(ures_getLocaleByType(dateTimePatterns, ULOC_VALID_LOCALE, &status), |
- ures_getLocaleByType(dateTimePatterns, ULOC_ACTUAL_LOCALE, &status)); |
+ setLocaleIDs(ures_getLocaleByType(dateTimePatterns.getAlias(), ULOC_VALID_LOCALE, &status), |
+ ures_getLocaleByType(dateTimePatterns.getAlias(), ULOC_ACTUAL_LOCALE, &status)); |
// create a symbols object from the locale |
fSymbols = DateFormatSymbols::createForLocale(locale, status); |
@@ -726,144 +754,122 @@ void SimpleDateFormat::construct(EStyle timeStyle, |
// if the pattern should include both date and time information, use the date/time |
// pattern string as a guide to tell use how to glue together the appropriate date |
- // and time pattern strings. The actual gluing-together is handled by a convenience |
- // method on MessageFormat. |
+ // and time pattern strings. |
if ((timeStyle != kNone) && (dateStyle != kNone)) |
{ |
- Formattable timeDateArray[2]; |
- |
- // use Formattable::adoptString() so that we can use fastCopyFrom() |
- // instead of Formattable::setString()'s unaware, safe, deep string clone |
- // see Jitterbug 2296 |
- |
- currentBundle = ures_getByIndex(dateTimePatterns, (int32_t)timeStyle, NULL, &status); |
+ currentBundle.adoptInstead( |
+ ures_getByIndex(dateTimePatterns.getAlias(), (int32_t)timeStyle, NULL, &status)); |
if (U_FAILURE(status)) { |
status = U_INVALID_FORMAT_ERROR; |
return; |
} |
- switch (ures_getType(currentBundle)) { |
+ switch (ures_getType(currentBundle.getAlias())) { |
case URES_STRING: { |
- resStr = ures_getString(currentBundle, &resStrLen, &status); |
+ resStr = ures_getString(currentBundle.getAlias(), &resStrLen, &status); |
break; |
} |
case URES_ARRAY: { |
- resStr = ures_getStringByIndex(currentBundle, 0, &resStrLen, &status); |
- ovrStr = ures_getStringByIndex(currentBundle, 1, &ovrStrLen, &status); |
+ resStr = ures_getStringByIndex(currentBundle.getAlias(), 0, &resStrLen, &status); |
+ ovrStr = ures_getStringByIndex(currentBundle.getAlias(), 1, &ovrStrLen, &status); |
fTimeOverride.setTo(TRUE, ovrStr, ovrStrLen); |
break; |
} |
default: { |
status = U_INVALID_FORMAT_ERROR; |
- ures_close(currentBundle); |
return; |
} |
} |
- ures_close(currentBundle); |
- UnicodeString *tempus1 = new UnicodeString(TRUE, resStr, resStrLen); |
- // NULL pointer check |
- if (tempus1 == NULL) { |
- status = U_MEMORY_ALLOCATION_ERROR; |
- return; |
- } |
- timeDateArray[0].adoptString(tempus1); |
+ UnicodeString tempus1(TRUE, resStr, resStrLen); |
- currentBundle = ures_getByIndex(dateTimePatterns, (int32_t)dateStyle, NULL, &status); |
+ currentBundle.adoptInstead( |
+ ures_getByIndex(dateTimePatterns.getAlias(), (int32_t)dateStyle, NULL, &status)); |
if (U_FAILURE(status)) { |
status = U_INVALID_FORMAT_ERROR; |
return; |
} |
- switch (ures_getType(currentBundle)) { |
+ switch (ures_getType(currentBundle.getAlias())) { |
case URES_STRING: { |
- resStr = ures_getString(currentBundle, &resStrLen, &status); |
+ resStr = ures_getString(currentBundle.getAlias(), &resStrLen, &status); |
break; |
} |
case URES_ARRAY: { |
- resStr = ures_getStringByIndex(currentBundle, 0, &resStrLen, &status); |
- ovrStr = ures_getStringByIndex(currentBundle, 1, &ovrStrLen, &status); |
+ resStr = ures_getStringByIndex(currentBundle.getAlias(), 0, &resStrLen, &status); |
+ ovrStr = ures_getStringByIndex(currentBundle.getAlias(), 1, &ovrStrLen, &status); |
fDateOverride.setTo(TRUE, ovrStr, ovrStrLen); |
break; |
} |
default: { |
status = U_INVALID_FORMAT_ERROR; |
- ures_close(currentBundle); |
return; |
} |
} |
- ures_close(currentBundle); |
- UnicodeString *tempus2 = new UnicodeString(TRUE, resStr, resStrLen); |
- // Null pointer check |
- if (tempus2 == NULL) { |
- status = U_MEMORY_ALLOCATION_ERROR; |
- return; |
- } |
- timeDateArray[1].adoptString(tempus2); |
+ UnicodeString tempus2(TRUE, resStr, resStrLen); |
int32_t glueIndex = kDateTime; |
- int32_t patternsSize = ures_getSize(dateTimePatterns); |
+ int32_t patternsSize = ures_getSize(dateTimePatterns.getAlias()); |
if (patternsSize >= (kDateTimeOffset + kShort + 1)) { |
// Get proper date time format |
glueIndex = (int32_t)(kDateTimeOffset + (dateStyle - kDateOffset)); |
} |
- resStr = ures_getStringByIndex(dateTimePatterns, glueIndex, &resStrLen, &status); |
- MessageFormat::format(UnicodeString(TRUE, resStr, resStrLen), timeDateArray, 2, fPattern, status); |
+ resStr = ures_getStringByIndex(dateTimePatterns.getAlias(), glueIndex, &resStrLen, &status); |
+ SimpleFormatter(UnicodeString(TRUE, resStr, resStrLen), 2, 2, status). |
+ format(tempus1, tempus2, fPattern, status); |
} |
// if the pattern includes just time data or just date date, load the appropriate |
// pattern string from the resources |
// setTo() - see DateFormatSymbols::assignArray comments |
else if (timeStyle != kNone) { |
- currentBundle = ures_getByIndex(dateTimePatterns, (int32_t)timeStyle, NULL, &status); |
+ currentBundle.adoptInstead( |
+ ures_getByIndex(dateTimePatterns.getAlias(), (int32_t)timeStyle, NULL, &status)); |
if (U_FAILURE(status)) { |
status = U_INVALID_FORMAT_ERROR; |
return; |
} |
- switch (ures_getType(currentBundle)) { |
+ switch (ures_getType(currentBundle.getAlias())) { |
case URES_STRING: { |
- resStr = ures_getString(currentBundle, &resStrLen, &status); |
+ resStr = ures_getString(currentBundle.getAlias(), &resStrLen, &status); |
break; |
} |
case URES_ARRAY: { |
- resStr = ures_getStringByIndex(currentBundle, 0, &resStrLen, &status); |
- ovrStr = ures_getStringByIndex(currentBundle, 1, &ovrStrLen, &status); |
+ resStr = ures_getStringByIndex(currentBundle.getAlias(), 0, &resStrLen, &status); |
+ ovrStr = ures_getStringByIndex(currentBundle.getAlias(), 1, &ovrStrLen, &status); |
fDateOverride.setTo(TRUE, ovrStr, ovrStrLen); |
break; |
} |
default: { |
status = U_INVALID_FORMAT_ERROR; |
- ures_close(currentBundle); |
return; |
} |
} |
fPattern.setTo(TRUE, resStr, resStrLen); |
- ures_close(currentBundle); |
} |
else if (dateStyle != kNone) { |
- currentBundle = ures_getByIndex(dateTimePatterns, (int32_t)dateStyle, NULL, &status); |
+ currentBundle.adoptInstead( |
+ ures_getByIndex(dateTimePatterns.getAlias(), (int32_t)dateStyle, NULL, &status)); |
if (U_FAILURE(status)) { |
status = U_INVALID_FORMAT_ERROR; |
return; |
} |
- switch (ures_getType(currentBundle)) { |
+ switch (ures_getType(currentBundle.getAlias())) { |
case URES_STRING: { |
- resStr = ures_getString(currentBundle, &resStrLen, &status); |
+ resStr = ures_getString(currentBundle.getAlias(), &resStrLen, &status); |
break; |
} |
case URES_ARRAY: { |
- resStr = ures_getStringByIndex(currentBundle, 0, &resStrLen, &status); |
- ovrStr = ures_getStringByIndex(currentBundle, 1, &ovrStrLen, &status); |
+ resStr = ures_getStringByIndex(currentBundle.getAlias(), 0, &resStrLen, &status); |
+ ovrStr = ures_getStringByIndex(currentBundle.getAlias(), 1, &ovrStrLen, &status); |
fDateOverride.setTo(TRUE, ovrStr, ovrStrLen); |
break; |
} |
default: { |
status = U_INVALID_FORMAT_ERROR; |
- ures_close(currentBundle); |
return; |
} |
} |
fPattern.setTo(TRUE, resStr, resStrLen); |
- ures_close(currentBundle); |
} |
// and if it includes _neither_, that's an error |
@@ -906,6 +912,8 @@ SimpleDateFormat::initialize(const Locale& locale, |
{ |
status = U_MISSING_RESOURCE_ERROR; |
} |
+ |
+ parsePattern(); |
} |
/* Initialize the fields we use to disambiguate ambiguous years. Separate |
@@ -986,7 +994,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; |
@@ -1178,6 +1186,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 |
@@ -1206,6 +1215,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 |
@@ -1238,8 +1248,7 @@ _appendSymbolWithMonthPattern(UnicodeString& dst, int32_t value, const UnicodeSt |
if (monthPattern == NULL) { |
dst += symbols[value]; |
} else { |
- Formattable monthName((const UnicodeString&)(symbols[value])); |
- MessageFormat::format(*monthPattern, &monthName, 1, dst, status); |
+ SimpleFormatter(*monthPattern, 1, 1, status).format(symbols[value], dst, status); |
} |
} |
} |
@@ -1355,6 +1364,7 @@ SimpleDateFormat::processOverrideString(const Locale &locale, const UnicodeStrin |
if (type==kOvrStrDate) { |
break; |
} |
+ U_FALLTHROUGH; |
} |
case kOvrStrTime : { |
for ( int8_t i=0 ; i<kTimeFieldsCount; i++ ) { |
@@ -1437,7 +1447,7 @@ SimpleDateFormat::subFormat(UnicodeString &appendTo, |
return; |
} |
UnicodeString hebr("hebr", 4, US_INV); |
- |
+ |
switch (patternCharIndex) { |
// for any "G" symbol, write out the appropriate era string |
@@ -1466,6 +1476,7 @@ SimpleDateFormat::subFormat(UnicodeString &appendTo, |
break; |
} |
// else fall through to numeric year handling, do not break here |
+ U_FALLTHROUGH; |
// OLD: for "yyyy", write out the whole year; for "yy", write out the last 2 digits |
// NEW: UTS#35: |
@@ -1559,7 +1570,7 @@ SimpleDateFormat::subFormat(UnicodeString &appendTo, |
} else if (count == 2) { |
value /= 10; |
} |
- FieldPosition p(0); |
+ FieldPosition p(FieldPosition::DONT_CARE); |
currentNumberFormat->format(value, appendTo, p); |
if (count > 3) { |
currentNumberFormat->setMinimumIntegerDigits(count - 3); |
@@ -1585,6 +1596,7 @@ SimpleDateFormat::subFormat(UnicodeString &appendTo, |
return; |
} |
// fall through, do not break here |
+ U_FALLTHROUGH; |
case UDAT_DAY_OF_WEEK_FIELD: |
if (count == 5) { |
_appendSymbol(appendTo, value, fSymbols->fNarrowWeekdays, |
@@ -1806,6 +1818,140 @@ 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); |
+ |
+ // Note: "midnight" can be ambiguous as to whether it refers to beginning of day or end of day. |
+ // For ICU 57 output of "midnight" is temporarily suppressed. |
+ |
+ // 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; |
+ |
+ // Note: "midnight" can be ambiguous as to whether it refers to beginning of day or end of day. |
+ // For ICU 57 output of "midnight" is temporarily suppressed. |
+ |
+ if (periodType != DayPeriodRules::DAYPERIOD_AM && |
+ periodType != DayPeriodRules::DAYPERIOD_PM && |
+ periodType != DayPeriodRules::DAYPERIOD_MIDNIGHT) { |
+ 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 == NULL || 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 (periodType == DayPeriodRules::DAYPERIOD_AM || |
+ periodType == DayPeriodRules::DAYPERIOD_PM || |
+ 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 |
@@ -1848,7 +1994,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) { |
@@ -1911,7 +2057,7 @@ SimpleDateFormat::zeroPaddingNumber( |
int32_t value, int32_t minDigits, int32_t maxDigits) const |
{ |
if (currentNumberFormat!=NULL) { |
- FieldPosition pos(0); |
+ FieldPosition pos(FieldPosition::DONT_CARE); |
currentNumberFormat->setMinimumIntegerDigits(minDigits); |
currentNumberFormat->setMaximumIntegerDigits(maxDigits); |
@@ -1974,6 +2120,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; |
@@ -2012,7 +2161,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) { |
@@ -2088,7 +2237,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 |
@@ -2124,7 +2273,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; |
@@ -2140,6 +2289,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; } |
+ } |
+ U_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. |
@@ -2364,6 +2583,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, |
@@ -2374,17 +2616,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) { |
@@ -2394,47 +2636,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) |
@@ -2443,7 +2685,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()) { |
@@ -2461,26 +2703,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. |
@@ -2492,20 +2734,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; |
} |
@@ -2542,8 +2784,7 @@ int32_t SimpleDateFormat::matchString(const UnicodeString& text, |
if (monthPattern != NULL) { |
UErrorCode status = U_ZERO_ERROR; |
UnicodeString leapMonthName; |
- Formattable monthName((const UnicodeString&)(data[i])); |
- MessageFormat::format(*monthPattern, &monthName, 1, leapMonthName, status); |
+ SimpleFormatter(*monthPattern, 1, 1, status).format(data[i], leapMonthName, status); |
if (U_SUCCESS(status)) { |
if ((matchLen = matchStringWithOptionalDot(text, start, leapMonthName)) > bestMatchLength) { |
bestMatch = i; |
@@ -2614,7 +2855,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; |
@@ -2721,7 +2963,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); |
@@ -2744,7 +2986,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. |
@@ -2757,9 +2999,9 @@ int32_t SimpleDateFormat::subParse(const UnicodeString& text, int32_t& start, UC |
if (value < 0 || value > 24) { |
return -start; |
} |
- |
+ |
// fall through to gotNumber check |
- |
+ U_FALLTHROUGH; |
case UDAT_YEAR_FIELD: |
case UDAT_YEAR_WOY_FIELD: |
case UDAT_FRACTIONAL_SECOND_FIELD: |
@@ -2767,9 +3009,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; |
@@ -2948,9 +3190,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 |
- |
+ U_FALLTHROUGH; |
case UDAT_HOUR_OF_DAY0_FIELD: |
cal.set(UCAL_HOUR_OF_DAY, value); |
return pos.getIndex(); |
@@ -2983,6 +3225,7 @@ int32_t SimpleDateFormat::subParse(const UnicodeString& text, int32_t& start, UC |
} |
// else for eee-eeeee fall through to handling of EEE-EEEEE |
// fall through, do not break here |
+ U_FALLTHROUGH; |
case UDAT_DAY_OF_WEEK_FIELD: |
{ |
// Want to be able to parse both short and long forms. |
@@ -3073,9 +3316,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 |
- |
+ U_FALLTHROUGH; |
case UDAT_HOUR0_FIELD: |
cal.set(UCAL_HOUR, value); |
return pos.getIndex(); |
@@ -3265,7 +3508,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; |
@@ -3288,6 +3531,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 |
@@ -3427,7 +3734,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 |
@@ -3491,6 +3798,7 @@ void |
SimpleDateFormat::applyPattern(const UnicodeString& pattern) |
{ |
fPattern = pattern; |
+ parsePattern(); |
} |
//---------------------------------------------------------------------- |
@@ -3818,6 +4126,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 */ |