|  | /* | 
|  | ******************************************************************************* | 
|  | * Copyright (C) 1997-2001, International Business Machines Corporation and    * | 
|  | * others. All Rights Reserved.                                                * | 
|  | ******************************************************************************* | 
|  | * | 
|  | * File SMPDTFMT.CPP | 
|  | * | 
|  | * Modification History: | 
|  | * | 
|  | *   Date        Name        Description | 
|  | *   02/19/97    aliu        Converted from java. | 
|  | *   03/31/97    aliu        Modified extensively to work with 50 locales. | 
|  | *   04/01/97    aliu        Added support for centuries. | 
|  | *   07/09/97    helena      Made ParsePosition into a class. | 
|  | *   07/21/98    stephen     Added initializeDefaultCentury. | 
|  | *                             Removed getZoneIndex (added in DateFormatSymbols) | 
|  | *                             Removed subParseLong | 
|  | *                             Removed chk | 
|  | *  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 | 
|  | ******************************************************************************** | 
|  | */ | 
|  |  | 
|  | #include "unicode/smpdtfmt.h" | 
|  | #include "unicode/dtfmtsym.h" | 
|  | #include "unicode/resbund.h" | 
|  | #include "unicode/msgfmt.h" | 
|  | #include "unicode/calendar.h" | 
|  | #include "unicode/gregocal.h" | 
|  | #include "unicode/timezone.h" | 
|  | #include "unicode/decimfmt.h" | 
|  | #include "unicode/dcfmtsym.h" | 
|  | #include "unicode/unicode.h" | 
|  | #include "unicode/ustring.h" | 
|  | #include "mutex.h" | 
|  | #include <float.h> | 
|  |  | 
|  | // ***************************************************************************** | 
|  | // class SimpleDateFormat | 
|  | // ***************************************************************************** | 
|  |  | 
|  | U_NAMESPACE_BEGIN | 
|  |  | 
|  | // For time zones that have no names, use strings GMT+minutes and | 
|  | // GMT-minutes. For instance, in France the time zone is GMT+60. | 
|  | // Also accepted are GMT+H:MM or GMT-H:MM. | 
|  | const UChar SimpleDateFormat::fgGmt[]      = {0x0047, 0x004D, 0x0054, 0x0000};         // "GMT" | 
|  | const UChar SimpleDateFormat::fgGmtPlus[]  = {0x0047, 0x004D, 0x0054, 0x002B, 0x0000}; // "GMT+" | 
|  | const UChar SimpleDateFormat::fgGmtMinus[] = {0x0047, 0x004D, 0x0054, 0x002D, 0x0000}; // "GMT-" | 
|  |  | 
|  | // This is a pattern-of-last-resort used when we can't load a usable pattern out | 
|  | // of a resource. | 
|  | const UChar SimpleDateFormat::fgDefaultPattern[] = | 
|  | { | 
|  | 0x79, 0x79, 0x79, 0x79, 0x4D, 0x4D, 0x64, 0x64, 0x20, 0x68, 0x68, 0x3A, 0x6D, 0x6D, 0x20, 0x61, 0 | 
|  | };  /* "yyyyMMdd hh:mm a" */ | 
|  |  | 
|  | /** | 
|  | * These are the tags we expect to see in normal resource bundle files associated | 
|  | * with a locale. | 
|  | */ | 
|  | const char SimpleDateFormat::fgDateTimePatternsTag[]="DateTimePatterns"; | 
|  |  | 
|  | const char      SimpleDateFormat::fgClassID = 0; // Value is irrelevant | 
|  |  | 
|  | /** | 
|  | * This value of defaultCenturyStart indicates that the system default is to be | 
|  | * used. | 
|  | */ | 
|  | const UDate     SimpleDateFormat::fgSystemDefaultCentury        = DBL_MIN; | 
|  | const int32_t   SimpleDateFormat::fgSystemDefaultCenturyYear    = -1; | 
|  |  | 
|  | UDate           SimpleDateFormat::fgSystemDefaultCenturyStart       = DBL_MIN; | 
|  | int32_t         SimpleDateFormat::fgSystemDefaultCenturyStartYear   = -1; | 
|  |  | 
|  | //---------------------------------------------------------------------- | 
|  |  | 
|  | SimpleDateFormat::~SimpleDateFormat() | 
|  | { | 
|  | delete fSymbols; | 
|  | } | 
|  |  | 
|  | //---------------------------------------------------------------------- | 
|  |  | 
|  | SimpleDateFormat::SimpleDateFormat(UErrorCode& status) | 
|  | :   fSymbols(NULL), | 
|  | fDefaultCenturyStart(fgSystemDefaultCentury), | 
|  | fDefaultCenturyStartYear(fgSystemDefaultCenturyYear) | 
|  | { | 
|  | construct(kShort, (EStyle) (kShort + kDateOffset), Locale::getDefault(), status); | 
|  | } | 
|  |  | 
|  | //---------------------------------------------------------------------- | 
|  |  | 
|  | SimpleDateFormat::SimpleDateFormat(const UnicodeString& pattern, | 
|  | UErrorCode &status) | 
|  | :   fPattern(pattern), | 
|  | fSymbols(new DateFormatSymbols(status)), | 
|  | fDefaultCenturyStart(fgSystemDefaultCentury), | 
|  | fDefaultCenturyStartYear(fgSystemDefaultCenturyYear) | 
|  | { | 
|  | initialize(Locale::getDefault(), status); | 
|  | } | 
|  |  | 
|  | //---------------------------------------------------------------------- | 
|  |  | 
|  | SimpleDateFormat::SimpleDateFormat(const UnicodeString& pattern, | 
|  | const Locale& locale, | 
|  | UErrorCode& status) | 
|  | :   fPattern(pattern), | 
|  | fSymbols(new DateFormatSymbols(locale, status)), | 
|  | fDefaultCenturyStart(fgSystemDefaultCentury), | 
|  | fDefaultCenturyStartYear(fgSystemDefaultCenturyYear) | 
|  | { | 
|  | initialize(locale, status); | 
|  | } | 
|  |  | 
|  | //---------------------------------------------------------------------- | 
|  |  | 
|  | SimpleDateFormat::SimpleDateFormat(const UnicodeString& pattern, | 
|  | DateFormatSymbols* symbolsToAdopt, | 
|  | UErrorCode& status) | 
|  | :   fPattern(pattern), | 
|  | fSymbols(symbolsToAdopt), | 
|  | fDefaultCenturyStart(fgSystemDefaultCentury), | 
|  | fDefaultCenturyStartYear(fgSystemDefaultCenturyYear) | 
|  | { | 
|  | initialize(Locale::getDefault(), status); | 
|  | } | 
|  |  | 
|  | //---------------------------------------------------------------------- | 
|  |  | 
|  | SimpleDateFormat::SimpleDateFormat(const UnicodeString& pattern, | 
|  | const DateFormatSymbols& symbols, | 
|  | UErrorCode& status) | 
|  | :   fPattern(pattern), | 
|  | fSymbols(new DateFormatSymbols(symbols)), | 
|  | fDefaultCenturyStart(fgSystemDefaultCentury), | 
|  | fDefaultCenturyStartYear(fgSystemDefaultCenturyYear) | 
|  | { | 
|  | initialize(Locale::getDefault(), status); | 
|  | } | 
|  |  | 
|  | //---------------------------------------------------------------------- | 
|  |  | 
|  | // Not for public consumption; used by DateFormat | 
|  | SimpleDateFormat::SimpleDateFormat(EStyle timeStyle, | 
|  | EStyle dateStyle, | 
|  | const Locale& locale, | 
|  | UErrorCode& status) | 
|  | :   fSymbols(NULL), | 
|  | fDefaultCenturyStart(fgSystemDefaultCentury), | 
|  | fDefaultCenturyStartYear(fgSystemDefaultCenturyYear) | 
|  | { | 
|  | construct(timeStyle, dateStyle, locale, status); | 
|  | } | 
|  |  | 
|  | //---------------------------------------------------------------------- | 
|  |  | 
|  | /** | 
|  | * Not for public consumption; used by DateFormat.  This constructor | 
|  | * never fails.  If the resource data is not available, it uses the | 
|  | * the last resort symbols. | 
|  | */ | 
|  | SimpleDateFormat::SimpleDateFormat(const Locale& locale, | 
|  | UErrorCode& status) | 
|  | :   fPattern(fgDefaultPattern), | 
|  | fSymbols(NULL), | 
|  | fDefaultCenturyStart(fgSystemDefaultCentury), | 
|  | fDefaultCenturyStartYear(fgSystemDefaultCenturyYear) | 
|  | { | 
|  | if (U_FAILURE(status)) return; | 
|  | fSymbols = new DateFormatSymbols(locale, status); | 
|  | if (U_FAILURE(status)) | 
|  | { | 
|  | status = U_ZERO_ERROR; | 
|  | delete fSymbols; | 
|  | // This constructor doesn't fail; it uses last resort data | 
|  | fSymbols = new DateFormatSymbols(status); | 
|  | } | 
|  | initialize(locale, status); | 
|  | } | 
|  |  | 
|  | //---------------------------------------------------------------------- | 
|  |  | 
|  | SimpleDateFormat::SimpleDateFormat(const SimpleDateFormat& other) | 
|  | :   DateFormat(other), | 
|  | fSymbols(NULL), | 
|  | fDefaultCenturyStart(fgSystemDefaultCentury), | 
|  | fDefaultCenturyStartYear(fgSystemDefaultCenturyYear) | 
|  | { | 
|  | *this = other; | 
|  | } | 
|  |  | 
|  | //---------------------------------------------------------------------- | 
|  |  | 
|  | SimpleDateFormat& SimpleDateFormat::operator=(const SimpleDateFormat& other) | 
|  | { | 
|  | DateFormat::operator=(other); | 
|  |  | 
|  | delete fSymbols; | 
|  | fSymbols = NULL; | 
|  |  | 
|  | if (other.fSymbols) | 
|  | fSymbols = new DateFormatSymbols(*other.fSymbols); | 
|  |  | 
|  | fDefaultCenturyStart         = other.fDefaultCenturyStart; | 
|  | fDefaultCenturyStartYear     = other.fDefaultCenturyStartYear; | 
|  |  | 
|  | fPattern = other.fPattern; | 
|  |  | 
|  | return *this; | 
|  | } | 
|  |  | 
|  | //---------------------------------------------------------------------- | 
|  |  | 
|  | Format* | 
|  | SimpleDateFormat::clone() const | 
|  | { | 
|  | return new SimpleDateFormat(*this); | 
|  | } | 
|  |  | 
|  | //---------------------------------------------------------------------- | 
|  |  | 
|  | UBool | 
|  | SimpleDateFormat::operator==(const Format& other) const | 
|  | { | 
|  | if (DateFormat::operator==(other) && | 
|  | other.getDynamicClassID() == getStaticClassID()) | 
|  | { | 
|  | SimpleDateFormat* that = (SimpleDateFormat*)&other; | 
|  | return     (fPattern             == that->fPattern && | 
|  | fSymbols             != NULL && // Check for pathological object | 
|  | that->fSymbols         != NULL && // Check for pathological object | 
|  | *fSymbols             == *that->fSymbols && | 
|  | fDefaultCenturyStart == that->fDefaultCenturyStart); | 
|  | } | 
|  | return FALSE; | 
|  | } | 
|  |  | 
|  | //---------------------------------------------------------------------- | 
|  |  | 
|  | void SimpleDateFormat::construct(EStyle timeStyle, | 
|  | EStyle dateStyle, | 
|  | const Locale& locale, | 
|  | UErrorCode& status) | 
|  | { | 
|  | // called by several constructors to load pattern data from the resources | 
|  |  | 
|  | if (U_FAILURE(status)) return; | 
|  |  | 
|  | // load up the DateTimePatters resource from the appropriate locale (throw | 
|  | // an error if for some weird reason the resource is malformed) | 
|  |  | 
|  | ResourceBundle resources((char *)0, locale, status); | 
|  |  | 
|  | ResourceBundle dateTimePatterns = resources.get(fgDateTimePatternsTag, status); | 
|  | if (U_FAILURE(status)) return; | 
|  |  | 
|  | if (dateTimePatterns.getSize() <= kDateTime) | 
|  | { | 
|  | status = U_INVALID_FORMAT_ERROR; | 
|  | return; | 
|  | } | 
|  |  | 
|  | // create a symbols object from the locale | 
|  | fSymbols = new DateFormatSymbols(locale, status); | 
|  |  | 
|  | UnicodeString str; | 
|  |  | 
|  | // Move dateStyle from the range [0, 3] to [4, 7] if necessary | 
|  | //if (dateStyle >= 0 && dateStyle < DATE_OFFSET) dateStyle = (EStyle)(dateStyle + DATE_OFFSET); | 
|  |  | 
|  | // 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. | 
|  | if ((timeStyle != kNone) && | 
|  | (dateStyle != kNone)) | 
|  | { | 
|  | //  Object[] dateTimeArgs = { | 
|  | //     dateTimePatterns[timeStyle], dateTimePatterns[dateStyle] | 
|  | //  }; | 
|  | //  pattern = MessageFormat.format(dateTimePatterns[8], dateTimeArgs); | 
|  |  | 
|  | Formattable *timeDateArray = new Formattable[2]; | 
|  | //timeDateArray[0].setString(UnicodeString(dateTimePatterns[timeStyle])); | 
|  | //timeDateArray[1].setString(UnicodeString(dateTimePatterns[dateStyle])); | 
|  | timeDateArray[0].setString(dateTimePatterns.getStringEx(timeStyle, status)); | 
|  | timeDateArray[1].setString(dateTimePatterns.getStringEx(dateStyle, status)); | 
|  |  | 
|  | //MessageFormat::format(UnicodeString(dateTimePatterns[kDateTime]), timeDateArray, 2, fPattern, status); | 
|  | MessageFormat::format(dateTimePatterns.getStringEx(kDateTime, status), timeDateArray, 2, fPattern, status); | 
|  | delete [] timeDateArray; | 
|  | } | 
|  |  | 
|  | // if the pattern includes just time data or just date date, load the appropriate | 
|  | // pattern string from the resources | 
|  | //else if (timeStyle != kNone) fPattern = UnicodeString(dateTimePatterns[timeStyle]); | 
|  | //else if (dateStyle != kNone) fPattern = UnicodeString(dateTimePatterns[dateStyle]); | 
|  | else if (timeStyle != kNone) fPattern = dateTimePatterns.getStringEx(timeStyle, status); | 
|  | else if (dateStyle != kNone) fPattern = dateTimePatterns.getStringEx(dateStyle, status); | 
|  |  | 
|  | // and if it includes _neither_, that's an error | 
|  | else status = U_INVALID_FORMAT_ERROR; | 
|  |  | 
|  | // finally, finish initializing by creating a Calendar and a NumberFormat | 
|  | initialize(locale, status); | 
|  | } | 
|  |  | 
|  | //---------------------------------------------------------------------- | 
|  |  | 
|  | void | 
|  | SimpleDateFormat::initialize(const Locale& locale, | 
|  | UErrorCode& status) | 
|  | { | 
|  | if (U_FAILURE(status)) return; | 
|  |  | 
|  | // {sfb} should this be here? | 
|  | if (fSymbols->fZoneStringsColCount < 1) | 
|  | { | 
|  | status = U_INVALID_FORMAT_ERROR; // Check for bogus locale data | 
|  | return; | 
|  | } | 
|  |  | 
|  | // We don't need to check that the row count is >= 1, since all 2d arrays have at | 
|  | // least one row | 
|  | fCalendar = Calendar::createInstance(TimeZone::createDefault(), locale, status); | 
|  | fNumberFormat = NumberFormat::createInstance(locale, status); | 
|  | if (fNumberFormat != NULL && U_SUCCESS(status)) | 
|  | { | 
|  | // no matter what the locale's default number format looked like, we want | 
|  | // to modify it so that it doesn't use thousands separators, doesn't always | 
|  | // show the decimal point, and recognizes integers only when parsing | 
|  |  | 
|  | fNumberFormat->setGroupingUsed(FALSE); | 
|  | if (fNumberFormat->getDynamicClassID() == DecimalFormat::getStaticClassID()) | 
|  | ((DecimalFormat*)fNumberFormat)->setDecimalSeparatorAlwaysShown(FALSE); | 
|  | fNumberFormat->setParseIntegerOnly(TRUE); | 
|  | fNumberFormat->setMinimumFractionDigits(0); // To prevent "Jan 1.00, 1997.00" | 
|  |  | 
|  | initializeDefaultCentury(); | 
|  | } | 
|  | else if (U_SUCCESS(status)) | 
|  | { | 
|  | status = U_MISSING_RESOURCE_ERROR; | 
|  | } | 
|  | } | 
|  |  | 
|  | /* Initialize the fields we use to disambiguate ambiguous years. Separate | 
|  | * so we can call it from readObject(). | 
|  | */ | 
|  | void SimpleDateFormat::initializeDefaultCentury() | 
|  | { | 
|  | fDefaultCenturyStart        = internalGetDefaultCenturyStart(); | 
|  | fDefaultCenturyStartYear    = internalGetDefaultCenturyStartYear(); | 
|  |  | 
|  | UErrorCode status = U_ZERO_ERROR; | 
|  | fCalendar->setTime(fDefaultCenturyStart, status); | 
|  | // {sfb} throw away error | 
|  | } | 
|  |  | 
|  | /* Define one-century window into which to disambiguate dates using | 
|  | * two-digit years. Make public in JDK 1.2. | 
|  | */ | 
|  | void SimpleDateFormat::parseAmbiguousDatesAsAfter(UDate startDate, UErrorCode& status) | 
|  | { | 
|  | if(U_FAILURE(status)) | 
|  | return; | 
|  |  | 
|  | fCalendar->setTime(startDate, status); | 
|  | if(U_SUCCESS(status)) { | 
|  | fDefaultCenturyStart = startDate; | 
|  | fDefaultCenturyStartYear = fCalendar->get(Calendar::YEAR, status); | 
|  | } | 
|  | } | 
|  |  | 
|  | //---------------------------------------------------------------------- | 
|  |  | 
|  | UnicodeString& | 
|  | SimpleDateFormat::format(UDate date, UnicodeString& toAppendTo, FieldPosition& pos) const | 
|  | { | 
|  | if (fCalendar == 0) { | 
|  | return toAppendTo; | 
|  | } | 
|  |  | 
|  | UErrorCode status = U_ZERO_ERROR; | 
|  | pos.setBeginIndex(0); | 
|  | pos.setEndIndex(0); | 
|  |  | 
|  | // load up our Calendar with the date/time we're formatting (the subroutines of this | 
|  | // function pick it up from there, since they need it anyway to split the value | 
|  | // into fields) | 
|  | fCalendar->setTime(date, status); | 
|  |  | 
|  | UBool inQuote = FALSE; | 
|  | UChar prevCh = 0; | 
|  | int32_t count = 0; | 
|  | UnicodeString str; | 
|  |  | 
|  | // loop through the pattern string character by character | 
|  | for (int32_t i = 0; i < fPattern.length() && U_SUCCESS(status); ++i) { | 
|  | UChar ch = fPattern[i]; | 
|  |  | 
|  | // Use subFormat() to format a repeated pattern character | 
|  | // when a different pattern or non-pattern character is seen | 
|  | if (ch != prevCh && count > 0) { | 
|  | toAppendTo += subFormat(str, prevCh, count, toAppendTo.length(), pos, status); | 
|  | count = 0; | 
|  | } | 
|  | if (ch == 0x0027 /*'\''*/) { | 
|  | // Consecutive single quotes are a single quote literal, | 
|  | // either outside of quotes or between quotes | 
|  | if ((i+1) < fPattern.length() && fPattern[i+1] == 0x0027 /*'\''*/) { | 
|  | toAppendTo += (UChar)0x0027 /*'\''*/; | 
|  | ++i; | 
|  | } else { | 
|  | inQuote = ! inQuote; | 
|  | } | 
|  | } | 
|  | else if ( ! inQuote && ((ch >= 0x0061 /*'a'*/ && ch <= 0x007A /*'z'*/) | 
|  | || (ch >= 0x0041 /*'A'*/ && ch <= 0x005A /*'Z'*/))) { | 
|  | // ch is a date-time pattern character to be interpreted | 
|  | // by subFormat(); count the number of times it is repeated | 
|  | prevCh = ch; | 
|  | ++count; | 
|  | } | 
|  | else { | 
|  | // Append quoted characters and unquoted non-pattern characters | 
|  | toAppendTo += ch; | 
|  | } | 
|  | } | 
|  |  | 
|  | // Format the last item in the pattern, if any | 
|  | if (count > 0) { | 
|  | toAppendTo += subFormat(str, prevCh, count, toAppendTo.length(), pos, status); | 
|  | } | 
|  |  | 
|  | // and if something failed (e.g., an invalid format character), reset our FieldPosition | 
|  | // to (0, 0) to show that | 
|  | // {sfb} look at this later- are these being set correctly? | 
|  | if (U_FAILURE(status)) { | 
|  | pos.setBeginIndex(0); | 
|  | pos.setEndIndex(0); | 
|  | } | 
|  |  | 
|  | return toAppendTo; | 
|  | } | 
|  |  | 
|  | UnicodeString& | 
|  | SimpleDateFormat::format(const Formattable& obj, | 
|  | UnicodeString& toAppendTo, | 
|  | FieldPosition& pos, | 
|  | UErrorCode& status) const | 
|  | { | 
|  | // this is just here to get around the hiding problem | 
|  | // (the previous format() override would hide the version of | 
|  | // format() on DateFormat that this function correspond to, so we | 
|  | // have to redefine it here) | 
|  | return DateFormat::format(obj, toAppendTo, pos, status); | 
|  | } | 
|  |  | 
|  | //---------------------------------------------------------------------- | 
|  |  | 
|  | // Map index into pattern character string to Calendar field number. | 
|  | const Calendar::EDateFields | 
|  | SimpleDateFormat::fgPatternIndexToCalendarField[] = | 
|  | { | 
|  | Calendar::ERA, Calendar::YEAR, Calendar::MONTH, Calendar::DATE, | 
|  | Calendar::HOUR_OF_DAY, Calendar::HOUR_OF_DAY, Calendar::MINUTE, | 
|  | Calendar::SECOND, Calendar::MILLISECOND, Calendar::DAY_OF_WEEK, | 
|  | Calendar::DAY_OF_YEAR, Calendar::DAY_OF_WEEK_IN_MONTH, | 
|  | Calendar::WEEK_OF_YEAR, Calendar::WEEK_OF_MONTH, | 
|  | Calendar::AM_PM, Calendar::HOUR, Calendar::HOUR, Calendar::ZONE_OFFSET, | 
|  | Calendar::YEAR_WOY, Calendar::DOW_LOCAL | 
|  | }; | 
|  |  | 
|  | // Map index into pattern character string to DateFormat field number | 
|  | const DateFormat::EField | 
|  | SimpleDateFormat::fgPatternIndexToDateFormatField[] = { | 
|  | DateFormat::kEraField, DateFormat::kYearField, DateFormat::kMonthField, | 
|  | DateFormat::kDateField, DateFormat::kHourOfDay1Field, | 
|  | DateFormat::kHourOfDay0Field, DateFormat::kMinuteField, | 
|  | DateFormat::kSecondField, DateFormat::kMillisecondField, | 
|  | DateFormat::kDayOfWeekField, DateFormat::kDayOfYearField, | 
|  | DateFormat::kDayOfWeekInMonthField, DateFormat::kWeekOfYearField, | 
|  | DateFormat::kWeekOfMonthField, DateFormat::kAmPmField, | 
|  | DateFormat::kHour1Field, DateFormat::kHour0Field, | 
|  | DateFormat::kTimezoneField, DateFormat::kYearWOYField, | 
|  | DateFormat::kDOWLocalField | 
|  | }; | 
|  |  | 
|  |  | 
|  | //---------------------------------------------------------------------- | 
|  |  | 
|  | UnicodeString& | 
|  | SimpleDateFormat::subFormat(UnicodeString& result, | 
|  | UChar ch, | 
|  | int32_t count, | 
|  | int32_t beginOffset, | 
|  | FieldPosition& pos, | 
|  | UErrorCode& status) const | 
|  | { | 
|  | // this function gets called by format() to produce the appropriate substitution | 
|  | // text for an individual pattern symbol (e.g., "HH" or "yyyy") | 
|  |  | 
|  | UChar *patternCharPtr = u_strchr(DateFormatSymbols::getPatternUChars(), ch); | 
|  | EField patternCharIndex; | 
|  | int32_t maxIntCount = 10; | 
|  | UnicodeString str; // Scratch | 
|  |  | 
|  | result.remove(); | 
|  |  | 
|  | // if the pattern character is unrecognized, signal an error and dump out | 
|  | if (patternCharPtr == NULL) | 
|  | { | 
|  | status = U_INVALID_FORMAT_ERROR; | 
|  | return result; | 
|  | } | 
|  |  | 
|  | patternCharIndex = (EField)(patternCharPtr - DateFormatSymbols::getPatternUChars()); | 
|  | Calendar::EDateFields field = fgPatternIndexToCalendarField[patternCharIndex]; | 
|  | int32_t value = fCalendar->get(field, status); | 
|  | if (U_FAILURE(status)) return result; | 
|  |  | 
|  | switch (patternCharIndex) { | 
|  |  | 
|  | // for any "G" symbol, write out the appropriate era string | 
|  | case kEraField: | 
|  | result = fSymbols->fEras[value]; | 
|  | break; | 
|  |  | 
|  | // for "yyyy", write out the whole year; for "yy", write out the last 2 digits | 
|  | case kYearField: | 
|  | case kYearWOYField: | 
|  | if (count >= 4) | 
|  | zeroPaddingNumber(result, value, 4, maxIntCount); | 
|  | else | 
|  | zeroPaddingNumber(result, value, 2, 2); | 
|  | break; | 
|  |  | 
|  | // for "MMMM", write out the whole month name, for "MMM", write out the month | 
|  | // abbreviation, for "M" or "MM", write out the month as a number with the | 
|  | // appropriate number of digits | 
|  | case kMonthField: | 
|  | if (count >= 4) | 
|  | result = fSymbols->fMonths[value]; | 
|  | else if (count == 3) | 
|  | result = fSymbols->fShortMonths[value]; | 
|  | else | 
|  | zeroPaddingNumber(result, value + 1, count, maxIntCount); | 
|  | break; | 
|  |  | 
|  | // for "k" and "kk", write out the hour, adjusting midnight to appear as "24" | 
|  | case kHourOfDay1Field: | 
|  | if (value == 0) | 
|  | zeroPaddingNumber(result, fCalendar->getMaximum(Calendar::HOUR_OF_DAY) + 1, count, maxIntCount); | 
|  | else | 
|  | zeroPaddingNumber(result, value, count, maxIntCount); | 
|  | break; | 
|  |  | 
|  | // for "SS" and "S", we want to truncate digits so that you still see the MOST | 
|  | // significant digits rather than the LEAST (as is the case with the year) | 
|  | case kMillisecondField: | 
|  | if (count > 3) | 
|  | count = 3; | 
|  | else if (count == 2) | 
|  | value = value / 10; | 
|  | else if (count == 1) | 
|  | value = value / 100; | 
|  | zeroPaddingNumber(result, value, count, maxIntCount); | 
|  | break; | 
|  |  | 
|  | // for "EEEE", write out the day-of-the-week name; otherwise, use the abbreviation | 
|  | case kDayOfWeekField: | 
|  | if (count >= 4) | 
|  | result = fSymbols->fWeekdays[value]; | 
|  | else | 
|  | result = fSymbols->fShortWeekdays[value]; | 
|  | break; | 
|  |  | 
|  | // for and "a" symbol, write out the whole AM/PM string | 
|  | case kAmPmField: | 
|  | result = fSymbols->fAmPms[value]; | 
|  | break; | 
|  |  | 
|  | // for "h" and "hh", write out the hour, adjusting noon and midnight to show up | 
|  | // as "12" | 
|  | case kHour1Field: | 
|  | if (value == 0) | 
|  | zeroPaddingNumber(result, fCalendar->getLeastMaximum(Calendar::HOUR) + 1, count, maxIntCount); | 
|  | else | 
|  | zeroPaddingNumber(result, value, count, maxIntCount); | 
|  | break; | 
|  |  | 
|  | // for the "z" symbols, we have to check our time zone data first.  If we have a | 
|  | // localized name for the time zone, then "zzzz" is the whole name and anything | 
|  | // shorter is the abbreviation (we also have to check for daylight savings time | 
|  | // since the name will be different).  If we don't have a localized time zone name, | 
|  | // then the time zone shows up as "GMT+hh:mm" or "GMT-hh:mm" (where "hh:mm" is the | 
|  | // offset from GMT) regardless of how many z's were in the pattern symbol | 
|  | case kTimezoneField: { | 
|  | int32_t zoneIndex = fSymbols->getZoneIndex(fCalendar->getTimeZone().getID(str)); | 
|  | if (zoneIndex == -1) { | 
|  | UnicodeString zoneString; | 
|  |  | 
|  | value = fCalendar->get(Calendar::ZONE_OFFSET, status) + | 
|  | fCalendar->get(Calendar::DST_OFFSET, status); | 
|  |  | 
|  | if (value < 0) { | 
|  | zoneString += fgGmtMinus; | 
|  | value = -value; // suppress the '-' sign for text display. | 
|  | } | 
|  | else | 
|  | zoneString += fgGmtPlus; | 
|  |  | 
|  | zoneString += zeroPaddingNumber(str, (int32_t)(value/U_MILLIS_PER_HOUR), 2, 2); | 
|  | zoneString += (UChar)0x003A /*':'*/; | 
|  | zoneString += zeroPaddingNumber(str, (int32_t)((value%U_MILLIS_PER_HOUR)/U_MILLIS_PER_MINUTE), 2, 2); | 
|  |  | 
|  | result = zoneString; | 
|  | } | 
|  | else if (fCalendar->get(Calendar::DST_OFFSET, status) != 0) { | 
|  | if (count >= 4) | 
|  | result = fSymbols->fZoneStrings[zoneIndex][3]; | 
|  | else | 
|  | result = fSymbols->fZoneStrings[zoneIndex][4]; | 
|  | } | 
|  | else { | 
|  | if (count >= 4) | 
|  | result = fSymbols->fZoneStrings[zoneIndex][1]; | 
|  | else | 
|  | result = fSymbols->fZoneStrings[zoneIndex][2]; | 
|  | } | 
|  | } | 
|  | break; | 
|  |  | 
|  | // all of the other pattern symbols can be formatted as simple numbers with | 
|  | // appropriate zero padding | 
|  | default: | 
|  | // case kDateField: | 
|  | // case kHourOfDay0Field: | 
|  | // case kMinuteField: | 
|  | // case kSecondField: | 
|  | // case kDayOfYearField: | 
|  | // case kDayOfWeekInMonthField: | 
|  | // case kWeekOfYearField: | 
|  | // case kWeekOfMonthField: | 
|  | // case kHour0Field: | 
|  | // case kDOWLocalField: | 
|  | zeroPaddingNumber(result, value, count, maxIntCount); | 
|  | break; | 
|  | } | 
|  |  | 
|  | // if the field we're formatting is the one the FieldPosition says it's interested | 
|  | // in, fill in the FieldPosition with this field's positions | 
|  | if (pos.getField() == fgPatternIndexToDateFormatField[patternCharIndex]) { | 
|  | if (pos.getBeginIndex() == 0 && pos.getEndIndex() == 0) { | 
|  | pos.setBeginIndex(beginOffset); | 
|  | pos.setEndIndex(beginOffset + result.length()); | 
|  | } | 
|  | } | 
|  |  | 
|  | return result; | 
|  | } | 
|  |  | 
|  | //---------------------------------------------------------------------- | 
|  |  | 
|  | UnicodeString& | 
|  | SimpleDateFormat::zeroPaddingNumber(UnicodeString& result, int32_t value, int32_t minDigits, int32_t maxDigits) const | 
|  | { | 
|  | FieldPosition pos(0); | 
|  |  | 
|  | result.remove(); | 
|  | fNumberFormat->setMinimumIntegerDigits(minDigits); | 
|  | fNumberFormat->setMaximumIntegerDigits(maxDigits); | 
|  | return fNumberFormat->format(value, result, pos);  // 3rd arg is there to speed up processing | 
|  | } | 
|  |  | 
|  | //---------------------------------------------------------------------- | 
|  |  | 
|  | // {sfb} removed | 
|  | /* | 
|  | // this function will dump output to the console on a debug build when there's a parse error | 
|  | #ifdef _DEBUG | 
|  | void chk(ParsePosition& val, UChar ch, ParsePosition& start, int32_t count) | 
|  | { | 
|  | if (val.getIndex() < 0) | 
|  | { | 
|  | cout << "[Parse failure on '" << (char)ch << "' x " << dec << count << " @ " << start.getIndex() << ']'; | 
|  | } | 
|  | } | 
|  | #else | 
|  | inline void chk(ParsePosition& val, UChar ch, ParsePosition& start, int32_t count) | 
|  | { | 
|  | } | 
|  | #endif | 
|  |  | 
|  | inline Date | 
|  | parseFailureResult(ParsePosition& pos, ParsePosition& oldStart, ParsePosition& failurePos) | 
|  | { | 
|  | // Note: The C++ version currently supports the notion of returning zero | 
|  | // with a non-zero parse position, but only if this format is lenient. | 
|  | // The returned position in this case is the first un-parseable character. | 
|  | // This is useful, but is not present in the Java version, and causes a | 
|  | // DateFormat test to fail. | 
|  |  | 
|  | // For now, I am removing this function.  It can be restored later. | 
|  |  | 
|  | // if (!isLenient()) pos = oldStart; | 
|  | // else { pos = failurePos.getIndex(); if (pos.getIndex() < 0) pos = -pos.getIndex(); }; | 
|  | pos = oldStart; | 
|  | return 0; | 
|  | } | 
|  | */ | 
|  |  | 
|  | UDate | 
|  | SimpleDateFormat::parse(const UnicodeString& text, ParsePosition& pos) const | 
|  | { | 
|  | int32_t start = pos.getIndex(); | 
|  | int32_t oldStart = start; | 
|  | UBool ambiguousYear[] = { FALSE }; | 
|  |  | 
|  | fCalendar->clear(); | 
|  |  | 
|  | UBool inQuote = FALSE; | 
|  | UChar prevCh = 0; | 
|  | int32_t count = 0; | 
|  | int32_t interQuoteCount = 1; // Number of chars between quotes | 
|  |  | 
|  | // loop through the pattern string character by character, using it to control how | 
|  | // we match characters in the input | 
|  | for (int32_t i = 0; i < fPattern.length();++i) { | 
|  | UChar ch = fPattern[i]; | 
|  |  | 
|  | // if we're inside a quoted string, match characters exactly until we hit | 
|  | // another single quote (two single quotes in a row match one single quote | 
|  | // in the input) | 
|  | if (inQuote) | 
|  | { | 
|  | if (ch == 0x0027 /*'\''*/) | 
|  | { | 
|  | // ends with 2nd single quote | 
|  | inQuote = FALSE; | 
|  | // two consecutive quotes outside a quote means we have | 
|  | // a quote literal we need to match. | 
|  | if (count == 0) | 
|  | { | 
|  | if(start > text.length() || ch != text[start]) | 
|  | { | 
|  | pos.setIndex(oldStart); | 
|  | pos.setErrorIndex(start); | 
|  | // {sfb} what is the correct Date for failure? | 
|  | return 0; | 
|  | } | 
|  | ++start; | 
|  | } | 
|  | count = 0; | 
|  | interQuoteCount = 0; | 
|  | } | 
|  | else | 
|  | { | 
|  | // pattern uses text following from 1st single quote. | 
|  | if (start >= text.length() || ch != text[start]) { | 
|  | // Check for cases like: 'at' in pattern vs "xt" | 
|  | // in time text, where 'a' doesn't match with 'x'. | 
|  | // If fail to match, return null. | 
|  | pos.setIndex(oldStart); // left unchanged | 
|  | pos.setErrorIndex(start); | 
|  | // {sfb} what is correct Date for failure? | 
|  | return 0; | 
|  | } | 
|  | ++count; | 
|  | ++start; | 
|  | } | 
|  | } | 
|  |  | 
|  | // if we're not inside a quoted string... | 
|  | else { | 
|  |  | 
|  | // ...a quote mark puts us into a quoted string (and we parse any pending | 
|  | // pattern symbols) | 
|  | if (ch == 0x0027 /*'\''*/) { | 
|  | inQuote = TRUE; | 
|  | if (count > 0) | 
|  | { | 
|  | int32_t startOffset = start; | 
|  | start = subParse(text, start, prevCh, count, FALSE, ambiguousYear); | 
|  | if ( start < 0 ) { | 
|  | pos.setErrorIndex(startOffset); | 
|  | pos.setIndex(oldStart); | 
|  | // {sfb} correct Date | 
|  | return 0; | 
|  | } | 
|  | count = 0; | 
|  | } | 
|  |  | 
|  | if (interQuoteCount == 0) | 
|  | { | 
|  | // This indicates two consecutive quotes inside a quote, | 
|  | // for example, 'o''clock'.  We need to parse this as | 
|  | // representing a single quote within the quote. | 
|  | int32_t startOffset = start; | 
|  | if (start >= text.length() ||  ch != text[start]) | 
|  | { | 
|  | pos.setErrorIndex(startOffset); | 
|  | pos.setIndex(oldStart); | 
|  | // {sfb} correct Date | 
|  | return 0; | 
|  | } | 
|  | ++start; | 
|  | count = 1; // Make it look like we never left | 
|  | } | 
|  | } | 
|  |  | 
|  | // if we're on a letter, collect copies of the same letter to determine | 
|  | // the whole parse symbol.  when we hit a different character, parse the | 
|  | // input based on the resulting symbol | 
|  | else if ((ch >= 0x0061 /*'a'*/ && ch <= 0x007A /*'z'*/) | 
|  | || (ch >= 0x0041 /*'A'*/ && ch <= 0x005A /*'Z'*/)) | 
|  | { | 
|  | // ch is a date-time pattern | 
|  | if (ch != prevCh && count > 0) // e.g., yyyyMMdd | 
|  | { | 
|  | int32_t startOffset = start; | 
|  | // This is the only case where we pass in 'true' for | 
|  | // obeyCount.  That's because the next field directly | 
|  | // abuts this one, so we have to use the count to know when | 
|  | // to stop parsing. [LIU] | 
|  | start = subParse(text, start, prevCh, count, TRUE, ambiguousYear); | 
|  | if (start < 0) { | 
|  | pos.setErrorIndex(startOffset); | 
|  | pos.setIndex(oldStart); | 
|  | // {sfb} correct Date | 
|  | return 0; | 
|  | } | 
|  | prevCh = ch; | 
|  | count = 1; | 
|  | } | 
|  | else { | 
|  | if (ch != prevCh) | 
|  | prevCh = ch; | 
|  | count++; | 
|  | } | 
|  | } | 
|  |  | 
|  | // if we're on a non-letter, parse based on any pending pattern symbols | 
|  | else if (count > 0) | 
|  | { | 
|  | // handle cases like: MM-dd-yy, HH:mm:ss, or yyyy MM dd, | 
|  | // where ch = '-', ':', or ' ', repectively. | 
|  | int32_t startOffset = start; | 
|  | start = subParse( text, start, prevCh, count, FALSE, ambiguousYear); | 
|  | if ( start < 0 ) { | 
|  | pos.setErrorIndex(startOffset); | 
|  | pos.setIndex(oldStart); | 
|  | // {sfb} correct Date? | 
|  | return 0; | 
|  | } | 
|  | if (start >= text.length() || ch != text[start]) { | 
|  | // handle cases like: 'MMMM dd' in pattern vs. "janx20" | 
|  | // in time text, where ' ' doesn't match with 'x'. | 
|  | pos.setErrorIndex(start); | 
|  | pos.setIndex(oldStart); | 
|  | // {sfb} correct Date? | 
|  | return 0; | 
|  | } | 
|  | start++; | 
|  | count = 0; | 
|  | prevCh = 0; | 
|  | } | 
|  |  | 
|  | // otherwise, match characters exactly | 
|  | else | 
|  | { | 
|  | if (start >= text.length() || ch != text[start]) { | 
|  | // handle cases like: 'MMMM   dd' in pattern vs. | 
|  | // "jan,,,20" in time text, where "   " doesn't | 
|  | // match with ",,,". | 
|  |  | 
|  | pos.setErrorIndex(start); | 
|  | pos.setIndex(oldStart); | 
|  | // {sfb} correct Date? | 
|  | return 0; | 
|  | } | 
|  | start++; | 
|  | } | 
|  |  | 
|  | ++interQuoteCount; | 
|  | } | 
|  | } | 
|  |  | 
|  | // if we still have a pending pattern symbol after we're done looping through | 
|  | // characters in the pattern string, parse the input based on the final pending | 
|  | // pattern symbol | 
|  | if (count > 0) | 
|  | { | 
|  | int32_t startOffset = start; | 
|  | start = subParse(text, start, prevCh, count, FALSE, ambiguousYear); | 
|  | if ( start < 0 ) { | 
|  | pos.setIndex(oldStart); | 
|  | pos.setErrorIndex(startOffset); | 
|  | // {sfb} correct Date?> | 
|  | return 0; | 
|  | } | 
|  | } | 
|  |  | 
|  | // At this point the fields of Calendar have been set.  Calendar | 
|  | // will fill in default values for missing fields when the time | 
|  | // is computed. | 
|  |  | 
|  | pos.setIndex(start); | 
|  |  | 
|  | // This part is a problem:  When we call parsedDate.after, we compute the time. | 
|  | // Take the date April 3 2004 at 2:30 am.  When this is first set up, the year | 
|  | // will be wrong if we're parsing a 2-digit year pattern.  It will be 1904. | 
|  | // April 3 1904 is a Sunday (unlike 2004) so it is the DST onset day.  2:30 am | 
|  | // is therefore an "impossible" time, since the time goes from 1:59 to 3:00 am | 
|  | // on that day.  It is therefore parsed out to fields as 3:30 am.  Then we | 
|  | // add 100 years, and get April 3 2004 at 3:30 am.  Note that April 3 2004 is | 
|  | // a Saturday, so it can have a 2:30 am -- and it should. [LIU] | 
|  | /* | 
|  | UDate parsedDate = calendar.getTime(); | 
|  | if( ambiguousYear[0] && !parsedDate.after(fDefaultCenturyStart) ) { | 
|  | calendar.add(Calendar.YEAR, 100); | 
|  | parsedDate = calendar.getTime(); | 
|  | } | 
|  | */ | 
|  | // Because of the above condition, save off the fields in case we need to readjust. | 
|  | // The procedure we use here is not particularly efficient, but there is no other | 
|  | // way to do this given the API restrictions present in Calendar.  We minimize | 
|  | // inefficiency by only performing this computation when it might apply, that is, | 
|  | // when the two-digit year is equal to the start year, and thus might fall at the | 
|  | // front or the back of the default century.  This only works because we adjust | 
|  | // the year correctly to start with in other cases -- see subParse(). | 
|  | UErrorCode status = U_ZERO_ERROR; | 
|  | UDate parsedDate; | 
|  | if (ambiguousYear[0]) // If this is true then the two-digit year == the default start year | 
|  | { | 
|  | // We need a copy of the fields, and we need to avoid triggering a call to | 
|  | // complete(), which will recalculate the fields.  Since we can't access | 
|  | // the fields[] array in Calendar, we clone the entire object.  This will | 
|  | // stop working if Calendar.clone() is ever rewritten to call complete(). | 
|  | Calendar *savedCalendar = fCalendar->clone(); | 
|  | parsedDate = fCalendar->getTime(status); | 
|  | // {sfb} check internalGetDefaultCenturyStart | 
|  | if (parsedDate < internalGetDefaultCenturyStart()) | 
|  | { | 
|  | // We can't use add here because that does a complete() first. | 
|  | savedCalendar->set(Calendar::YEAR, internalGetDefaultCenturyStartYear() + 100); | 
|  | parsedDate = savedCalendar->getTime(status); | 
|  | } | 
|  | delete savedCalendar; | 
|  | } | 
|  | else parsedDate = fCalendar->getTime(status); | 
|  |  | 
|  | // If any Calendar calls failed, we pretend that we | 
|  | // couldn't parse the string, when in reality this isn't quite accurate-- | 
|  | // we did parse it; the Calendar calls just failed. | 
|  | if (U_FAILURE(status)) { | 
|  | pos.setErrorIndex(start); | 
|  | pos.setIndex(oldStart); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | return parsedDate; | 
|  | } | 
|  |  | 
|  | UDate | 
|  | SimpleDateFormat::parse(const UnicodeString& text, UErrorCode& status) const | 
|  | { | 
|  | // redefined here because the other parse() function hides this function's | 
|  | // ounterpart on DateFormat | 
|  | return DateFormat::parse(text, status); | 
|  | } | 
|  | //---------------------------------------------------------------------- | 
|  |  | 
|  | int32_t SimpleDateFormat::matchString(const UnicodeString& text, | 
|  | int32_t start, | 
|  | Calendar::EDateFields field, | 
|  | const UnicodeString* data, | 
|  | int32_t dataCount) const | 
|  | { | 
|  | int32_t i = 0; | 
|  | int32_t count = dataCount; | 
|  |  | 
|  | if (field == Calendar::DAY_OF_WEEK) i = 1; | 
|  |  | 
|  | // There may be multiple strings in the data[] array which begin with | 
|  | // the same prefix (e.g., Cerven and Cervenec (June and July) in Czech). | 
|  | // We keep track of the longest match, and return that.  Note that this | 
|  | // unfortunately requires us to test all array elements. | 
|  | int32_t bestMatchLength = 0, bestMatch = -1; | 
|  |  | 
|  | // {sfb} kludge to support case-insensitive comparison | 
|  | UnicodeString lcaseText(text); | 
|  | lcaseText.toLower(); | 
|  |  | 
|  | for (; i < count; ++i) | 
|  | { | 
|  | int32_t length = data[i].length(); | 
|  | // Always compare if we have no match yet; otherwise only compare | 
|  | // against potentially better matches (longer strings). | 
|  |  | 
|  | UnicodeString lcase(data[i]); | 
|  | lcase.toLower(); | 
|  |  | 
|  | if (length > bestMatchLength && (lcaseText.compareBetween(start, start + length, lcase, 0, length)) == 0) | 
|  | { | 
|  | bestMatch = i; | 
|  | bestMatchLength = length; | 
|  | } | 
|  | } | 
|  | if (bestMatch >= 0) | 
|  | { | 
|  | fCalendar->set(field, bestMatch); | 
|  | return start + bestMatchLength; | 
|  | } | 
|  |  | 
|  | return -start; | 
|  | } | 
|  |  | 
|  | //---------------------------------------------------------------------- | 
|  |  | 
|  | void | 
|  | SimpleDateFormat::set2DigitYearStart(UDate d, UErrorCode& status) | 
|  | { | 
|  | parseAmbiguousDatesAsAfter(d, status); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Parse the given text, at the given position, as a numeric value, using | 
|  | * this objects fNumberFormat. Return the corresponding long value in the | 
|  | * fill-in parameter 'value'. If the parse fails, this method leaves pos | 
|  | * unchanged and returns FALSE; otherwise it advances pos and | 
|  | * returns TRUE. | 
|  | */ | 
|  | // {sfb} removed | 
|  | /* | 
|  | UBool | 
|  | SimpleDateFormat::subParseLong(const UnicodeString& text, ParsePosition& pos, int32_t& value) const | 
|  | { | 
|  | Formattable parseResult; | 
|  | ParsePosition posSave = pos; | 
|  | fNumberFormat->parse(text, parseResult, pos); | 
|  | if (pos != posSave && parseResult.getType() == Formattable::kLong) | 
|  | { | 
|  | value = parseResult.getLong(); | 
|  | return TRUE; | 
|  | } | 
|  | pos = posSave; | 
|  | return FALSE; | 
|  | } | 
|  | */ | 
|  |  | 
|  | /** | 
|  | * Private member function that converts the parsed date strings into | 
|  | * timeFields. Returns -start (for ParsePosition) if failed. | 
|  | * @param text the time text to be parsed. | 
|  | * @param start where to start parsing. | 
|  | * @param ch the pattern character for the date field text to be parsed. | 
|  | * @param count the count of a pattern character. | 
|  | * @return the new start position if matching succeeded; a negative number | 
|  | * indicating matching failure, otherwise. | 
|  | */ | 
|  | int32_t SimpleDateFormat::subParse(const UnicodeString& text, int32_t& start, UChar ch, int32_t count, | 
|  | UBool obeyCount, UBool ambiguousYear[]) const | 
|  | { | 
|  | Formattable number; | 
|  | int32_t value = 0; | 
|  | int32_t i; | 
|  | ParsePosition pos(0); | 
|  | int32_t patternCharIndex; | 
|  | UChar *patternCharPtr = u_strchr(DateFormatSymbols::getPatternUChars(), ch); | 
|  |  | 
|  | if (patternCharPtr == NULL) { | 
|  | return -start; | 
|  | } | 
|  |  | 
|  | patternCharIndex = (EField)(patternCharPtr - DateFormatSymbols::getPatternUChars()); | 
|  | pos.setIndex(start); | 
|  |  | 
|  | Calendar::EDateFields field = fgPatternIndexToCalendarField[patternCharIndex]; | 
|  |  | 
|  | // If there are any spaces here, skip over them.  If we hit the end | 
|  | // of the string, then fail. | 
|  | for (;;) { | 
|  | if (pos.getIndex() >= text.length()) | 
|  | return -start; | 
|  | UChar c = text[pos.getIndex()]; | 
|  | if (c != 0x0020 /*' '*/ && c != 0x0009 /*'\t'*/) | 
|  | break; | 
|  | pos.setIndex(pos.getIndex() + 1); | 
|  | } | 
|  |  | 
|  | // We handle a few special cases here where we need to parse | 
|  | // a number value.  We handle further, more generic cases below.  We need | 
|  | // to handle some of them here because some fields require extra processing on | 
|  | // the parsed value. | 
|  | if (patternCharIndex == kHourOfDay1Field /*HOUR_OF_DAY1_FIELD*/ || | 
|  | patternCharIndex == kHour1Field /*HOUR1_FIELD*/ || | 
|  | (patternCharIndex == kMonthField /*MONTH_FIELD*/ && count <= 2) || | 
|  | patternCharIndex == kYearField /*YEAR*/ || | 
|  | patternCharIndex == kYearWOYField) | 
|  | { | 
|  | int32_t parseStart = pos.getIndex(); // WORK AROUND BUG IN NUMBER FORMAT IN 1.2B3 | 
|  | // It would be good to unify this with the obeyCount logic below, | 
|  | // but that's going to be difficult. | 
|  | if (obeyCount) | 
|  | { | 
|  | if ((start+count) > text.length()) | 
|  | return -start; | 
|  | UnicodeString temp; | 
|  | text.extractBetween(0, start + count, temp); | 
|  | fNumberFormat->parse(temp, number, pos); | 
|  | } | 
|  | else | 
|  | fNumberFormat->parse(text, number, pos); | 
|  | if (pos.getIndex() == parseStart) | 
|  | // WORK AROUND BUG IN NUMBER FORMAT IN 1.2B3 | 
|  | return -start; | 
|  | value = number.getLong(); | 
|  | } | 
|  |  | 
|  | switch (patternCharIndex) { | 
|  | case kEraField: | 
|  | return matchString(text, start, Calendar::ERA, fSymbols->fEras, fSymbols->fErasCount); | 
|  | case kYearField: | 
|  | // If there are 3 or more YEAR pattern characters, this indicates | 
|  | // that the year value is to be treated literally, without any | 
|  | // two-digit year adjustments (e.g., from "01" to 2001).  Otherwise | 
|  | // we made adjustments to place the 2-digit year in the proper | 
|  | // century, for parsed strings from "00" to "99".  Any other string | 
|  | // is treated literally:  "2250", "-1", "1", "002". | 
|  | if (count <= 2 && (pos.getIndex() - start) == 2 | 
|  | && Unicode::isDigit(text.charAt(start)) | 
|  | && Unicode::isDigit(text.charAt(start+1))) | 
|  | { | 
|  | // Assume for example that the defaultCenturyStart is 6/18/1903. | 
|  | // This means that two-digit years will be forced into the range | 
|  | // 6/18/1903 to 6/17/2003.  As a result, years 00, 01, and 02 | 
|  | // correspond to 2000, 2001, and 2002.  Years 04, 05, etc. correspond | 
|  | // to 1904, 1905, etc.  If the year is 03, then it is 2003 if the | 
|  | // other fields specify a date before 6/18, or 1903 if they specify a | 
|  | // date afterwards.  As a result, 03 is an ambiguous year.  All other | 
|  | // two-digit years are unambiguous. | 
|  | int32_t ambiguousTwoDigitYear = fDefaultCenturyStartYear % 100; | 
|  | ambiguousYear[0] = (value == ambiguousTwoDigitYear); | 
|  | value += (fDefaultCenturyStartYear/100)*100 + | 
|  | (value < ambiguousTwoDigitYear ? 100 : 0); | 
|  | } | 
|  | fCalendar->set(Calendar::YEAR, value); | 
|  | return pos.getIndex(); | 
|  | case kYearWOYField: | 
|  | // Comment is the same as for kYearFiels - look above | 
|  | if (count <= 2 && (pos.getIndex() - start) == 2 | 
|  | && Unicode::isDigit(text.charAt(start)) | 
|  | && Unicode::isDigit(text.charAt(start+1))) | 
|  | { | 
|  | int32_t ambiguousTwoDigitYear = fDefaultCenturyStartYear % 100; | 
|  | ambiguousYear[0] = (value == ambiguousTwoDigitYear); | 
|  | value += (fDefaultCenturyStartYear/100)*100 + | 
|  | (value < ambiguousTwoDigitYear ? 100 : 0); | 
|  | } | 
|  | fCalendar->set(Calendar::YEAR_WOY, value); | 
|  | return pos.getIndex(); | 
|  | case kMonthField: | 
|  | if (count <= 2) // i.e., M or MM. | 
|  | { | 
|  | // Don't want to parse the month if it is a string | 
|  | // while pattern uses numeric style: M or MM. | 
|  | // [We computed 'value' above.] | 
|  | fCalendar->set(Calendar::MONTH, value - 1); | 
|  | return pos.getIndex(); | 
|  | } | 
|  | else | 
|  | { | 
|  | // count >= 3 // i.e., MMM or MMMM | 
|  | // Want to be able to parse both short and long forms. | 
|  | // Try count == 4 first: | 
|  | int32_t newStart = 0; | 
|  | if ((newStart = matchString(text, start, Calendar::MONTH, | 
|  | fSymbols->fMonths, fSymbols->fMonthsCount)) > 0) | 
|  | return newStart; | 
|  | else // count == 4 failed, now try count == 3 | 
|  | return matchString(text, start, Calendar::MONTH, | 
|  | fSymbols->fShortMonths, fSymbols->fShortMonthsCount); | 
|  | } | 
|  | case kHourOfDay1Field: | 
|  | // [We computed 'value' above.] | 
|  | if (value == fCalendar->getMaximum(Calendar::HOUR_OF_DAY) + 1) | 
|  | value = 0; | 
|  | fCalendar->set(Calendar::HOUR_OF_DAY, value); | 
|  | return pos.getIndex(); | 
|  | case kDayOfWeekField: | 
|  | { | 
|  | // Want to be able to parse both short and long forms. | 
|  | // Try count == 4 (DDDD) first: | 
|  | int32_t newStart = 0; | 
|  | if ((newStart = matchString(text, start, Calendar::DAY_OF_WEEK, | 
|  | fSymbols->fWeekdays, fSymbols->fWeekdaysCount)) > 0) | 
|  | return newStart; | 
|  | else // DDDD failed, now try DDD | 
|  | return matchString(text, start, Calendar::DAY_OF_WEEK, | 
|  | fSymbols->fShortWeekdays, fSymbols->fShortWeekdaysCount); | 
|  | } | 
|  | case kAmPmField: | 
|  | return matchString(text, start, Calendar::AM_PM, fSymbols->fAmPms, fSymbols->fAmPmsCount); | 
|  | case kHour1Field: | 
|  | // [We computed 'value' above.] | 
|  | if (value == fCalendar->getLeastMaximum(Calendar::HOUR)+1) | 
|  | value = 0; | 
|  | fCalendar->set(Calendar::HOUR, value); | 
|  | return pos.getIndex(); | 
|  | case kTimezoneField: | 
|  | { | 
|  | // First try to parse generic forms such as GMT-07:00. Do this first | 
|  | // in case localized DateFormatZoneData contains the string "GMT" | 
|  | // for a zone; in that case, we don't want to match the first three | 
|  | // characters of GMT+/-HH:MM etc. | 
|  |  | 
|  | UnicodeString lcaseText(text); | 
|  | UnicodeString lcaseGMT(fgGmt); | 
|  | int32_t sign = 0; | 
|  | int32_t offset; | 
|  | int32_t gmtLen = lcaseGMT.length(); | 
|  |  | 
|  | // For time zones that have no known names, look for strings | 
|  | // of the form: | 
|  | //    GMT[+-]hours:minutes or | 
|  | //    GMT[+-]hhmm or | 
|  | //    GMT. | 
|  |  | 
|  | // {sfb} kludge for case-insensitive compare | 
|  | lcaseText.toLower(); | 
|  | lcaseGMT.toLower(); | 
|  |  | 
|  | if ((text.length() - start) > gmtLen && | 
|  | (lcaseText.compare(start, gmtLen, lcaseGMT, 0, gmtLen)) == 0) | 
|  | { | 
|  | fCalendar->set(Calendar::DST_OFFSET, 0); | 
|  |  | 
|  | pos.setIndex(start + gmtLen); | 
|  |  | 
|  | if( text[pos.getIndex()] == 0x002B /*'+'*/ ) | 
|  | sign = 1; | 
|  | else if( text[pos.getIndex()] == 0x002D /*'-'*/ ) | 
|  | sign = -1; | 
|  | else { | 
|  | fCalendar->set(Calendar::ZONE_OFFSET, 0 ); | 
|  | return pos.getIndex(); | 
|  | } | 
|  |  | 
|  | // Look for hours:minutes or hhmm. | 
|  | pos.setIndex(pos.getIndex() + 1); | 
|  | // WORK AROUND BUG IN NUMBER FORMAT IN 1.2B3 | 
|  | int32_t parseStart = pos.getIndex(); | 
|  | Formattable tzNumber; | 
|  | fNumberFormat->parse(text, tzNumber, pos); | 
|  | if( pos.getIndex() == parseStart) { | 
|  | // WORK AROUND BUG IN NUMBER FORMAT IN 1.2B3 | 
|  | return -start; | 
|  | } | 
|  | if( text[pos.getIndex()] == 0x003A /*':'*/ ) { | 
|  | // This is the hours:minutes case | 
|  | offset = tzNumber.getLong() * 60; | 
|  | pos.setIndex(pos.getIndex() + 1); | 
|  | // WORK AROUND BUG IN NUMBER FORMAT IN 1.2B3 | 
|  | parseStart = pos.getIndex(); | 
|  | fNumberFormat->parse(text, tzNumber, pos); | 
|  | if( pos.getIndex() == parseStart) { | 
|  | // WORK AROUND BUG IN NUMBER FORMAT IN 1.2B3 | 
|  | return -start; | 
|  | } | 
|  | offset += tzNumber.getLong(); | 
|  | } | 
|  | else { | 
|  | // This is the hhmm case. | 
|  | offset = tzNumber.getLong(); | 
|  | if( offset < 24 ) | 
|  | offset *= 60; | 
|  | else | 
|  | offset = offset % 100 + offset / 100 * 60; | 
|  | } | 
|  |  | 
|  | // Fall through for final processing below of 'offset' and 'sign'. | 
|  | } | 
|  | else { | 
|  | // At this point, check for named time zones by looking through | 
|  | // the locale data from the DateFormatZoneData strings. | 
|  | // Want to be able to parse both short and long forms. | 
|  | for (i = 0; i < fSymbols->fZoneStringsRowCount; i++) | 
|  | { | 
|  | // Checking long and short zones [1 & 2], | 
|  | // and long and short daylight [3 & 4]. | 
|  | int32_t j = 1; | 
|  |  | 
|  | // {sfb} kludge for case-insensitive compare | 
|  | UnicodeString s1(text); | 
|  | s1.toLower(); | 
|  | UnicodeString s2; | 
|  |  | 
|  | for (; j <= 4; ++j) | 
|  | { | 
|  | s2 = fSymbols->fZoneStrings[i][j]; | 
|  | s2.toLower(); | 
|  |  | 
|  | if ((s1.compare(start, s2.length(), s2, 0, s2.length())) == 0) | 
|  | break; | 
|  | } | 
|  | if (j <= 4) | 
|  | { | 
|  | TimeZone *tz = TimeZone::createTimeZone(fSymbols->fZoneStrings[i][0]); | 
|  | fCalendar->set(Calendar::ZONE_OFFSET, tz->getRawOffset()); | 
|  | // Must call set() with something -- TODO -- Fix this to | 
|  | // use the correct DST SAVINGS for the zone. | 
|  | delete tz; | 
|  | fCalendar->set(Calendar::DST_OFFSET, j >= 3 ? U_MILLIS_PER_HOUR : 0); | 
|  | return (start + fSymbols->fZoneStrings[i][j].length()); | 
|  | } | 
|  | } | 
|  |  | 
|  | // As a last resort, look for numeric timezones of the form | 
|  | // [+-]hhmm as specified by RFC 822.  This code is actually | 
|  | // a little more permissive than RFC 822.  It will try to do | 
|  | // its best with numbers that aren't strictly 4 digits long. | 
|  | UErrorCode status = U_ZERO_ERROR; | 
|  | DecimalFormat *fmt = new DecimalFormat("+####;-####", status); | 
|  | if(U_FAILURE(status)) | 
|  | return -start; | 
|  | fmt->setParseIntegerOnly(TRUE); | 
|  | // WORK AROUND BUG IN NUMBER FORMAT IN 1.2B3 | 
|  | int32_t parseStart = pos.getIndex(); | 
|  | Formattable tzNumber; | 
|  | fmt->parse( text, tzNumber, pos ); | 
|  | if( pos.getIndex() == parseStart) { | 
|  | // WORK AROUND BUG IN NUMBER FORMAT IN 1.2B3 | 
|  | return -start;   // Wasn't actually a number. | 
|  | } | 
|  | offset = tzNumber.getLong(); | 
|  | sign = 1; | 
|  | if( offset < 0 ) { | 
|  | sign = -1; | 
|  | offset = -offset; | 
|  | } | 
|  | if( offset < 24 ) | 
|  | offset = offset * 60; | 
|  | else | 
|  | offset = offset % 100 + offset / 100 * 60; | 
|  |  | 
|  | // Fall through for final processing below of 'offset' and 'sign'. | 
|  | } | 
|  |  | 
|  | // Do the final processing for both of the above cases.  We only | 
|  | // arrive here if the form GMT+/-... or an RFC 822 form was seen. | 
|  | if (sign != 0) | 
|  | { | 
|  | offset *= U_MILLIS_PER_MINUTE * sign; | 
|  |  | 
|  | if (fCalendar->getTimeZone().useDaylightTime()) | 
|  | { | 
|  | fCalendar->set(Calendar::DST_OFFSET, U_MILLIS_PER_HOUR); | 
|  | offset -= U_MILLIS_PER_HOUR; | 
|  | } | 
|  | fCalendar->set(Calendar::ZONE_OFFSET, offset); | 
|  |  | 
|  | return pos.getIndex(); | 
|  | } | 
|  |  | 
|  | // All efforts to parse a zone failed. | 
|  | return -start; | 
|  | } | 
|  | default: | 
|  | // case 3: // 'd' - DATE | 
|  | // case 5: // 'H' - HOUR_OF_DAY:0-based.  eg, 23:59 + 1 hour =>> 00:59 | 
|  | // case 6: // 'm' - MINUTE | 
|  | // case 7: // 's' - SECOND | 
|  | // case 8: // 'S' - MILLISECOND | 
|  | // case 10: // 'D' - DAY_OF_YEAR | 
|  | // case 11: // 'F' - DAY_OF_WEEK_IN_MONTH | 
|  | // case 12: // 'w' - WEEK_OF_YEAR | 
|  | // case 13: // 'W' - WEEK_OF_MONTH | 
|  | // case 16: // 'K' - HOUR: 0-based.  eg, 11PM + 1 hour =>> 0 AM | 
|  | // 'e' - DOW_LOCAL | 
|  |  | 
|  | // WORK AROUND BUG IN NUMBER FORMAT IN 1.2B3 | 
|  | int32_t parseStart = pos.getIndex(); | 
|  | // Handle "generic" fields | 
|  | if (obeyCount) | 
|  | { | 
|  | if ((start+count) > text.length()) | 
|  | return -start; | 
|  | UnicodeString s; | 
|  | // {sfb} old code had extract, make sure it works | 
|  | text.extractBetween(0, start + count, s); | 
|  | fNumberFormat->parse(s, number, pos); | 
|  | } | 
|  | else | 
|  | fNumberFormat->parse(text, number, pos); | 
|  | if (pos.getIndex() != parseStart) { | 
|  | // WORK AROUND BUG IN NUMBER FORMAT IN 1.2B3 | 
|  | fCalendar->set(field, number.getLong()); | 
|  | return pos.getIndex(); | 
|  | } | 
|  | return -start; | 
|  | } | 
|  | } | 
|  |  | 
|  | //---------------------------------------------------------------------- | 
|  |  | 
|  | void SimpleDateFormat::translatePattern(const UnicodeString& originalPattern, | 
|  | UnicodeString& translatedPattern, | 
|  | const UnicodeString& from, | 
|  | const UnicodeString& to, | 
|  | 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 | 
|  | // 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 | 
|  | // to localized patterns or localized to generic. | 
|  | if (U_FAILURE(status)) | 
|  | return; | 
|  |  | 
|  | translatedPattern.remove(); | 
|  | UBool inQuote = FALSE; | 
|  | for (UTextOffset i = 0; i < originalPattern.length(); ++i) { | 
|  | UChar c = originalPattern[i]; | 
|  | if (inQuote) { | 
|  | if (c == 0x0027 /*'\''*/) | 
|  | inQuote = FALSE; | 
|  | } | 
|  | else { | 
|  | if (c == 0x0027 /*'\''*/) | 
|  | inQuote = TRUE; | 
|  | else if ((c >= 0x0061 /*'a'*/ && c <= 0x007A) /*'z'*/ | 
|  | || (c >= 0x0041 /*'A'*/ && c <= 0x005A /*'Z'*/)) { | 
|  | UTextOffset ci = from.indexOf(c); | 
|  | if (ci == -1) { | 
|  | status = U_INVALID_FORMAT_ERROR; | 
|  | return; | 
|  | } | 
|  | c = to[ci]; | 
|  | } | 
|  | } | 
|  | translatedPattern += c; | 
|  | } | 
|  | if (inQuote) { | 
|  | status = U_INVALID_FORMAT_ERROR; | 
|  | return; | 
|  | } | 
|  | } | 
|  |  | 
|  | //---------------------------------------------------------------------- | 
|  |  | 
|  | UnicodeString& | 
|  | SimpleDateFormat::toPattern(UnicodeString& result) const | 
|  | { | 
|  | result = fPattern; | 
|  | return result; | 
|  | } | 
|  |  | 
|  | //---------------------------------------------------------------------- | 
|  |  | 
|  | UnicodeString& | 
|  | SimpleDateFormat::toLocalizedPattern(UnicodeString& result, | 
|  | UErrorCode& status) const | 
|  | { | 
|  | translatePattern(fPattern, result, DateFormatSymbols::getPatternUChars(), fSymbols->fLocalPatternChars, status); | 
|  | return result; | 
|  | } | 
|  |  | 
|  | //---------------------------------------------------------------------- | 
|  |  | 
|  | void | 
|  | SimpleDateFormat::applyPattern(const UnicodeString& pattern) | 
|  | { | 
|  | fPattern = pattern; | 
|  | } | 
|  |  | 
|  | //---------------------------------------------------------------------- | 
|  |  | 
|  | void | 
|  | SimpleDateFormat::applyLocalizedPattern(const UnicodeString& pattern, | 
|  | UErrorCode &status) | 
|  | { | 
|  | translatePattern(pattern, fPattern, fSymbols->fLocalPatternChars, DateFormatSymbols::getPatternUChars(), status); | 
|  | } | 
|  |  | 
|  | //---------------------------------------------------------------------- | 
|  |  | 
|  | const DateFormatSymbols* | 
|  | SimpleDateFormat::getDateFormatSymbols() const | 
|  | { | 
|  | return fSymbols; | 
|  | } | 
|  |  | 
|  | //---------------------------------------------------------------------- | 
|  |  | 
|  | void | 
|  | SimpleDateFormat::adoptDateFormatSymbols(DateFormatSymbols* newFormatSymbols) | 
|  | { | 
|  | delete fSymbols; | 
|  | fSymbols = newFormatSymbols; | 
|  | } | 
|  |  | 
|  | //---------------------------------------------------------------------- | 
|  | void | 
|  | SimpleDateFormat::setDateFormatSymbols(const DateFormatSymbols& newFormatSymbols) | 
|  | { | 
|  | delete fSymbols; | 
|  | fSymbols = new DateFormatSymbols(newFormatSymbols); | 
|  | } | 
|  |  | 
|  |  | 
|  | //---------------------------------------------------------------------- | 
|  |  | 
|  | // {sfb} removed | 
|  | /*int32_t | 
|  | SimpleDateFormat::getZoneIndex(const UnicodeString& ID) const | 
|  | { | 
|  | // this function searches a time zone list for a time zone with the specified | 
|  | // ID.  It'll either return an apprpriate row number or -1 if the ID wasn't | 
|  | // found. | 
|  | int32_t index, col; | 
|  |  | 
|  | for (col=0; col<=4 && col<fSymbols->fZoneStringsColCount; col+=2) | 
|  | { | 
|  | for (index = 0; index < fSymbols->fZoneStringsRowCount; index++) | 
|  | { | 
|  | if (fSymbols->fZoneStrings[index][col] == ID) return index; | 
|  | } | 
|  | } | 
|  |  | 
|  | return - 1; | 
|  | }*/ | 
|  |  | 
|  | //---------------------------------------------------------------------- | 
|  |  | 
|  | UDate | 
|  | SimpleDateFormat::internalGetDefaultCenturyStart() const | 
|  | { | 
|  | // lazy-evaluate systemDefaultCenturyStart | 
|  | if (fgSystemDefaultCenturyStart == fgSystemDefaultCentury) | 
|  | initializeSystemDefaultCentury(); | 
|  |  | 
|  | // use defaultCenturyStart unless it's the flag value; | 
|  | // then use systemDefaultCenturyStart | 
|  | return (fDefaultCenturyStart == fgSystemDefaultCentury) ? | 
|  | fgSystemDefaultCenturyStart : fDefaultCenturyStart; | 
|  | } | 
|  |  | 
|  | int32_t | 
|  | SimpleDateFormat::internalGetDefaultCenturyStartYear() const | 
|  | { | 
|  | // lazy-evaluate systemDefaultCenturyStartYear | 
|  | if (fgSystemDefaultCenturyStart == fgSystemDefaultCentury) | 
|  | initializeSystemDefaultCentury(); | 
|  |  | 
|  | // use defaultCenturyStart unless it's the flag value; | 
|  | // then use systemDefaultCenturyStartYear | 
|  | //return (fDefaultCenturyStart == fgSystemDefaultCentury) ? | 
|  | return (fDefaultCenturyStartYear == fgSystemDefaultCenturyYear) ? | 
|  | fgSystemDefaultCenturyStartYear : fDefaultCenturyStartYear; | 
|  | } | 
|  |  | 
|  | void | 
|  | SimpleDateFormat::initializeSystemDefaultCentury() | 
|  | { | 
|  | // initialize systemDefaultCentury and systemDefaultCenturyYear based | 
|  | // on the current time.  They'll be set to 80 years before | 
|  | // the current time. | 
|  | // No point in locking as it should be idempotent. | 
|  | if (fgSystemDefaultCenturyStart == fgSystemDefaultCentury) | 
|  | { | 
|  | UErrorCode status = U_ZERO_ERROR; | 
|  | Calendar *calendar = Calendar::createInstance(status); | 
|  | if (calendar != NULL && U_SUCCESS(status)) | 
|  | { | 
|  | calendar->setTime(Calendar::getNow(), status); | 
|  | calendar->add(Calendar::YEAR, -80, status); | 
|  | fgSystemDefaultCenturyStart = calendar->getTime(status); | 
|  | fgSystemDefaultCenturyStartYear = calendar->get(Calendar::YEAR, status); | 
|  | delete calendar; | 
|  | } | 
|  | // We have no recourse upon failure unless we want to propagate the failure | 
|  | // out. | 
|  | } | 
|  | } | 
|  |  | 
|  | U_NAMESPACE_END | 
|  |  | 
|  | //eof |