X-Git-Url: https://git.saurik.com/apple/icu.git/blobdiff_plain/a0b4f637ba1a6c3c5651b61a69303b029bacf7d3..2ca993e82fb37b597a3c73ecd1586a139a6579c5:/icuSources/i18n/smpdtfmt.cpp diff --git a/icuSources/i18n/smpdtfmt.cpp b/icuSources/i18n/smpdtfmt.cpp index b5ca4a0c..2bcb5920 100644 --- a/icuSources/i18n/smpdtfmt.cpp +++ b/icuSources/i18n/smpdtfmt.cpp @@ -1,6 +1,6 @@ /* ******************************************************************************* -* Copyright (C) 1997-2015, International Business Machines Corporation and * +* Copyright (C) 1997-2016, International Business Machines Corporation and * * others. All Rights Reserved. * ******************************************************************************* * @@ -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 @@ -42,6 +42,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" @@ -62,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 @@ -209,7 +214,11 @@ static const int32_t gFieldRangeBias[] = { -1, // 'X' - UDAT_TIMEZONE_ISO_FIELD -1, // 'x' - UDAT_TIMEZONE_ISO_LOCAL_FIELD -1, // 'r' - UDAT_RELATED_YEAR_FIELD +#if UDAT_HAS_PATTERN_CHAR_FOR_TIME_SEPARATOR -1, // ':' - UDAT_TIME_SEPARATOR_FIELD +#else + -1, // (no pattern character currently) - UDAT_TIME_SEPARATOR_FIELD +#endif }; // A slightly looser range check for lenient parsing static const int32_t gFieldRangeBiasLenient[] = { @@ -248,7 +257,11 @@ static const int32_t gFieldRangeBiasLenient[] = { -1, // 'X' - UDAT_TIMEZONE_ISO_FIELD -1, // 'x' - UDAT_TIMEZONE_ISO_LOCAL_FIELD -1, // 'r' - UDAT_RELATED_YEAR_FIELD +#if UDAT_HAS_PATTERN_CHAR_FOR_TIME_SEPARATOR -1, // ':' - UDAT_TIME_SEPARATOR_FIELD +#else + -1, // (no pattern character currently) - UDAT_TIME_SEPARATOR_FIELD +#endif }; // When calendar uses hebr numbering (i.e. he@calendar=hebrew), @@ -393,32 +406,6 @@ class SimpleDateFormatMutableNFs : public UMemory { //---------------------------------------------------------------------- -static void updateTimeSepFromPattern( - const UnicodeString& pattern, - DateFormatSymbols* symbols ) { - UnicodeString hourMinChars("hHKkm", -1, US_INV); // pattern chars for hours, minutes - UnicodeString colon(":", -1, US_INV); - UBool inQuoted = FALSE; - UBool lastPatCharWasHourMin = FALSE; - int32_t patPos, patLen = pattern.length(); - for (patPos = 0; patPos < patLen; patPos++) { - UChar patChr = pattern.charAt(patPos); - if (patChr == 0x27 /* ASCII-range single quote */) { - inQuoted = !inQuoted; - } else if (!inQuoted) { - if (patChr == 0x3A /*colon*/ && lastPatCharWasHourMin) { - symbols->setTimeSeparatorString(colon); - break; - } - if ((patChr >= 0x41 && patChr <= 0x5A) || (patChr >= 0x61 && patChr <= 0x7A)) { - lastPatCharWasHourMin = (hourMinChars.indexOf(patChr) >= 0); - } - } - } -} - -//---------------------------------------------------------------------- - SimpleDateFormat::~SimpleDateFormat() { delete fSymbols; @@ -466,7 +453,7 @@ SimpleDateFormat::SimpleDateFormat(const UnicodeString& pattern, fSymbols = DateFormatSymbols::createForLocale(fLocale, status); initialize(fLocale, status); initializeDefaultCentury(); - updateTimeSepFromPattern(fPattern, fSymbols); + } //---------------------------------------------------------------------- @@ -513,7 +500,6 @@ SimpleDateFormat::SimpleDateFormat(const UnicodeString& pattern, fSymbols = DateFormatSymbols::createForLocale(fLocale, status); initialize(fLocale, status); initializeDefaultCentury(); - updateTimeSepFromPattern(fPattern, fSymbols); } //---------------------------------------------------------------------- @@ -684,6 +670,8 @@ SimpleDateFormat& SimpleDateFormat::operator=(const SimpleDateFormat& other) fHaveDefaultCentury = other.fHaveDefaultCentury; fPattern = other.fPattern; + fHasMinute = other.fHasMinute; + fHasSecond = other.fHasSecond; fLocale = other.fLocale; // TimeZoneFormat can now be set independently via setter. @@ -798,16 +786,9 @@ 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); if (U_FAILURE(status)) { status = U_INVALID_FORMAT_ERROR; @@ -832,13 +813,7 @@ void SimpleDateFormat::construct(EStyle timeStyle, } 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); if (U_FAILURE(status)) { @@ -864,13 +839,7 @@ void SimpleDateFormat::construct(EStyle timeStyle, } 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); @@ -880,7 +849,8 @@ void SimpleDateFormat::construct(EStyle timeStyle, } resStr = ures_getStringByIndex(dateTimePatterns, glueIndex, &resStrLen, &status); - MessageFormat::format(UnicodeString(TRUE, resStr, resStrLen), timeDateArray, 2, fPattern, 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 @@ -993,6 +963,8 @@ SimpleDateFormat::initialize(const Locale& locale, { status = U_MISSING_RESOURCE_ERROR; } + + parsePattern(); } /* Initialize the fields we use to disambiguate ambiguous years. Separate @@ -1021,7 +993,7 @@ void SimpleDateFormat::initializeBooleanAttributes() setBooleanAttribute(UDAT_PARSE_ALLOW_WHITESPACE, true, status); setBooleanAttribute(UDAT_PARSE_ALLOW_NUMERIC, true, status); - setBooleanAttribute(UDAT_PARSE_PARTIAL_MATCH, true, status); + setBooleanAttribute(UDAT_PARSE_PARTIAL_LITERAL_MATCH, true, status); setBooleanAttribute(UDAT_PARSE_MULTIPLE_PATTERNS_FOR_MATCH, true, status); } @@ -1073,7 +1045,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; @@ -1179,8 +1151,13 @@ int32_t SimpleDateFormat::getLevelFromChar(UChar ch) { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // ! " # $ % & ' ( ) * + , - . / -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, +#if UDAT_HAS_PATTERN_CHAR_FOR_TIME_SEPARATOR // 0 1 2 3 4 5 6 7 8 9 : ; < = > ? -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 0, -1, -1, -1, -1, -1, +#else + // 0 1 2 3 4 5 6 7 8 9 : ; < = > ? + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, +#endif // @ A B C D E F G H I J K L M N O -1, 40, -1, -1, 20, 30, 30, 0, 50, -1, -1, 50, 20, 20, -1, 0, // P Q R S T U V W X Y Z [ \ ] ^ _ @@ -1210,8 +1187,13 @@ UBool SimpleDateFormat::isSyntaxChar(UChar ch) { FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, // 0 1 2 3 4 5 6 7 FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, +#if UDAT_HAS_PATTERN_CHAR_FOR_TIME_SEPARATOR // 8 9 : ; < = > ? FALSE, FALSE, TRUE, FALSE, FALSE, FALSE, FALSE, FALSE, +#else + // 8 9 : ; < = > ? + FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, +#endif // @ A B C D E F G FALSE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, // H I J K L M N O @@ -1255,7 +1237,12 @@ 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 + /*no pattern char for UDAT_TIME_SEPARATOR_FIELD*/ UCAL_FIELD_COUNT, /* => no useful mapping to any calendar field */ +#endif }; // Map index into pattern character string to DateFormat field number @@ -1279,7 +1266,12 @@ 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 + /*no pattern char for UDAT_TIME_SEPARATOR_FIELD*/ UDAT_TIME_SEPARATOR_FIELD, +#endif }; //---------------------------------------------------------------------- @@ -1307,8 +1299,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); } } } @@ -1424,6 +1415,7 @@ SimpleDateFormat::processOverrideString(const Locale &locale, const UnicodeStrin if (type==kOvrStrDate) { break; } + U_FALLTHROUGH; } case kOvrStrTime : { for ( int8_t i=0 ; ifNarrowWeekdays, @@ -1719,7 +1713,8 @@ SimpleDateFormat::subFormat(UnicodeString &appendTo, } break; - // for ":", write out the time separator string + // if we see pattern character for UDAT_TIME_SEPARATOR_FIELD (none currently defined), + // write out the time separator string. Leave support in for future definition. case UDAT_TIME_SEPARATOR_FIELD: { UnicodeString separator; @@ -1874,6 +1869,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 @@ -1916,7 +2045,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) { @@ -2042,6 +2171,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; @@ -2080,7 +2212,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) { @@ -2156,7 +2288,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 @@ -2192,8 +2324,8 @@ 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_MATCH, status), isLenient())) { + + 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; } @@ -2208,6 +2340,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. @@ -2432,6 +2634,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; +} + //---------------------------------------------------------------------- #define IS_BIDI_MARK(c) (c==0x200E || c==0x200F || c==0x061C) @@ -2444,17 +2669,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) { @@ -2464,21 +2689,21 @@ UBool SimpleDateFormat::matchLiterals(const UnicodeString &pattern, continue; } } - + if (!IS_BIDI_MARK(ch)) { literal += ch; } } - + // at this point, literal contains the pattern literal text (without bidi marks) // 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 pattern literal literal.trim(); - + // ignore any leading whitespace (or bidi marks) in the text while (t < text.length()) { UChar ch = text.charAt(t); @@ -2488,7 +2713,7 @@ UBool SimpleDateFormat::matchLiterals(const UnicodeString &pattern, t += 1; } } - + // Get ignorables, move up here const UnicodeSet *ignorables = NULL; UDateFormatField patternCharIndex = DateFormatSymbols::getPatternCharIndex(pattern.charAt(i)); @@ -2498,7 +2723,7 @@ UBool SimpleDateFormat::matchLiterals(const UnicodeString &pattern, for (p = 0; p < literal.length() && t < text.length();) { UBool needWhitespace = FALSE; - + // Skip any whitespace at current position in pattern, // but remember whether we found whitespace in the pattern // (we already deleted any bidi marks in the pattern). @@ -2506,7 +2731,7 @@ UBool SimpleDateFormat::matchLiterals(const UnicodeString &pattern, needWhitespace = TRUE; p += 1; } - + // If the pattern has whitespace at this point, skip it in text as well // (if the text does not have any, that may be an error for strict parsing) if (needWhitespace) { @@ -2521,10 +2746,10 @@ UBool SimpleDateFormat::matchLiterals(const UnicodeString &pattern, } else if (!IS_BIDI_MARK(tch)) { break; } - + t += 1; } - + // TODO: should we require internal spaces // in lenient mode? (There won't be any // leading or trailing spaces) @@ -2533,7 +2758,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()) { @@ -2556,15 +2781,15 @@ 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 // This fix is for http://bugs.icu-project.org/trac/ticket/10855 and adds "&& oldLeniency" //if(partialMatchLenient && oldLeniency) { @@ -2576,33 +2801,33 @@ UBool SimpleDateFormat::matchLiterals(const UnicodeString &pattern, ) { 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. if (p <= 0) { // no match. Pretend it matched a run of whitespace // and ignorables in the text. - + for (t = textOffset; t < text.length(); t += 1) { UChar ch = text.charAt(t); - + if (!IS_BIDI_MARK(ch) && (ignorables == NULL || !ignorables->contains(ch))) { break; } } } - + // if we get here, we've got a complete match. patternOffset = i - 1; textOffset = t; - + return TRUE; } @@ -2639,8 +2864,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; @@ -2711,7 +2935,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; @@ -2819,7 +3044,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); @@ -2847,7 +3072,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. @@ -2860,9 +3085,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: @@ -2870,9 +3095,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; @@ -3051,9 +3276,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(); @@ -3086,6 +3311,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. @@ -3176,9 +3402,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(); @@ -3366,7 +3592,9 @@ int32_t SimpleDateFormat::subParse(const UnicodeString& text, int32_t& start, UC } return -start; } - case UDAT_TIME_SEPARATOR_FIELD: // ':' + // 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: { static const UChar def_sep = DateFormatSymbols::DEFAULT_TIME_SEPARATOR; static const UChar alt_sep = DateFormatSymbols::ALTERNATE_TIME_SEPARATOR; @@ -3389,6 +3617,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 @@ -3533,7 +3825,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 @@ -3597,7 +3889,7 @@ void SimpleDateFormat::applyPattern(const UnicodeString& pattern) { fPattern = pattern; - updateTimeSepFromPattern(fPattern, fSymbols); + parsePattern(); } //---------------------------------------------------------------------- @@ -3609,7 +3901,6 @@ SimpleDateFormat::applyLocalizedPattern(const UnicodeString& pattern, translatePattern(pattern, fPattern, fSymbols->fLocalPatternChars, UnicodeString(DateFormatSymbols::getPatternUChars()), status); - updateTimeSepFromPattern(fPattern, fSymbols); } //---------------------------------------------------------------------- @@ -3926,6 +4217,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 */