michael@0: /* michael@0: ******************************************************************************* michael@0: * Copyright (C) 1997-2013, International Business Machines Corporation and * michael@0: * others. All Rights Reserved. * michael@0: ******************************************************************************* michael@0: * michael@0: * File SMPDTFMT.CPP michael@0: * michael@0: * Modification History: michael@0: * michael@0: * Date Name Description michael@0: * 02/19/97 aliu Converted from java. michael@0: * 03/31/97 aliu Modified extensively to work with 50 locales. michael@0: * 04/01/97 aliu Added support for centuries. michael@0: * 07/09/97 helena Made ParsePosition into a class. michael@0: * 07/21/98 stephen Added initializeDefaultCentury. michael@0: * Removed getZoneIndex (added in DateFormatSymbols) michael@0: * Removed subParseLong michael@0: * Removed chk michael@0: * 02/22/99 stephen Removed character literals for EBCDIC safety michael@0: * 10/14/99 aliu Updated 2-digit year parsing so that only "00" thru michael@0: * "99" are recognized. {j28 4182066} michael@0: * 11/15/99 weiv Added support for week of year/day of week format michael@0: ******************************************************************************** michael@0: */ michael@0: michael@0: #define ZID_KEY_MAX 128 michael@0: michael@0: #include "unicode/utypes.h" michael@0: michael@0: #if !UCONFIG_NO_FORMATTING michael@0: michael@0: #include "unicode/smpdtfmt.h" michael@0: #include "unicode/dtfmtsym.h" michael@0: #include "unicode/ures.h" michael@0: #include "unicode/msgfmt.h" michael@0: #include "unicode/calendar.h" michael@0: #include "unicode/gregocal.h" michael@0: #include "unicode/timezone.h" michael@0: #include "unicode/decimfmt.h" michael@0: #include "unicode/dcfmtsym.h" michael@0: #include "unicode/uchar.h" michael@0: #include "unicode/uniset.h" michael@0: #include "unicode/ustring.h" michael@0: #include "unicode/basictz.h" michael@0: #include "unicode/simpletz.h" michael@0: #include "unicode/rbtz.h" michael@0: #include "unicode/tzfmt.h" michael@0: #include "unicode/utf16.h" michael@0: #include "unicode/vtzone.h" michael@0: #include "unicode/udisplaycontext.h" michael@0: #include "olsontz.h" michael@0: #include "patternprops.h" michael@0: #include "fphdlimp.h" michael@0: #include "gregoimp.h" michael@0: #include "hebrwcal.h" michael@0: #include "cstring.h" michael@0: #include "uassert.h" michael@0: #include "cmemory.h" michael@0: #include "umutex.h" michael@0: #include michael@0: #include "smpdtfst.h" michael@0: michael@0: #if defined( U_DEBUG_CALSVC ) || defined (U_DEBUG_CAL) michael@0: #include michael@0: #endif michael@0: michael@0: // ***************************************************************************** michael@0: // class SimpleDateFormat michael@0: // ***************************************************************************** michael@0: michael@0: U_NAMESPACE_BEGIN michael@0: michael@0: static const UChar PATTERN_CHAR_BASE = 0x40; michael@0: michael@0: /** michael@0: * Last-resort string to use for "GMT" when constructing time zone strings. michael@0: */ michael@0: // For time zones that have no names, use strings GMT+minutes and michael@0: // GMT-minutes. For instance, in France the time zone is GMT+60. michael@0: // Also accepted are GMT+H:MM or GMT-H:MM. michael@0: // Currently not being used michael@0: //static const UChar gGmt[] = {0x0047, 0x004D, 0x0054, 0x0000}; // "GMT" michael@0: //static const UChar gGmtPlus[] = {0x0047, 0x004D, 0x0054, 0x002B, 0x0000}; // "GMT+" michael@0: //static const UChar gGmtMinus[] = {0x0047, 0x004D, 0x0054, 0x002D, 0x0000}; // "GMT-" michael@0: //static const UChar gDefGmtPat[] = {0x0047, 0x004D, 0x0054, 0x007B, 0x0030, 0x007D, 0x0000}; /* GMT{0} */ michael@0: //static const UChar gDefGmtNegHmsPat[] = {0x002D, 0x0048, 0x0048, 0x003A, 0x006D, 0x006D, 0x003A, 0x0073, 0x0073, 0x0000}; /* -HH:mm:ss */ michael@0: //static const UChar gDefGmtNegHmPat[] = {0x002D, 0x0048, 0x0048, 0x003A, 0x006D, 0x006D, 0x0000}; /* -HH:mm */ michael@0: //static const UChar gDefGmtPosHmsPat[] = {0x002B, 0x0048, 0x0048, 0x003A, 0x006D, 0x006D, 0x003A, 0x0073, 0x0073, 0x0000}; /* +HH:mm:ss */ michael@0: //static const UChar gDefGmtPosHmPat[] = {0x002B, 0x0048, 0x0048, 0x003A, 0x006D, 0x006D, 0x0000}; /* +HH:mm */ michael@0: //static const UChar gUt[] = {0x0055, 0x0054, 0x0000}; // "UT" michael@0: //static const UChar gUtc[] = {0x0055, 0x0054, 0x0043, 0x0000}; // "UT" michael@0: michael@0: typedef enum GmtPatSize { michael@0: kGmtLen = 3, michael@0: kGmtPatLen = 6, michael@0: kNegHmsLen = 9, michael@0: kNegHmLen = 6, michael@0: kPosHmsLen = 9, michael@0: kPosHmLen = 6, michael@0: kUtLen = 2, michael@0: kUtcLen = 3 michael@0: } GmtPatSize; michael@0: michael@0: // Stuff needed for numbering system overrides michael@0: michael@0: typedef enum OvrStrType { michael@0: kOvrStrDate = 0, michael@0: kOvrStrTime = 1, michael@0: kOvrStrBoth = 2 michael@0: } OvrStrType; michael@0: michael@0: static const UDateFormatField kDateFields[] = { michael@0: UDAT_YEAR_FIELD, michael@0: UDAT_MONTH_FIELD, michael@0: UDAT_DATE_FIELD, michael@0: UDAT_DAY_OF_YEAR_FIELD, michael@0: UDAT_DAY_OF_WEEK_IN_MONTH_FIELD, michael@0: UDAT_WEEK_OF_YEAR_FIELD, michael@0: UDAT_WEEK_OF_MONTH_FIELD, michael@0: UDAT_YEAR_WOY_FIELD, michael@0: UDAT_EXTENDED_YEAR_FIELD, michael@0: UDAT_JULIAN_DAY_FIELD, michael@0: UDAT_STANDALONE_DAY_FIELD, michael@0: UDAT_STANDALONE_MONTH_FIELD, michael@0: UDAT_QUARTER_FIELD, michael@0: UDAT_STANDALONE_QUARTER_FIELD, michael@0: UDAT_YEAR_NAME_FIELD }; michael@0: static const int8_t kDateFieldsCount = 15; michael@0: michael@0: static const UDateFormatField kTimeFields[] = { michael@0: UDAT_HOUR_OF_DAY1_FIELD, michael@0: UDAT_HOUR_OF_DAY0_FIELD, michael@0: UDAT_MINUTE_FIELD, michael@0: UDAT_SECOND_FIELD, michael@0: UDAT_FRACTIONAL_SECOND_FIELD, michael@0: UDAT_HOUR1_FIELD, michael@0: UDAT_HOUR0_FIELD, michael@0: UDAT_MILLISECONDS_IN_DAY_FIELD, michael@0: UDAT_TIMEZONE_RFC_FIELD, michael@0: UDAT_TIMEZONE_LOCALIZED_GMT_OFFSET_FIELD }; michael@0: static const int8_t kTimeFieldsCount = 10; michael@0: michael@0: michael@0: // This is a pattern-of-last-resort used when we can't load a usable pattern out michael@0: // of a resource. michael@0: static const UChar gDefaultPattern[] = michael@0: { michael@0: 0x79, 0x79, 0x79, 0x79, 0x4D, 0x4D, 0x64, 0x64, 0x20, 0x68, 0x68, 0x3A, 0x6D, 0x6D, 0x20, 0x61, 0 michael@0: }; /* "yyyyMMdd hh:mm a" */ michael@0: michael@0: // This prefix is designed to NEVER MATCH real text, in order to michael@0: // suppress the parsing of negative numbers. Adjust as needed (if michael@0: // this becomes valid Unicode). michael@0: static const UChar SUPPRESS_NEGATIVE_PREFIX[] = {0xAB00, 0}; michael@0: michael@0: /** michael@0: * These are the tags we expect to see in normal resource bundle files associated michael@0: * with a locale. michael@0: */ michael@0: static const char gDateTimePatternsTag[]="DateTimePatterns"; michael@0: michael@0: //static const UChar gEtcUTC[] = {0x45, 0x74, 0x63, 0x2F, 0x55, 0x54, 0x43, 0x00}; // "Etc/UTC" michael@0: static const UChar QUOTE = 0x27; // Single quote michael@0: michael@0: /* michael@0: * The field range check bias for each UDateFormatField. michael@0: * The bias is added to the minimum and maximum values michael@0: * before they are compared to the parsed number. michael@0: * For example, the calendar stores zero-based month numbers michael@0: * but the parsed month numbers start at 1, so the bias is 1. michael@0: * michael@0: * A value of -1 means that the value is not checked. michael@0: */ michael@0: static const int32_t gFieldRangeBias[] = { michael@0: -1, // 'G' - UDAT_ERA_FIELD michael@0: -1, // 'y' - UDAT_YEAR_FIELD michael@0: 1, // 'M' - UDAT_MONTH_FIELD michael@0: 0, // 'd' - UDAT_DATE_FIELD michael@0: -1, // 'k' - UDAT_HOUR_OF_DAY1_FIELD michael@0: -1, // 'H' - UDAT_HOUR_OF_DAY0_FIELD michael@0: 0, // 'm' - UDAT_MINUTE_FIELD michael@0: 0, // 's' - UDAT_SEOND_FIELD michael@0: -1, // 'S' - UDAT_FRACTIONAL_SECOND_FIELD (0-999?) michael@0: -1, // 'E' - UDAT_DAY_OF_WEEK_FIELD (1-7?) michael@0: -1, // 'D' - UDAT_DAY_OF_YEAR_FIELD (1 - 366?) michael@0: -1, // 'F' - UDAT_DAY_OF_WEEK_IN_MONTH_FIELD (1-5?) michael@0: -1, // 'w' - UDAT_WEEK_OF_YEAR_FIELD (1-52?) michael@0: -1, // 'W' - UDAT_WEEK_OF_MONTH_FIELD (1-5?) michael@0: -1, // 'a' - UDAT_AM_PM_FIELD michael@0: -1, // 'h' - UDAT_HOUR1_FIELD michael@0: -1, // 'K' - UDAT_HOUR0_FIELD michael@0: -1, // 'z' - UDAT_TIMEZONE_FIELD michael@0: -1, // 'Y' - UDAT_YEAR_WOY_FIELD michael@0: -1, // 'e' - UDAT_DOW_LOCAL_FIELD michael@0: -1, // 'u' - UDAT_EXTENDED_YEAR_FIELD michael@0: -1, // 'g' - UDAT_JULIAN_DAY_FIELD michael@0: -1, // 'A' - UDAT_MILLISECONDS_IN_DAY_FIELD michael@0: -1, // 'Z' - UDAT_TIMEZONE_RFC_FIELD michael@0: -1, // 'v' - UDAT_TIMEZONE_GENERIC_FIELD michael@0: 0, // 'c' - UDAT_STANDALONE_DAY_FIELD michael@0: 1, // 'L' - UDAT_STANDALONE_MONTH_FIELD michael@0: -1, // 'Q' - UDAT_QUARTER_FIELD (1-4?) michael@0: -1, // 'q' - UDAT_STANDALONE_QUARTER_FIELD michael@0: -1 // 'V' - UDAT_TIMEZONE_SPECIAL_FIELD michael@0: -1, // 'U' - UDAT_YEAR_NAME_FIELD michael@0: -1, // 'O' - UDAT_TIMEZONE_LOCALIZED_GMT_OFFSET_FIELD michael@0: -1, // 'X' - UDAT_TIMEZONE_ISO_FIELD michael@0: -1, // 'x' - UDAT_TIMEZONE_ISO_LOCAL_FIELD michael@0: }; michael@0: michael@0: // When calendar uses hebr numbering (i.e. he@calendar=hebrew), michael@0: // offset the years within the current millenium down to 1-999 michael@0: static const int32_t HEBREW_CAL_CUR_MILLENIUM_START_YEAR = 5000; michael@0: static const int32_t HEBREW_CAL_CUR_MILLENIUM_END_YEAR = 6000; michael@0: michael@0: static UMutex LOCK = U_MUTEX_INITIALIZER; michael@0: michael@0: UOBJECT_DEFINE_RTTI_IMPLEMENTATION(SimpleDateFormat) michael@0: michael@0: //---------------------------------------------------------------------- michael@0: michael@0: SimpleDateFormat::~SimpleDateFormat() michael@0: { michael@0: delete fSymbols; michael@0: if (fNumberFormatters) { michael@0: uprv_free(fNumberFormatters); michael@0: } michael@0: if (fTimeZoneFormat) { michael@0: delete fTimeZoneFormat; michael@0: } michael@0: michael@0: while (fOverrideList) { michael@0: NSOverride *cur = fOverrideList; michael@0: fOverrideList = cur->next; michael@0: delete cur->nf; michael@0: uprv_free(cur); michael@0: } michael@0: } michael@0: michael@0: //---------------------------------------------------------------------- michael@0: michael@0: SimpleDateFormat::SimpleDateFormat(UErrorCode& status) michael@0: : fLocale(Locale::getDefault()), michael@0: fSymbols(NULL), michael@0: fTimeZoneFormat(NULL), michael@0: fNumberFormatters(NULL), michael@0: fOverrideList(NULL), michael@0: fCapitalizationContext(UDISPCTX_CAPITALIZATION_NONE) michael@0: { michael@0: setBooleanAttribute(UDAT_PARSE_ALLOW_WHITESPACE, true, status).setBooleanAttribute(UDAT_PARSE_ALLOW_NUMERIC, true, status); michael@0: construct(kShort, (EStyle) (kShort + kDateOffset), fLocale, status); michael@0: initializeDefaultCentury(); michael@0: } michael@0: michael@0: //---------------------------------------------------------------------- michael@0: michael@0: SimpleDateFormat::SimpleDateFormat(const UnicodeString& pattern, michael@0: UErrorCode &status) michael@0: : fPattern(pattern), michael@0: fLocale(Locale::getDefault()), michael@0: fSymbols(NULL), michael@0: fTimeZoneFormat(NULL), michael@0: fNumberFormatters(NULL), michael@0: fOverrideList(NULL), michael@0: fCapitalizationContext(UDISPCTX_CAPITALIZATION_NONE) michael@0: { michael@0: fDateOverride.setToBogus(); michael@0: fTimeOverride.setToBogus(); michael@0: setBooleanAttribute(UDAT_PARSE_ALLOW_WHITESPACE, true, status).setBooleanAttribute(UDAT_PARSE_ALLOW_NUMERIC, true, status); michael@0: initializeSymbols(fLocale, initializeCalendar(NULL,fLocale,status), status); michael@0: initialize(fLocale, status); michael@0: initializeDefaultCentury(); michael@0: michael@0: } michael@0: //---------------------------------------------------------------------- michael@0: michael@0: SimpleDateFormat::SimpleDateFormat(const UnicodeString& pattern, michael@0: const UnicodeString& override, michael@0: UErrorCode &status) michael@0: : fPattern(pattern), michael@0: fLocale(Locale::getDefault()), michael@0: fSymbols(NULL), michael@0: fTimeZoneFormat(NULL), michael@0: fNumberFormatters(NULL), michael@0: fOverrideList(NULL), michael@0: fCapitalizationContext(UDISPCTX_CAPITALIZATION_NONE) michael@0: { michael@0: fDateOverride.setTo(override); michael@0: fTimeOverride.setToBogus(); michael@0: setBooleanAttribute(UDAT_PARSE_ALLOW_WHITESPACE, true, status).setBooleanAttribute(UDAT_PARSE_ALLOW_NUMERIC, true, status); michael@0: initializeSymbols(fLocale, initializeCalendar(NULL,fLocale,status), status); michael@0: initialize(fLocale, status); michael@0: initializeDefaultCentury(); michael@0: michael@0: processOverrideString(fLocale,override,kOvrStrBoth,status); michael@0: michael@0: } michael@0: michael@0: //---------------------------------------------------------------------- michael@0: michael@0: SimpleDateFormat::SimpleDateFormat(const UnicodeString& pattern, michael@0: const Locale& locale, michael@0: UErrorCode& status) michael@0: : fPattern(pattern), michael@0: fLocale(locale), michael@0: fTimeZoneFormat(NULL), michael@0: fNumberFormatters(NULL), michael@0: fOverrideList(NULL), michael@0: fCapitalizationContext(UDISPCTX_CAPITALIZATION_NONE) michael@0: { michael@0: michael@0: fDateOverride.setToBogus(); michael@0: fTimeOverride.setToBogus(); michael@0: setBooleanAttribute(UDAT_PARSE_ALLOW_WHITESPACE, true, status).setBooleanAttribute(UDAT_PARSE_ALLOW_NUMERIC, true, status); michael@0: michael@0: initializeSymbols(fLocale, initializeCalendar(NULL,fLocale,status), status); michael@0: initialize(fLocale, status); michael@0: initializeDefaultCentury(); michael@0: } michael@0: michael@0: //---------------------------------------------------------------------- michael@0: michael@0: SimpleDateFormat::SimpleDateFormat(const UnicodeString& pattern, michael@0: const UnicodeString& override, michael@0: const Locale& locale, michael@0: UErrorCode& status) michael@0: : fPattern(pattern), michael@0: fLocale(locale), michael@0: fTimeZoneFormat(NULL), michael@0: fNumberFormatters(NULL), michael@0: fOverrideList(NULL), michael@0: fCapitalizationContext(UDISPCTX_CAPITALIZATION_NONE) michael@0: { michael@0: michael@0: fDateOverride.setTo(override); michael@0: fTimeOverride.setToBogus(); michael@0: setBooleanAttribute(UDAT_PARSE_ALLOW_WHITESPACE, true, status).setBooleanAttribute(UDAT_PARSE_ALLOW_NUMERIC, true, status); michael@0: michael@0: initializeSymbols(fLocale, initializeCalendar(NULL,fLocale,status), status); michael@0: initialize(fLocale, status); michael@0: initializeDefaultCentury(); michael@0: michael@0: processOverrideString(locale,override,kOvrStrBoth,status); michael@0: michael@0: } michael@0: michael@0: //---------------------------------------------------------------------- michael@0: michael@0: SimpleDateFormat::SimpleDateFormat(const UnicodeString& pattern, michael@0: DateFormatSymbols* symbolsToAdopt, michael@0: UErrorCode& status) michael@0: : fPattern(pattern), michael@0: fLocale(Locale::getDefault()), michael@0: fSymbols(symbolsToAdopt), michael@0: fTimeZoneFormat(NULL), michael@0: fNumberFormatters(NULL), michael@0: fOverrideList(NULL), michael@0: fCapitalizationContext(UDISPCTX_CAPITALIZATION_NONE) michael@0: { michael@0: michael@0: fDateOverride.setToBogus(); michael@0: fTimeOverride.setToBogus(); michael@0: setBooleanAttribute(UDAT_PARSE_ALLOW_WHITESPACE, true, status).setBooleanAttribute(UDAT_PARSE_ALLOW_NUMERIC, true, status); michael@0: michael@0: initializeCalendar(NULL,fLocale,status); michael@0: initialize(fLocale, status); michael@0: initializeDefaultCentury(); michael@0: } michael@0: michael@0: //---------------------------------------------------------------------- michael@0: michael@0: SimpleDateFormat::SimpleDateFormat(const UnicodeString& pattern, michael@0: const DateFormatSymbols& symbols, michael@0: UErrorCode& status) michael@0: : fPattern(pattern), michael@0: fLocale(Locale::getDefault()), michael@0: fSymbols(new DateFormatSymbols(symbols)), michael@0: fTimeZoneFormat(NULL), michael@0: fNumberFormatters(NULL), michael@0: fOverrideList(NULL), michael@0: fCapitalizationContext(UDISPCTX_CAPITALIZATION_NONE) michael@0: { michael@0: michael@0: fDateOverride.setToBogus(); michael@0: fTimeOverride.setToBogus(); michael@0: setBooleanAttribute(UDAT_PARSE_ALLOW_WHITESPACE, true, status).setBooleanAttribute(UDAT_PARSE_ALLOW_NUMERIC, true, status); michael@0: michael@0: initializeCalendar(NULL, fLocale, status); michael@0: initialize(fLocale, status); michael@0: initializeDefaultCentury(); michael@0: } michael@0: michael@0: //---------------------------------------------------------------------- michael@0: michael@0: // Not for public consumption; used by DateFormat michael@0: SimpleDateFormat::SimpleDateFormat(EStyle timeStyle, michael@0: EStyle dateStyle, michael@0: const Locale& locale, michael@0: UErrorCode& status) michael@0: : fLocale(locale), michael@0: fSymbols(NULL), michael@0: fTimeZoneFormat(NULL), michael@0: fNumberFormatters(NULL), michael@0: fOverrideList(NULL), michael@0: fCapitalizationContext(UDISPCTX_CAPITALIZATION_NONE) michael@0: { michael@0: setBooleanAttribute(UDAT_PARSE_ALLOW_WHITESPACE, true, status).setBooleanAttribute(UDAT_PARSE_ALLOW_NUMERIC, true, status); michael@0: construct(timeStyle, dateStyle, fLocale, status); michael@0: if(U_SUCCESS(status)) { michael@0: initializeDefaultCentury(); michael@0: } michael@0: } michael@0: michael@0: //---------------------------------------------------------------------- michael@0: michael@0: /** michael@0: * Not for public consumption; used by DateFormat. This constructor michael@0: * never fails. If the resource data is not available, it uses the michael@0: * the last resort symbols. michael@0: */ michael@0: SimpleDateFormat::SimpleDateFormat(const Locale& locale, michael@0: UErrorCode& status) michael@0: : fPattern(gDefaultPattern), michael@0: fLocale(locale), michael@0: fSymbols(NULL), michael@0: fTimeZoneFormat(NULL), michael@0: fNumberFormatters(NULL), michael@0: fOverrideList(NULL), michael@0: fCapitalizationContext(UDISPCTX_CAPITALIZATION_NONE) michael@0: { michael@0: if (U_FAILURE(status)) return; michael@0: initializeSymbols(fLocale, initializeCalendar(NULL, fLocale, status),status); michael@0: if (U_FAILURE(status)) michael@0: { michael@0: status = U_ZERO_ERROR; michael@0: delete fSymbols; michael@0: // This constructor doesn't fail; it uses last resort data michael@0: fSymbols = new DateFormatSymbols(status); michael@0: /* test for NULL */ michael@0: if (fSymbols == 0) { michael@0: status = U_MEMORY_ALLOCATION_ERROR; michael@0: return; michael@0: } michael@0: } michael@0: michael@0: fDateOverride.setToBogus(); michael@0: fTimeOverride.setToBogus(); michael@0: setBooleanAttribute(UDAT_PARSE_ALLOW_WHITESPACE, true, status).setBooleanAttribute(UDAT_PARSE_ALLOW_NUMERIC, true, status); michael@0: michael@0: initialize(fLocale, status); michael@0: if(U_SUCCESS(status)) { michael@0: initializeDefaultCentury(); michael@0: } michael@0: } michael@0: michael@0: //---------------------------------------------------------------------- michael@0: michael@0: SimpleDateFormat::SimpleDateFormat(const SimpleDateFormat& other) michael@0: : DateFormat(other), michael@0: fLocale(other.fLocale), michael@0: fSymbols(NULL), michael@0: fTimeZoneFormat(NULL), michael@0: fNumberFormatters(NULL), michael@0: fOverrideList(NULL), michael@0: fCapitalizationContext(UDISPCTX_CAPITALIZATION_NONE) michael@0: { michael@0: UErrorCode status = U_ZERO_ERROR; michael@0: setBooleanAttribute(UDAT_PARSE_ALLOW_WHITESPACE, true, status).setBooleanAttribute(UDAT_PARSE_ALLOW_NUMERIC, true, status); michael@0: *this = other; michael@0: } michael@0: michael@0: //---------------------------------------------------------------------- michael@0: michael@0: SimpleDateFormat& SimpleDateFormat::operator=(const SimpleDateFormat& other) michael@0: { michael@0: if (this == &other) { michael@0: return *this; michael@0: } michael@0: DateFormat::operator=(other); michael@0: michael@0: delete fSymbols; michael@0: fSymbols = NULL; michael@0: michael@0: if (other.fSymbols) michael@0: fSymbols = new DateFormatSymbols(*other.fSymbols); michael@0: michael@0: fDefaultCenturyStart = other.fDefaultCenturyStart; michael@0: fDefaultCenturyStartYear = other.fDefaultCenturyStartYear; michael@0: fHaveDefaultCentury = other.fHaveDefaultCentury; michael@0: michael@0: fPattern = other.fPattern; michael@0: michael@0: // TimeZoneFormat in ICU4C only depends on a locale for now michael@0: if (fLocale != other.fLocale) { michael@0: delete fTimeZoneFormat; michael@0: fTimeZoneFormat = NULL; // forces lazy instantiation with the other locale michael@0: fLocale = other.fLocale; michael@0: } michael@0: michael@0: fCapitalizationContext = other.fCapitalizationContext; michael@0: michael@0: return *this; michael@0: } michael@0: michael@0: //---------------------------------------------------------------------- michael@0: michael@0: Format* michael@0: SimpleDateFormat::clone() const michael@0: { michael@0: return new SimpleDateFormat(*this); michael@0: } michael@0: michael@0: //---------------------------------------------------------------------- michael@0: michael@0: UBool michael@0: SimpleDateFormat::operator==(const Format& other) const michael@0: { michael@0: if (DateFormat::operator==(other)) { michael@0: // DateFormat::operator== guarantees following cast is safe michael@0: SimpleDateFormat* that = (SimpleDateFormat*)&other; michael@0: return (fPattern == that->fPattern && michael@0: fSymbols != NULL && // Check for pathological object michael@0: that->fSymbols != NULL && // Check for pathological object michael@0: *fSymbols == *that->fSymbols && michael@0: fHaveDefaultCentury == that->fHaveDefaultCentury && michael@0: fDefaultCenturyStart == that->fDefaultCenturyStart && michael@0: fCapitalizationContext == that->fCapitalizationContext); michael@0: } michael@0: return FALSE; michael@0: } michael@0: michael@0: //---------------------------------------------------------------------- michael@0: michael@0: void SimpleDateFormat::construct(EStyle timeStyle, michael@0: EStyle dateStyle, michael@0: const Locale& locale, michael@0: UErrorCode& status) michael@0: { michael@0: // called by several constructors to load pattern data from the resources michael@0: if (U_FAILURE(status)) return; michael@0: michael@0: // We will need the calendar to know what type of symbols to load. michael@0: initializeCalendar(NULL, locale, status); michael@0: if (U_FAILURE(status)) return; michael@0: michael@0: CalendarData calData(locale, fCalendar?fCalendar->getType():NULL, status); michael@0: UResourceBundle *dateTimePatterns = calData.getByKey(gDateTimePatternsTag, status); michael@0: UResourceBundle *currentBundle; michael@0: michael@0: if (U_FAILURE(status)) return; michael@0: michael@0: if (ures_getSize(dateTimePatterns) <= kDateTime) michael@0: { michael@0: status = U_INVALID_FORMAT_ERROR; michael@0: return; michael@0: } michael@0: michael@0: setLocaleIDs(ures_getLocaleByType(dateTimePatterns, ULOC_VALID_LOCALE, &status), michael@0: ures_getLocaleByType(dateTimePatterns, ULOC_ACTUAL_LOCALE, &status)); michael@0: michael@0: // create a symbols object from the locale michael@0: initializeSymbols(locale,fCalendar, status); michael@0: if (U_FAILURE(status)) return; michael@0: /* test for NULL */ michael@0: if (fSymbols == 0) { michael@0: status = U_MEMORY_ALLOCATION_ERROR; michael@0: return; michael@0: } michael@0: michael@0: const UChar *resStr,*ovrStr; michael@0: int32_t resStrLen,ovrStrLen = 0; michael@0: fDateOverride.setToBogus(); michael@0: fTimeOverride.setToBogus(); michael@0: michael@0: // if the pattern should include both date and time information, use the date/time michael@0: // pattern string as a guide to tell use how to glue together the appropriate date michael@0: // and time pattern strings. The actual gluing-together is handled by a convenience michael@0: // method on MessageFormat. michael@0: if ((timeStyle != kNone) && (dateStyle != kNone)) michael@0: { michael@0: Formattable timeDateArray[2]; michael@0: michael@0: // use Formattable::adoptString() so that we can use fastCopyFrom() michael@0: // instead of Formattable::setString()'s unaware, safe, deep string clone michael@0: // see Jitterbug 2296 michael@0: michael@0: currentBundle = ures_getByIndex(dateTimePatterns, (int32_t)timeStyle, NULL, &status); michael@0: if (U_FAILURE(status)) { michael@0: status = U_INVALID_FORMAT_ERROR; michael@0: return; michael@0: } michael@0: switch (ures_getType(currentBundle)) { michael@0: case URES_STRING: { michael@0: resStr = ures_getString(currentBundle, &resStrLen, &status); michael@0: break; michael@0: } michael@0: case URES_ARRAY: { michael@0: resStr = ures_getStringByIndex(currentBundle, 0, &resStrLen, &status); michael@0: ovrStr = ures_getStringByIndex(currentBundle, 1, &ovrStrLen, &status); michael@0: fTimeOverride.setTo(TRUE, ovrStr, ovrStrLen); michael@0: break; michael@0: } michael@0: default: { michael@0: status = U_INVALID_FORMAT_ERROR; michael@0: ures_close(currentBundle); michael@0: return; michael@0: } michael@0: } michael@0: ures_close(currentBundle); michael@0: michael@0: UnicodeString *tempus1 = new UnicodeString(TRUE, resStr, resStrLen); michael@0: // NULL pointer check michael@0: if (tempus1 == NULL) { michael@0: status = U_MEMORY_ALLOCATION_ERROR; michael@0: return; michael@0: } michael@0: timeDateArray[0].adoptString(tempus1); michael@0: michael@0: currentBundle = ures_getByIndex(dateTimePatterns, (int32_t)dateStyle, NULL, &status); michael@0: if (U_FAILURE(status)) { michael@0: status = U_INVALID_FORMAT_ERROR; michael@0: return; michael@0: } michael@0: switch (ures_getType(currentBundle)) { michael@0: case URES_STRING: { michael@0: resStr = ures_getString(currentBundle, &resStrLen, &status); michael@0: break; michael@0: } michael@0: case URES_ARRAY: { michael@0: resStr = ures_getStringByIndex(currentBundle, 0, &resStrLen, &status); michael@0: ovrStr = ures_getStringByIndex(currentBundle, 1, &ovrStrLen, &status); michael@0: fDateOverride.setTo(TRUE, ovrStr, ovrStrLen); michael@0: break; michael@0: } michael@0: default: { michael@0: status = U_INVALID_FORMAT_ERROR; michael@0: ures_close(currentBundle); michael@0: return; michael@0: } michael@0: } michael@0: ures_close(currentBundle); michael@0: michael@0: UnicodeString *tempus2 = new UnicodeString(TRUE, resStr, resStrLen); michael@0: // Null pointer check michael@0: if (tempus2 == NULL) { michael@0: status = U_MEMORY_ALLOCATION_ERROR; michael@0: return; michael@0: } michael@0: timeDateArray[1].adoptString(tempus2); michael@0: michael@0: int32_t glueIndex = kDateTime; michael@0: int32_t patternsSize = ures_getSize(dateTimePatterns); michael@0: if (patternsSize >= (kDateTimeOffset + kShort + 1)) { michael@0: // Get proper date time format michael@0: glueIndex = (int32_t)(kDateTimeOffset + (dateStyle - kDateOffset)); michael@0: } michael@0: michael@0: resStr = ures_getStringByIndex(dateTimePatterns, glueIndex, &resStrLen, &status); michael@0: MessageFormat::format(UnicodeString(TRUE, resStr, resStrLen), timeDateArray, 2, fPattern, status); michael@0: } michael@0: // if the pattern includes just time data or just date date, load the appropriate michael@0: // pattern string from the resources michael@0: // setTo() - see DateFormatSymbols::assignArray comments michael@0: else if (timeStyle != kNone) { michael@0: currentBundle = ures_getByIndex(dateTimePatterns, (int32_t)timeStyle, NULL, &status); michael@0: if (U_FAILURE(status)) { michael@0: status = U_INVALID_FORMAT_ERROR; michael@0: return; michael@0: } michael@0: switch (ures_getType(currentBundle)) { michael@0: case URES_STRING: { michael@0: resStr = ures_getString(currentBundle, &resStrLen, &status); michael@0: break; michael@0: } michael@0: case URES_ARRAY: { michael@0: resStr = ures_getStringByIndex(currentBundle, 0, &resStrLen, &status); michael@0: ovrStr = ures_getStringByIndex(currentBundle, 1, &ovrStrLen, &status); michael@0: fDateOverride.setTo(TRUE, ovrStr, ovrStrLen); michael@0: break; michael@0: } michael@0: default: { michael@0: status = U_INVALID_FORMAT_ERROR; michael@0: ures_close(currentBundle); michael@0: return; michael@0: } michael@0: } michael@0: fPattern.setTo(TRUE, resStr, resStrLen); michael@0: ures_close(currentBundle); michael@0: } michael@0: else if (dateStyle != kNone) { michael@0: currentBundle = ures_getByIndex(dateTimePatterns, (int32_t)dateStyle, NULL, &status); michael@0: if (U_FAILURE(status)) { michael@0: status = U_INVALID_FORMAT_ERROR; michael@0: return; michael@0: } michael@0: switch (ures_getType(currentBundle)) { michael@0: case URES_STRING: { michael@0: resStr = ures_getString(currentBundle, &resStrLen, &status); michael@0: break; michael@0: } michael@0: case URES_ARRAY: { michael@0: resStr = ures_getStringByIndex(currentBundle, 0, &resStrLen, &status); michael@0: ovrStr = ures_getStringByIndex(currentBundle, 1, &ovrStrLen, &status); michael@0: fDateOverride.setTo(TRUE, ovrStr, ovrStrLen); michael@0: break; michael@0: } michael@0: default: { michael@0: status = U_INVALID_FORMAT_ERROR; michael@0: ures_close(currentBundle); michael@0: return; michael@0: } michael@0: } michael@0: fPattern.setTo(TRUE, resStr, resStrLen); michael@0: ures_close(currentBundle); michael@0: } michael@0: michael@0: // and if it includes _neither_, that's an error michael@0: else michael@0: status = U_INVALID_FORMAT_ERROR; michael@0: michael@0: // finally, finish initializing by creating a Calendar and a NumberFormat michael@0: initialize(locale, status); michael@0: } michael@0: michael@0: //---------------------------------------------------------------------- michael@0: michael@0: Calendar* michael@0: SimpleDateFormat::initializeCalendar(TimeZone* adoptZone, const Locale& locale, UErrorCode& status) michael@0: { michael@0: if(!U_FAILURE(status)) { michael@0: fCalendar = Calendar::createInstance(adoptZone?adoptZone:TimeZone::createDefault(), locale, status); michael@0: } michael@0: if (U_SUCCESS(status) && fCalendar == NULL) { michael@0: status = U_MEMORY_ALLOCATION_ERROR; michael@0: } michael@0: return fCalendar; michael@0: } michael@0: michael@0: void michael@0: SimpleDateFormat::initializeSymbols(const Locale& locale, Calendar* calendar, UErrorCode& status) michael@0: { michael@0: if(U_FAILURE(status)) { michael@0: fSymbols = NULL; michael@0: } else { michael@0: // pass in calendar type - use NULL (default) if no calendar set (or err). michael@0: fSymbols = new DateFormatSymbols(locale, calendar?calendar->getType() :NULL , status); michael@0: // Null pointer check michael@0: if (fSymbols == NULL) { michael@0: status = U_MEMORY_ALLOCATION_ERROR; michael@0: return; michael@0: } michael@0: } michael@0: } michael@0: michael@0: void michael@0: SimpleDateFormat::initialize(const Locale& locale, michael@0: UErrorCode& status) michael@0: { michael@0: if (U_FAILURE(status)) return; michael@0: michael@0: // We don't need to check that the row count is >= 1, since all 2d arrays have at michael@0: // least one row michael@0: fNumberFormat = NumberFormat::createInstance(locale, status); michael@0: if (fNumberFormat != NULL && U_SUCCESS(status)) michael@0: { michael@0: // no matter what the locale's default number format looked like, we want michael@0: // to modify it so that it doesn't use thousands separators, doesn't always michael@0: // show the decimal point, and recognizes integers only when parsing michael@0: michael@0: fNumberFormat->setGroupingUsed(FALSE); michael@0: DecimalFormat* decfmt = dynamic_cast(fNumberFormat); michael@0: if (decfmt != NULL) { michael@0: decfmt->setDecimalSeparatorAlwaysShown(FALSE); michael@0: } michael@0: fNumberFormat->setParseIntegerOnly(TRUE); michael@0: fNumberFormat->setMinimumFractionDigits(0); // To prevent "Jan 1.00, 1997.00" michael@0: michael@0: //fNumberFormat->setLenient(TRUE); // Java uses a custom DateNumberFormat to format/parse michael@0: michael@0: initNumberFormatters(locale,status); michael@0: michael@0: } michael@0: else if (U_SUCCESS(status)) michael@0: { michael@0: status = U_MISSING_RESOURCE_ERROR; michael@0: } michael@0: } michael@0: michael@0: /* Initialize the fields we use to disambiguate ambiguous years. Separate michael@0: * so we can call it from readObject(). michael@0: */ michael@0: void SimpleDateFormat::initializeDefaultCentury() michael@0: { michael@0: if(fCalendar) { michael@0: fHaveDefaultCentury = fCalendar->haveDefaultCentury(); michael@0: if(fHaveDefaultCentury) { michael@0: fDefaultCenturyStart = fCalendar->defaultCenturyStart(); michael@0: fDefaultCenturyStartYear = fCalendar->defaultCenturyStartYear(); michael@0: } else { michael@0: fDefaultCenturyStart = DBL_MIN; michael@0: fDefaultCenturyStartYear = -1; michael@0: } michael@0: } michael@0: } michael@0: michael@0: /* Define one-century window into which to disambiguate dates using michael@0: * two-digit years. Make public in JDK 1.2. michael@0: */ michael@0: void SimpleDateFormat::parseAmbiguousDatesAsAfter(UDate startDate, UErrorCode& status) michael@0: { michael@0: if(U_FAILURE(status)) { michael@0: return; michael@0: } michael@0: if(!fCalendar) { michael@0: status = U_ILLEGAL_ARGUMENT_ERROR; michael@0: return; michael@0: } michael@0: michael@0: fCalendar->setTime(startDate, status); michael@0: if(U_SUCCESS(status)) { michael@0: fHaveDefaultCentury = TRUE; michael@0: fDefaultCenturyStart = startDate; michael@0: fDefaultCenturyStartYear = fCalendar->get(UCAL_YEAR, status); michael@0: } michael@0: } michael@0: michael@0: //---------------------------------------------------------------------- michael@0: michael@0: UnicodeString& michael@0: SimpleDateFormat::format(Calendar& cal, UnicodeString& appendTo, FieldPosition& pos) const michael@0: { michael@0: UErrorCode status = U_ZERO_ERROR; michael@0: FieldPositionOnlyHandler handler(pos); michael@0: return _format(cal, appendTo, handler, status); michael@0: } michael@0: michael@0: //---------------------------------------------------------------------- michael@0: michael@0: UnicodeString& michael@0: SimpleDateFormat::format(Calendar& cal, UnicodeString& appendTo, michael@0: FieldPositionIterator* posIter, UErrorCode& status) const michael@0: { michael@0: FieldPositionIteratorHandler handler(posIter, status); michael@0: return _format(cal, appendTo, handler, status); michael@0: } michael@0: michael@0: //---------------------------------------------------------------------- michael@0: michael@0: UnicodeString& michael@0: SimpleDateFormat::_format(Calendar& cal, UnicodeString& appendTo, michael@0: FieldPositionHandler& handler, UErrorCode& status) const michael@0: { michael@0: if ( U_FAILURE(status) ) { michael@0: return appendTo; michael@0: } michael@0: Calendar* workCal = &cal; michael@0: Calendar* calClone = NULL; michael@0: if (&cal != fCalendar && uprv_strcmp(cal.getType(), fCalendar->getType()) != 0) { michael@0: // Different calendar type michael@0: // We use the time and time zone from the input calendar, but michael@0: // do not use the input calendar for field calculation. michael@0: calClone = fCalendar->clone(); michael@0: if (calClone != NULL) { michael@0: UDate t = cal.getTime(status); michael@0: calClone->setTime(t, status); michael@0: calClone->setTimeZone(cal.getTimeZone()); michael@0: workCal = calClone; michael@0: } else { michael@0: status = U_MEMORY_ALLOCATION_ERROR; michael@0: return appendTo; michael@0: } michael@0: } michael@0: michael@0: UBool inQuote = FALSE; michael@0: UChar prevCh = 0; michael@0: int32_t count = 0; michael@0: int32_t fieldNum = 0; michael@0: michael@0: // loop through the pattern string character by character michael@0: for (int32_t i = 0; i < fPattern.length() && U_SUCCESS(status); ++i) { michael@0: UChar ch = fPattern[i]; michael@0: michael@0: // Use subFormat() to format a repeated pattern character michael@0: // when a different pattern or non-pattern character is seen michael@0: if (ch != prevCh && count > 0) { michael@0: subFormat(appendTo, prevCh, count, fCapitalizationContext, fieldNum++, handler, *workCal, status); michael@0: count = 0; michael@0: } michael@0: if (ch == QUOTE) { michael@0: // Consecutive single quotes are a single quote literal, michael@0: // either outside of quotes or between quotes michael@0: if ((i+1) < fPattern.length() && fPattern[i+1] == QUOTE) { michael@0: appendTo += (UChar)QUOTE; michael@0: ++i; michael@0: } else { michael@0: inQuote = ! inQuote; michael@0: } michael@0: } michael@0: else if ( ! inQuote && ((ch >= 0x0061 /*'a'*/ && ch <= 0x007A /*'z'*/) michael@0: || (ch >= 0x0041 /*'A'*/ && ch <= 0x005A /*'Z'*/))) { michael@0: // ch is a date-time pattern character to be interpreted michael@0: // by subFormat(); count the number of times it is repeated michael@0: prevCh = ch; michael@0: ++count; michael@0: } michael@0: else { michael@0: // Append quoted characters and unquoted non-pattern characters michael@0: appendTo += ch; michael@0: } michael@0: } michael@0: michael@0: // Format the last item in the pattern, if any michael@0: if (count > 0) { michael@0: subFormat(appendTo, prevCh, count, fCapitalizationContext, fieldNum++, handler, *workCal, status); michael@0: } michael@0: michael@0: if (calClone != NULL) { michael@0: delete calClone; michael@0: } michael@0: michael@0: return appendTo; michael@0: } michael@0: michael@0: //---------------------------------------------------------------------- michael@0: michael@0: /* Map calendar field into calendar field level. michael@0: * the larger the level, the smaller the field unit. michael@0: * For example, UCAL_ERA level is 0, UCAL_YEAR level is 10, michael@0: * UCAL_MONTH level is 20. michael@0: * NOTE: if new fields adds in, the table needs to update. michael@0: */ michael@0: const int32_t michael@0: SimpleDateFormat::fgCalendarFieldToLevel[] = michael@0: { michael@0: /*GyM*/ 0, 10, 20, michael@0: /*wW*/ 20, 30, michael@0: /*dDEF*/ 30, 20, 30, 30, michael@0: /*ahHm*/ 40, 50, 50, 60, michael@0: /*sS..*/ 70, 80, michael@0: /*z?Y*/ 0, 0, 10, michael@0: /*eug*/ 30, 10, 0, michael@0: /*A*/ 40 michael@0: }; michael@0: michael@0: michael@0: /* Map calendar field LETTER into calendar field level. michael@0: * the larger the level, the smaller the field unit. michael@0: * NOTE: if new fields adds in, the table needs to update. michael@0: */ michael@0: const int32_t michael@0: SimpleDateFormat::fgPatternCharToLevel[] = { michael@0: // A B C D E F G H I J K L M N O michael@0: -1, 40, -1, -1, 20, 30, 30, 0, 50, -1, -1, 50, 20, 20, -1, 0, michael@0: // P Q R S T U V W X Y Z michael@0: -1, 20, -1, 80, -1, 10, 0, 30, 0, 10, 0, -1, -1, -1, -1, -1, michael@0: // a b c d e f g h i j k l m n o michael@0: -1, 40, -1, 30, 30, 30, -1, 0, 50, -1, -1, 50, -1, 60, -1, -1, michael@0: // p q r s t u v w x y z michael@0: -1, 20, -1, 70, -1, 10, 0, 20, 0, 10, 0, -1, -1, -1, -1, -1 michael@0: }; michael@0: michael@0: michael@0: // Map index into pattern character string to Calendar field number. michael@0: const UCalendarDateFields michael@0: SimpleDateFormat::fgPatternIndexToCalendarField[] = michael@0: { michael@0: /*GyM*/ UCAL_ERA, UCAL_YEAR, UCAL_MONTH, michael@0: /*dkH*/ UCAL_DATE, UCAL_HOUR_OF_DAY, UCAL_HOUR_OF_DAY, michael@0: /*msS*/ UCAL_MINUTE, UCAL_SECOND, UCAL_MILLISECOND, michael@0: /*EDF*/ UCAL_DAY_OF_WEEK, UCAL_DAY_OF_YEAR, UCAL_DAY_OF_WEEK_IN_MONTH, michael@0: /*wWa*/ UCAL_WEEK_OF_YEAR, UCAL_WEEK_OF_MONTH, UCAL_AM_PM, michael@0: /*hKz*/ UCAL_HOUR, UCAL_HOUR, UCAL_ZONE_OFFSET, michael@0: /*Yeu*/ UCAL_YEAR_WOY, UCAL_DOW_LOCAL, UCAL_EXTENDED_YEAR, michael@0: /*gAZ*/ UCAL_JULIAN_DAY, UCAL_MILLISECONDS_IN_DAY, UCAL_ZONE_OFFSET, michael@0: /*v*/ UCAL_ZONE_OFFSET, michael@0: /*c*/ UCAL_DOW_LOCAL, michael@0: /*L*/ UCAL_MONTH, michael@0: /*Q*/ UCAL_MONTH, michael@0: /*q*/ UCAL_MONTH, michael@0: /*V*/ UCAL_ZONE_OFFSET, michael@0: /*U*/ UCAL_YEAR, michael@0: /*O*/ UCAL_ZONE_OFFSET, michael@0: /*Xx*/ UCAL_ZONE_OFFSET, UCAL_ZONE_OFFSET, michael@0: }; michael@0: michael@0: // Map index into pattern character string to DateFormat field number michael@0: const UDateFormatField michael@0: SimpleDateFormat::fgPatternIndexToDateFormatField[] = { michael@0: /*GyM*/ UDAT_ERA_FIELD, UDAT_YEAR_FIELD, UDAT_MONTH_FIELD, michael@0: /*dkH*/ UDAT_DATE_FIELD, UDAT_HOUR_OF_DAY1_FIELD, UDAT_HOUR_OF_DAY0_FIELD, michael@0: /*msS*/ UDAT_MINUTE_FIELD, UDAT_SECOND_FIELD, UDAT_FRACTIONAL_SECOND_FIELD, michael@0: /*EDF*/ UDAT_DAY_OF_WEEK_FIELD, UDAT_DAY_OF_YEAR_FIELD, UDAT_DAY_OF_WEEK_IN_MONTH_FIELD, michael@0: /*wWa*/ UDAT_WEEK_OF_YEAR_FIELD, UDAT_WEEK_OF_MONTH_FIELD, UDAT_AM_PM_FIELD, michael@0: /*hKz*/ UDAT_HOUR1_FIELD, UDAT_HOUR0_FIELD, UDAT_TIMEZONE_FIELD, michael@0: /*Yeu*/ UDAT_YEAR_WOY_FIELD, UDAT_DOW_LOCAL_FIELD, UDAT_EXTENDED_YEAR_FIELD, michael@0: /*gAZ*/ UDAT_JULIAN_DAY_FIELD, UDAT_MILLISECONDS_IN_DAY_FIELD, UDAT_TIMEZONE_RFC_FIELD, michael@0: /*v*/ UDAT_TIMEZONE_GENERIC_FIELD, michael@0: /*c*/ UDAT_STANDALONE_DAY_FIELD, michael@0: /*L*/ UDAT_STANDALONE_MONTH_FIELD, michael@0: /*Q*/ UDAT_QUARTER_FIELD, michael@0: /*q*/ UDAT_STANDALONE_QUARTER_FIELD, michael@0: /*V*/ UDAT_TIMEZONE_SPECIAL_FIELD, michael@0: /*U*/ UDAT_YEAR_NAME_FIELD, michael@0: /*O*/ UDAT_TIMEZONE_LOCALIZED_GMT_OFFSET_FIELD, michael@0: /*Xx*/ UDAT_TIMEZONE_ISO_FIELD, UDAT_TIMEZONE_ISO_LOCAL_FIELD, michael@0: }; michael@0: michael@0: //---------------------------------------------------------------------- michael@0: michael@0: /** michael@0: * Append symbols[value] to dst. Make sure the array index is not out michael@0: * of bounds. michael@0: */ michael@0: static inline void michael@0: _appendSymbol(UnicodeString& dst, michael@0: int32_t value, michael@0: const UnicodeString* symbols, michael@0: int32_t symbolsCount) { michael@0: U_ASSERT(0 <= value && value < symbolsCount); michael@0: if (0 <= value && value < symbolsCount) { michael@0: dst += symbols[value]; michael@0: } michael@0: } michael@0: michael@0: static inline void michael@0: _appendSymbolWithMonthPattern(UnicodeString& dst, int32_t value, const UnicodeString* symbols, int32_t symbolsCount, michael@0: const UnicodeString* monthPattern, UErrorCode& status) { michael@0: U_ASSERT(0 <= value && value < symbolsCount); michael@0: if (0 <= value && value < symbolsCount) { michael@0: if (monthPattern == NULL) { michael@0: dst += symbols[value]; michael@0: } else { michael@0: Formattable monthName((const UnicodeString&)(symbols[value])); michael@0: MessageFormat::format(*monthPattern, &monthName, 1, dst, status); michael@0: } michael@0: } michael@0: } michael@0: michael@0: //---------------------------------------------------------------------- michael@0: void michael@0: SimpleDateFormat::initNumberFormatters(const Locale &locale,UErrorCode &status) { michael@0: if (U_FAILURE(status)) { michael@0: return; michael@0: } michael@0: if ( fDateOverride.isBogus() && fTimeOverride.isBogus() ) { michael@0: return; michael@0: } michael@0: umtx_lock(&LOCK); michael@0: if (fNumberFormatters == NULL) { michael@0: fNumberFormatters = (NumberFormat**)uprv_malloc(UDAT_FIELD_COUNT * sizeof(NumberFormat*)); michael@0: if (fNumberFormatters) { michael@0: for (int32_t i = 0; i < UDAT_FIELD_COUNT; i++) { michael@0: fNumberFormatters[i] = fNumberFormat; michael@0: } michael@0: } else { michael@0: status = U_MEMORY_ALLOCATION_ERROR; michael@0: } michael@0: } michael@0: umtx_unlock(&LOCK); michael@0: michael@0: processOverrideString(locale,fDateOverride,kOvrStrDate,status); michael@0: processOverrideString(locale,fTimeOverride,kOvrStrTime,status); michael@0: michael@0: } michael@0: michael@0: void michael@0: SimpleDateFormat::processOverrideString(const Locale &locale, const UnicodeString &str, int8_t type, UErrorCode &status) { michael@0: if (str.isBogus()) { michael@0: return; michael@0: } michael@0: int32_t start = 0; michael@0: int32_t len; michael@0: UnicodeString nsName; michael@0: UnicodeString ovrField; michael@0: UBool moreToProcess = TRUE; michael@0: michael@0: while (moreToProcess) { michael@0: int32_t delimiterPosition = str.indexOf((UChar)ULOC_KEYWORD_ITEM_SEPARATOR_UNICODE,start); michael@0: if (delimiterPosition == -1) { michael@0: moreToProcess = FALSE; michael@0: len = str.length() - start; michael@0: } else { michael@0: len = delimiterPosition - start; michael@0: } michael@0: UnicodeString currentString(str,start,len); michael@0: int32_t equalSignPosition = currentString.indexOf((UChar)ULOC_KEYWORD_ASSIGN_UNICODE,0); michael@0: if (equalSignPosition == -1) { // Simple override string such as "hebrew" michael@0: nsName.setTo(currentString); michael@0: ovrField.setToBogus(); michael@0: } else { // Field specific override string such as "y=hebrew" michael@0: nsName.setTo(currentString,equalSignPosition+1); michael@0: ovrField.setTo(currentString,0,1); // We just need the first character. michael@0: } michael@0: michael@0: int32_t nsNameHash = nsName.hashCode(); michael@0: // See if the numbering system is in the override list, if not, then add it. michael@0: NSOverride *cur = fOverrideList; michael@0: NumberFormat *nf = NULL; michael@0: UBool found = FALSE; michael@0: while ( cur && !found ) { michael@0: if ( cur->hash == nsNameHash ) { michael@0: nf = cur->nf; michael@0: found = TRUE; michael@0: } michael@0: cur = cur->next; michael@0: } michael@0: michael@0: if (!found) { michael@0: cur = (NSOverride *)uprv_malloc(sizeof(NSOverride)); michael@0: if (cur) { michael@0: char kw[ULOC_KEYWORD_AND_VALUES_CAPACITY]; michael@0: uprv_strcpy(kw,"numbers="); michael@0: nsName.extract(0,len,kw+8,ULOC_KEYWORD_AND_VALUES_CAPACITY-8,US_INV); michael@0: michael@0: Locale ovrLoc(locale.getLanguage(),locale.getCountry(),locale.getVariant(),kw); michael@0: nf = NumberFormat::createInstance(ovrLoc,status); michael@0: michael@0: // no matter what the locale's default number format looked like, we want michael@0: // to modify it so that it doesn't use thousands separators, doesn't always michael@0: // show the decimal point, and recognizes integers only when parsing michael@0: michael@0: if (U_SUCCESS(status)) { michael@0: nf->setGroupingUsed(FALSE); michael@0: DecimalFormat* decfmt = dynamic_cast(nf); michael@0: if (decfmt != NULL) { michael@0: decfmt->setDecimalSeparatorAlwaysShown(FALSE); michael@0: } michael@0: nf->setParseIntegerOnly(TRUE); michael@0: nf->setMinimumFractionDigits(0); // To prevent "Jan 1.00, 1997.00" michael@0: michael@0: cur->nf = nf; michael@0: cur->hash = nsNameHash; michael@0: cur->next = fOverrideList; michael@0: fOverrideList = cur; michael@0: } michael@0: else { michael@0: // clean up before returning michael@0: if (cur != NULL) { michael@0: uprv_free(cur); michael@0: } michael@0: return; michael@0: } michael@0: michael@0: } else { michael@0: status = U_MEMORY_ALLOCATION_ERROR; michael@0: return; michael@0: } michael@0: } michael@0: michael@0: // Now that we have an appropriate number formatter, fill in the appropriate spaces in the michael@0: // number formatters table. michael@0: michael@0: if (ovrField.isBogus()) { michael@0: switch (type) { michael@0: case kOvrStrDate: michael@0: case kOvrStrBoth: { michael@0: for ( int8_t i=0 ; ifNarrowEras, fSymbols->fNarrowErasCount); michael@0: capContextUsageType = DateFormatSymbols::kCapContextUsageEraNarrow; michael@0: } else if (count == 4) { michael@0: _appendSymbol(appendTo, value, fSymbols->fEraNames, fSymbols->fEraNamesCount); michael@0: capContextUsageType = DateFormatSymbols::kCapContextUsageEraWide; michael@0: } else { michael@0: _appendSymbol(appendTo, value, fSymbols->fEras, fSymbols->fErasCount); michael@0: capContextUsageType = DateFormatSymbols::kCapContextUsageEraAbbrev; michael@0: } michael@0: } michael@0: break; michael@0: michael@0: case UDAT_YEAR_NAME_FIELD: michael@0: if (fSymbols->fShortYearNames != NULL && value <= fSymbols->fShortYearNamesCount) { michael@0: // the Calendar YEAR field runs 1 through 60 for cyclic years michael@0: _appendSymbol(appendTo, value - 1, fSymbols->fShortYearNames, fSymbols->fShortYearNamesCount); michael@0: break; michael@0: } michael@0: // else fall through to numeric year handling, do not break here michael@0: michael@0: // OLD: for "yyyy", write out the whole year; for "yy", write out the last 2 digits michael@0: // NEW: UTS#35: michael@0: //Year y yy yyy yyyy yyyyy michael@0: //AD 1 1 01 001 0001 00001 michael@0: //AD 12 12 12 012 0012 00012 michael@0: //AD 123 123 23 123 0123 00123 michael@0: //AD 1234 1234 34 1234 1234 01234 michael@0: //AD 12345 12345 45 12345 12345 12345 michael@0: case UDAT_YEAR_FIELD: michael@0: case UDAT_YEAR_WOY_FIELD: michael@0: if (fDateOverride.compare(hebr)==0 && value>HEBREW_CAL_CUR_MILLENIUM_START_YEAR && valueisLeapYear(hc->get(UCAL_YEAR,status)) && value == 6 && count >= 3 ) michael@0: value = 13; // Show alternate form for Adar II in leap years in Hebrew calendar. michael@0: if (!hc->isLeapYear(hc->get(UCAL_YEAR,status)) && value >= 6 && count < 3 ) michael@0: value--; // Adjust the month number down 1 in Hebrew non-leap years, i.e. Adar is 6, not 7. michael@0: } michael@0: { michael@0: int32_t isLeapMonth = (fSymbols->fLeapMonthPatterns != NULL && fSymbols->fLeapMonthPatternsCount >= DateFormatSymbols::kMonthPatternsCount)? michael@0: cal.get(UCAL_IS_LEAP_MONTH, status): 0; michael@0: // should consolidate the next section by using arrays of pointers & counts for the right symbols... michael@0: if (count == 5) { michael@0: if (patternCharIndex == UDAT_MONTH_FIELD) { michael@0: _appendSymbolWithMonthPattern(appendTo, value, fSymbols->fNarrowMonths, fSymbols->fNarrowMonthsCount, michael@0: (isLeapMonth!=0)? &(fSymbols->fLeapMonthPatterns[DateFormatSymbols::kLeapMonthPatternFormatNarrow]): NULL, status); michael@0: } else { michael@0: _appendSymbolWithMonthPattern(appendTo, value, fSymbols->fStandaloneNarrowMonths, fSymbols->fStandaloneNarrowMonthsCount, michael@0: (isLeapMonth!=0)? &(fSymbols->fLeapMonthPatterns[DateFormatSymbols::kLeapMonthPatternStandaloneNarrow]): NULL, status); michael@0: } michael@0: capContextUsageType = DateFormatSymbols::kCapContextUsageMonthNarrow; michael@0: } else if (count == 4) { michael@0: if (patternCharIndex == UDAT_MONTH_FIELD) { michael@0: _appendSymbolWithMonthPattern(appendTo, value, fSymbols->fMonths, fSymbols->fMonthsCount, michael@0: (isLeapMonth!=0)? &(fSymbols->fLeapMonthPatterns[DateFormatSymbols::kLeapMonthPatternFormatWide]): NULL, status); michael@0: capContextUsageType = DateFormatSymbols::kCapContextUsageMonthFormat; michael@0: } else { michael@0: _appendSymbolWithMonthPattern(appendTo, value, fSymbols->fStandaloneMonths, fSymbols->fStandaloneMonthsCount, michael@0: (isLeapMonth!=0)? &(fSymbols->fLeapMonthPatterns[DateFormatSymbols::kLeapMonthPatternStandaloneWide]): NULL, status); michael@0: capContextUsageType = DateFormatSymbols::kCapContextUsageMonthStandalone; michael@0: } michael@0: } else if (count == 3) { michael@0: if (patternCharIndex == UDAT_MONTH_FIELD) { michael@0: _appendSymbolWithMonthPattern(appendTo, value, fSymbols->fShortMonths, fSymbols->fShortMonthsCount, michael@0: (isLeapMonth!=0)? &(fSymbols->fLeapMonthPatterns[DateFormatSymbols::kLeapMonthPatternFormatAbbrev]): NULL, status); michael@0: capContextUsageType = DateFormatSymbols::kCapContextUsageMonthFormat; michael@0: } else { michael@0: _appendSymbolWithMonthPattern(appendTo, value, fSymbols->fStandaloneShortMonths, fSymbols->fStandaloneShortMonthsCount, michael@0: (isLeapMonth!=0)? &(fSymbols->fLeapMonthPatterns[DateFormatSymbols::kLeapMonthPatternStandaloneAbbrev]): NULL, status); michael@0: capContextUsageType = DateFormatSymbols::kCapContextUsageMonthStandalone; michael@0: } michael@0: } else { michael@0: UnicodeString monthNumber; michael@0: zeroPaddingNumber(currentNumberFormat,monthNumber, value + 1, count, maxIntCount); michael@0: _appendSymbolWithMonthPattern(appendTo, 0, &monthNumber, 1, michael@0: (isLeapMonth!=0)? &(fSymbols->fLeapMonthPatterns[DateFormatSymbols::kLeapMonthPatternNumeric]): NULL, status); michael@0: } michael@0: } michael@0: break; michael@0: michael@0: // for "k" and "kk", write out the hour, adjusting midnight to appear as "24" michael@0: case UDAT_HOUR_OF_DAY1_FIELD: michael@0: if (value == 0) michael@0: zeroPaddingNumber(currentNumberFormat,appendTo, cal.getMaximum(UCAL_HOUR_OF_DAY) + 1, count, maxIntCount); michael@0: else michael@0: zeroPaddingNumber(currentNumberFormat,appendTo, value, count, maxIntCount); michael@0: break; michael@0: michael@0: case UDAT_FRACTIONAL_SECOND_FIELD: michael@0: // Fractional seconds left-justify michael@0: { michael@0: currentNumberFormat->setMinimumIntegerDigits((count > 3) ? 3 : count); michael@0: currentNumberFormat->setMaximumIntegerDigits(maxIntCount); michael@0: if (count == 1) { michael@0: value /= 100; michael@0: } else if (count == 2) { michael@0: value /= 10; michael@0: } michael@0: FieldPosition p(0); michael@0: currentNumberFormat->format(value, appendTo, p); michael@0: if (count > 3) { michael@0: currentNumberFormat->setMinimumIntegerDigits(count - 3); michael@0: currentNumberFormat->format((int32_t)0, appendTo, p); michael@0: } michael@0: } michael@0: break; michael@0: michael@0: // for "ee" or "e", use local numeric day-of-the-week michael@0: // for "EEEEEE" or "eeeeee", write out the short day-of-the-week name michael@0: // for "EEEEE" or "eeeee", write out the narrow day-of-the-week name michael@0: // for "EEEE" or "eeee", write out the wide day-of-the-week name michael@0: // for "EEE" or "EE" or "E" or "eee", write out the abbreviated day-of-the-week name michael@0: case UDAT_DOW_LOCAL_FIELD: michael@0: if ( count < 3 ) { michael@0: zeroPaddingNumber(currentNumberFormat,appendTo, value, count, maxIntCount); michael@0: break; michael@0: } michael@0: // fall through to EEEEE-EEE handling, but for that we don't want local day-of-week, michael@0: // we want standard day-of-week, so first fix value to work for EEEEE-EEE. michael@0: value = cal.get(UCAL_DAY_OF_WEEK, status); michael@0: if (U_FAILURE(status)) { michael@0: return; michael@0: } michael@0: // fall through, do not break here michael@0: case UDAT_DAY_OF_WEEK_FIELD: michael@0: if (count == 5) { michael@0: _appendSymbol(appendTo, value, fSymbols->fNarrowWeekdays, michael@0: fSymbols->fNarrowWeekdaysCount); michael@0: capContextUsageType = DateFormatSymbols::kCapContextUsageDayNarrow; michael@0: } else if (count == 4) { michael@0: _appendSymbol(appendTo, value, fSymbols->fWeekdays, michael@0: fSymbols->fWeekdaysCount); michael@0: capContextUsageType = DateFormatSymbols::kCapContextUsageDayFormat; michael@0: } else if (count == 6) { michael@0: _appendSymbol(appendTo, value, fSymbols->fShorterWeekdays, michael@0: fSymbols->fShorterWeekdaysCount); michael@0: capContextUsageType = DateFormatSymbols::kCapContextUsageDayFormat; michael@0: } else { michael@0: _appendSymbol(appendTo, value, fSymbols->fShortWeekdays, michael@0: fSymbols->fShortWeekdaysCount); michael@0: capContextUsageType = DateFormatSymbols::kCapContextUsageDayFormat; michael@0: } michael@0: break; michael@0: michael@0: // for "ccc", write out the abbreviated day-of-the-week name michael@0: // for "cccc", write out the wide day-of-the-week name michael@0: // for "ccccc", use the narrow day-of-the-week name michael@0: // for "ccccc", use the short day-of-the-week name michael@0: case UDAT_STANDALONE_DAY_FIELD: michael@0: if ( count < 3 ) { michael@0: zeroPaddingNumber(currentNumberFormat,appendTo, value, 1, maxIntCount); michael@0: break; michael@0: } michael@0: // fall through to alpha DOW handling, but for that we don't want local day-of-week, michael@0: // we want standard day-of-week, so first fix value. michael@0: value = cal.get(UCAL_DAY_OF_WEEK, status); michael@0: if (U_FAILURE(status)) { michael@0: return; michael@0: } michael@0: if (count == 5) { michael@0: _appendSymbol(appendTo, value, fSymbols->fStandaloneNarrowWeekdays, michael@0: fSymbols->fStandaloneNarrowWeekdaysCount); michael@0: capContextUsageType = DateFormatSymbols::kCapContextUsageDayNarrow; michael@0: } else if (count == 4) { michael@0: _appendSymbol(appendTo, value, fSymbols->fStandaloneWeekdays, michael@0: fSymbols->fStandaloneWeekdaysCount); michael@0: capContextUsageType = DateFormatSymbols::kCapContextUsageDayStandalone; michael@0: } else if (count == 6) { michael@0: _appendSymbol(appendTo, value, fSymbols->fStandaloneShorterWeekdays, michael@0: fSymbols->fStandaloneShorterWeekdaysCount); michael@0: capContextUsageType = DateFormatSymbols::kCapContextUsageDayStandalone; michael@0: } else { // count == 3 michael@0: _appendSymbol(appendTo, value, fSymbols->fStandaloneShortWeekdays, michael@0: fSymbols->fStandaloneShortWeekdaysCount); michael@0: capContextUsageType = DateFormatSymbols::kCapContextUsageDayStandalone; michael@0: } michael@0: break; michael@0: michael@0: // for and "a" symbol, write out the whole AM/PM string michael@0: case UDAT_AM_PM_FIELD: michael@0: _appendSymbol(appendTo, value, fSymbols->fAmPms, michael@0: fSymbols->fAmPmsCount); michael@0: break; michael@0: michael@0: // for "h" and "hh", write out the hour, adjusting noon and midnight to show up michael@0: // as "12" michael@0: case UDAT_HOUR1_FIELD: michael@0: if (value == 0) michael@0: zeroPaddingNumber(currentNumberFormat,appendTo, cal.getLeastMaximum(UCAL_HOUR) + 1, count, maxIntCount); michael@0: else michael@0: zeroPaddingNumber(currentNumberFormat,appendTo, value, count, maxIntCount); michael@0: break; michael@0: michael@0: case UDAT_TIMEZONE_FIELD: // 'z' michael@0: case UDAT_TIMEZONE_RFC_FIELD: // 'Z' michael@0: case UDAT_TIMEZONE_GENERIC_FIELD: // 'v' michael@0: case UDAT_TIMEZONE_SPECIAL_FIELD: // 'V' michael@0: case UDAT_TIMEZONE_LOCALIZED_GMT_OFFSET_FIELD: // 'O' michael@0: case UDAT_TIMEZONE_ISO_FIELD: // 'X' michael@0: case UDAT_TIMEZONE_ISO_LOCAL_FIELD: // 'x' michael@0: { michael@0: UnicodeString zoneString; michael@0: const TimeZone& tz = cal.getTimeZone(); michael@0: UDate date = cal.getTime(status); michael@0: if (U_SUCCESS(status)) { michael@0: if (patternCharIndex == UDAT_TIMEZONE_FIELD) { michael@0: if (count < 4) { michael@0: // "z", "zz", "zzz" michael@0: tzFormat()->format(UTZFMT_STYLE_SPECIFIC_SHORT, tz, date, zoneString); michael@0: capContextUsageType = DateFormatSymbols::kCapContextUsageMetazoneShort; michael@0: } else { michael@0: // "zzzz" or longer michael@0: tzFormat()->format(UTZFMT_STYLE_SPECIFIC_LONG, tz, date, zoneString); michael@0: capContextUsageType = DateFormatSymbols::kCapContextUsageMetazoneLong; michael@0: } michael@0: } michael@0: else if (patternCharIndex == UDAT_TIMEZONE_RFC_FIELD) { michael@0: if (count < 4) { michael@0: // "Z" michael@0: tzFormat()->format(UTZFMT_STYLE_ISO_BASIC_LOCAL_FULL, tz, date, zoneString); michael@0: } else if (count == 5) { michael@0: // "ZZZZZ" michael@0: tzFormat()->format(UTZFMT_STYLE_ISO_EXTENDED_FULL, tz, date, zoneString); michael@0: } else { michael@0: // "ZZ", "ZZZ", "ZZZZ" michael@0: tzFormat()->format(UTZFMT_STYLE_LOCALIZED_GMT, tz, date, zoneString); michael@0: } michael@0: } michael@0: else if (patternCharIndex == UDAT_TIMEZONE_GENERIC_FIELD) { michael@0: if (count == 1) { michael@0: // "v" michael@0: tzFormat()->format(UTZFMT_STYLE_GENERIC_SHORT, tz, date, zoneString); michael@0: capContextUsageType = DateFormatSymbols::kCapContextUsageMetazoneShort; michael@0: } else if (count == 4) { michael@0: // "vvvv" michael@0: tzFormat()->format(UTZFMT_STYLE_GENERIC_LONG, tz, date, zoneString); michael@0: capContextUsageType = DateFormatSymbols::kCapContextUsageMetazoneLong; michael@0: } michael@0: } michael@0: else if (patternCharIndex == UDAT_TIMEZONE_SPECIAL_FIELD) { michael@0: if (count == 1) { michael@0: // "V" michael@0: tzFormat()->format(UTZFMT_STYLE_ZONE_ID_SHORT, tz, date, zoneString); michael@0: } else if (count == 2) { michael@0: // "VV" michael@0: tzFormat()->format(UTZFMT_STYLE_ZONE_ID, tz, date, zoneString); michael@0: } else if (count == 3) { michael@0: // "VVV" michael@0: tzFormat()->format(UTZFMT_STYLE_EXEMPLAR_LOCATION, tz, date, zoneString); michael@0: } else if (count == 4) { michael@0: // "VVVV" michael@0: tzFormat()->format(UTZFMT_STYLE_GENERIC_LOCATION, tz, date, zoneString); michael@0: capContextUsageType = DateFormatSymbols::kCapContextUsageZoneLong; michael@0: } michael@0: } michael@0: else if (patternCharIndex == UDAT_TIMEZONE_LOCALIZED_GMT_OFFSET_FIELD) { michael@0: if (count == 1) { michael@0: // "O" michael@0: tzFormat()->format(UTZFMT_STYLE_LOCALIZED_GMT_SHORT, tz, date, zoneString); michael@0: } else if (count == 4) { michael@0: // "OOOO" michael@0: tzFormat()->format(UTZFMT_STYLE_LOCALIZED_GMT, tz, date, zoneString); michael@0: } michael@0: } michael@0: else if (patternCharIndex == UDAT_TIMEZONE_ISO_FIELD) { michael@0: if (count == 1) { michael@0: // "X" michael@0: tzFormat()->format(UTZFMT_STYLE_ISO_BASIC_SHORT, tz, date, zoneString); michael@0: } else if (count == 2) { michael@0: // "XX" michael@0: tzFormat()->format(UTZFMT_STYLE_ISO_BASIC_FIXED, tz, date, zoneString); michael@0: } else if (count == 3) { michael@0: // "XXX" michael@0: tzFormat()->format(UTZFMT_STYLE_ISO_EXTENDED_FIXED, tz, date, zoneString); michael@0: } else if (count == 4) { michael@0: // "XXXX" michael@0: tzFormat()->format(UTZFMT_STYLE_ISO_BASIC_FULL, tz, date, zoneString); michael@0: } else if (count == 5) { michael@0: // "XXXXX" michael@0: tzFormat()->format(UTZFMT_STYLE_ISO_EXTENDED_FULL, tz, date, zoneString); michael@0: } michael@0: } michael@0: else if (patternCharIndex == UDAT_TIMEZONE_ISO_LOCAL_FIELD) { michael@0: if (count == 1) { michael@0: // "x" michael@0: tzFormat()->format(UTZFMT_STYLE_ISO_BASIC_LOCAL_SHORT, tz, date, zoneString); michael@0: } else if (count == 2) { michael@0: // "xx" michael@0: tzFormat()->format(UTZFMT_STYLE_ISO_BASIC_LOCAL_FIXED, tz, date, zoneString); michael@0: } else if (count == 3) { michael@0: // "xxx" michael@0: tzFormat()->format(UTZFMT_STYLE_ISO_EXTENDED_LOCAL_FIXED, tz, date, zoneString); michael@0: } else if (count == 4) { michael@0: // "xxxx" michael@0: tzFormat()->format(UTZFMT_STYLE_ISO_BASIC_LOCAL_FULL, tz, date, zoneString); michael@0: } else if (count == 5) { michael@0: // "xxxxx" michael@0: tzFormat()->format(UTZFMT_STYLE_ISO_EXTENDED_LOCAL_FULL, tz, date, zoneString); michael@0: } michael@0: } michael@0: else { michael@0: U_ASSERT(FALSE); michael@0: } michael@0: } michael@0: appendTo += zoneString; michael@0: } michael@0: break; michael@0: michael@0: case UDAT_QUARTER_FIELD: michael@0: if (count >= 4) michael@0: _appendSymbol(appendTo, value/3, fSymbols->fQuarters, michael@0: fSymbols->fQuartersCount); michael@0: else if (count == 3) michael@0: _appendSymbol(appendTo, value/3, fSymbols->fShortQuarters, michael@0: fSymbols->fShortQuartersCount); michael@0: else michael@0: zeroPaddingNumber(currentNumberFormat,appendTo, (value/3) + 1, count, maxIntCount); michael@0: break; michael@0: michael@0: case UDAT_STANDALONE_QUARTER_FIELD: michael@0: if (count >= 4) michael@0: _appendSymbol(appendTo, value/3, fSymbols->fStandaloneQuarters, michael@0: fSymbols->fStandaloneQuartersCount); michael@0: else if (count == 3) michael@0: _appendSymbol(appendTo, value/3, fSymbols->fStandaloneShortQuarters, michael@0: fSymbols->fStandaloneShortQuartersCount); michael@0: else michael@0: zeroPaddingNumber(currentNumberFormat,appendTo, (value/3) + 1, count, maxIntCount); michael@0: break; michael@0: michael@0: michael@0: // all of the other pattern symbols can be formatted as simple numbers with michael@0: // appropriate zero padding michael@0: default: michael@0: zeroPaddingNumber(currentNumberFormat,appendTo, value, count, maxIntCount); michael@0: break; michael@0: } michael@0: #if !UCONFIG_NO_BREAK_ITERATION michael@0: if (fieldNum == 0) { michael@0: // first field, check to see whether we need to titlecase it michael@0: UBool titlecase = FALSE; michael@0: switch (capitalizationContext) { michael@0: case UDISPCTX_CAPITALIZATION_FOR_BEGINNING_OF_SENTENCE: michael@0: titlecase = TRUE; michael@0: break; michael@0: case UDISPCTX_CAPITALIZATION_FOR_UI_LIST_OR_MENU: michael@0: titlecase = fSymbols->fCapitalization[capContextUsageType][0]; michael@0: break; michael@0: case UDISPCTX_CAPITALIZATION_FOR_STANDALONE: michael@0: titlecase = fSymbols->fCapitalization[capContextUsageType][1]; michael@0: break; michael@0: default: michael@0: // titlecase = FALSE; michael@0: break; michael@0: } michael@0: if (titlecase) { michael@0: UnicodeString firstField(appendTo, beginOffset); michael@0: firstField.toTitle(NULL, fLocale, U_TITLECASE_NO_LOWERCASE | U_TITLECASE_NO_BREAK_ADJUSTMENT); michael@0: appendTo.replaceBetween(beginOffset, appendTo.length(), firstField); michael@0: } michael@0: } michael@0: #endif michael@0: michael@0: handler.addAttribute(fgPatternIndexToDateFormatField[patternCharIndex], beginOffset, appendTo.length()); michael@0: } michael@0: michael@0: //---------------------------------------------------------------------- michael@0: michael@0: NumberFormat * michael@0: SimpleDateFormat::getNumberFormatByIndex(UDateFormatField index) const { michael@0: if (fNumberFormatters != NULL) { michael@0: return fNumberFormatters[index]; michael@0: } else { michael@0: return fNumberFormat; michael@0: } michael@0: } michael@0: michael@0: //---------------------------------------------------------------------- michael@0: void michael@0: SimpleDateFormat::zeroPaddingNumber(NumberFormat *currentNumberFormat,UnicodeString &appendTo, michael@0: int32_t value, int32_t minDigits, int32_t maxDigits) const michael@0: { michael@0: if (currentNumberFormat!=NULL) { michael@0: FieldPosition pos(0); michael@0: michael@0: currentNumberFormat->setMinimumIntegerDigits(minDigits); michael@0: currentNumberFormat->setMaximumIntegerDigits(maxDigits); michael@0: currentNumberFormat->format(value, appendTo, pos); // 3rd arg is there to speed up processing michael@0: } michael@0: } michael@0: michael@0: //---------------------------------------------------------------------- michael@0: michael@0: /** michael@0: * Return true if the given format character, occuring count michael@0: * times, represents a numeric field. michael@0: */ michael@0: UBool SimpleDateFormat::isNumeric(UChar formatChar, int32_t count) { michael@0: return DateFormatSymbols::isNumericPatternChar(formatChar, count); michael@0: } michael@0: michael@0: UBool michael@0: SimpleDateFormat::isAtNumericField(const UnicodeString &pattern, int32_t patternOffset) { michael@0: if (patternOffset >= pattern.length()) { michael@0: // not at any field michael@0: return FALSE; michael@0: } michael@0: UChar ch = pattern.charAt(patternOffset); michael@0: UDateFormatField f = DateFormatSymbols::getPatternCharIndex(ch); michael@0: if (f == UDAT_FIELD_COUNT) { michael@0: // not at any field michael@0: return FALSE; michael@0: } michael@0: int32_t i = patternOffset; michael@0: while (pattern.charAt(++i) == ch) {} michael@0: return DateFormatSymbols::isNumericField(f, i - patternOffset); michael@0: } michael@0: michael@0: UBool michael@0: SimpleDateFormat::isAfterNonNumericField(const UnicodeString &pattern, int32_t patternOffset) { michael@0: if (patternOffset <= 0) { michael@0: // not after any field michael@0: return FALSE; michael@0: } michael@0: UChar ch = pattern.charAt(--patternOffset); michael@0: UDateFormatField f = DateFormatSymbols::getPatternCharIndex(ch); michael@0: if (f == UDAT_FIELD_COUNT) { michael@0: // not after any field michael@0: return FALSE; michael@0: } michael@0: int32_t i = patternOffset; michael@0: while (pattern.charAt(--i) == ch) {} michael@0: return !DateFormatSymbols::isNumericField(f, patternOffset - i); michael@0: } michael@0: michael@0: void michael@0: SimpleDateFormat::parse(const UnicodeString& text, Calendar& cal, ParsePosition& parsePos) const michael@0: { michael@0: UErrorCode status = U_ZERO_ERROR; michael@0: int32_t pos = parsePos.getIndex(); michael@0: int32_t start = pos; michael@0: michael@0: UBool ambiguousYear[] = { FALSE }; michael@0: int32_t saveHebrewMonth = -1; michael@0: int32_t count = 0; michael@0: michael@0: // hack, reset tztype, cast away const michael@0: ((SimpleDateFormat*)this)->tztype = UTZFMT_TIME_TYPE_UNKNOWN; michael@0: michael@0: // For parsing abutting numeric fields. 'abutPat' is the michael@0: // offset into 'pattern' of the first of 2 or more abutting michael@0: // numeric fields. 'abutStart' is the offset into 'text' michael@0: // where parsing the fields begins. 'abutPass' starts off as 0 michael@0: // and increments each time we try to parse the fields. michael@0: int32_t abutPat = -1; // If >=0, we are in a run of abutting numeric fields michael@0: int32_t abutStart = 0; michael@0: int32_t abutPass = 0; michael@0: UBool inQuote = FALSE; michael@0: michael@0: MessageFormat * numericLeapMonthFormatter = NULL; michael@0: michael@0: Calendar* calClone = NULL; michael@0: Calendar *workCal = &cal; michael@0: if (&cal != fCalendar && uprv_strcmp(cal.getType(), fCalendar->getType()) != 0) { michael@0: // Different calendar type michael@0: // We use the time/zone from the input calendar, but michael@0: // do not use the input calendar for field calculation. michael@0: calClone = fCalendar->clone(); michael@0: if (calClone != NULL) { michael@0: calClone->setTime(cal.getTime(status),status); michael@0: if (U_FAILURE(status)) { michael@0: goto ExitParse; michael@0: } michael@0: calClone->setTimeZone(cal.getTimeZone()); michael@0: workCal = calClone; michael@0: } else { michael@0: status = U_MEMORY_ALLOCATION_ERROR; michael@0: goto ExitParse; michael@0: } michael@0: } michael@0: michael@0: if (fSymbols->fLeapMonthPatterns != NULL && fSymbols->fLeapMonthPatternsCount >= DateFormatSymbols::kMonthPatternsCount) { michael@0: numericLeapMonthFormatter = new MessageFormat(fSymbols->fLeapMonthPatterns[DateFormatSymbols::kLeapMonthPatternNumeric], fLocale, status); michael@0: if (numericLeapMonthFormatter == NULL) { michael@0: status = U_MEMORY_ALLOCATION_ERROR; michael@0: goto ExitParse; michael@0: } else if (U_FAILURE(status)) { michael@0: goto ExitParse; // this will delete numericLeapMonthFormatter michael@0: } michael@0: } michael@0: michael@0: for (int32_t i=0; i= 0x41 && ch <= 0x5A) || (ch >= 0x61 && ch <= 0x7A))) { // [A-Za-z] michael@0: int32_t fieldPat = i; michael@0: michael@0: // Count the length of this field specifier michael@0: count = 1; michael@0: while ((i+1) 12:34:56, but michael@0: // "12345" => 1:23:45. Likewise, for the pattern "yyyyMMdd" we michael@0: // try 4/2/2, 3/2/2, 2/2/2, and finally 1/2/2. michael@0: if (abutPat >= 0) { michael@0: // If we are at the start of a run of abutting fields, then michael@0: // shorten this field in each pass. If we can't shorten michael@0: // this field any more, then the parse of this set of michael@0: // abutting numeric fields has failed. michael@0: if (fieldPat == abutPat) { michael@0: count -= abutPass++; michael@0: if (count == 0) { michael@0: status = U_PARSE_ERROR; michael@0: goto ExitParse; michael@0: } michael@0: } michael@0: michael@0: pos = subParse(text, pos, ch, count, michael@0: TRUE, FALSE, ambiguousYear, saveHebrewMonth, *workCal, i, numericLeapMonthFormatter); michael@0: michael@0: // If the parse fails anywhere in the run, back up to the michael@0: // start of the run and retry. michael@0: if (pos < 0) { michael@0: i = abutPat - 1; michael@0: pos = abutStart; michael@0: continue; michael@0: } michael@0: } michael@0: michael@0: // Handle non-numeric fields and non-abutting numeric michael@0: // fields. michael@0: else if (ch != 0x6C) { // pattern char 'l' (SMALL LETTER L) just gets ignored michael@0: int32_t s = subParse(text, pos, ch, count, michael@0: FALSE, TRUE, ambiguousYear, saveHebrewMonth, *workCal, i, numericLeapMonthFormatter); michael@0: michael@0: if (s == -pos-1) { michael@0: // era not present, in special cases allow this to continue michael@0: // from the position where the era was expected michael@0: s = pos; michael@0: michael@0: if (i+1 < fPattern.length()) { michael@0: // move to next pattern character michael@0: UChar ch = fPattern.charAt(i+1); michael@0: michael@0: // check for whitespace michael@0: if (PatternProps::isWhiteSpace(ch)) { michael@0: i++; michael@0: // Advance over run in pattern michael@0: while ((i+1)getTime(status); michael@0: // {sfb} check internalGetDefaultCenturyStart michael@0: if (fHaveDefaultCentury && (parsedDate < fDefaultCenturyStart)) { michael@0: // We can't use add here because that does a complete() first. michael@0: cal.set(UCAL_YEAR, fDefaultCenturyStartYear + 100); michael@0: } michael@0: delete copy; michael@0: } michael@0: michael@0: if (tztype != UTZFMT_TIME_TYPE_UNKNOWN) { michael@0: copy = cal.clone(); michael@0: // Check for failed cloning. michael@0: if (copy == NULL) { michael@0: status = U_MEMORY_ALLOCATION_ERROR; michael@0: goto ExitParse; michael@0: } michael@0: const TimeZone & tz = cal.getTimeZone(); michael@0: BasicTimeZone *btz = NULL; michael@0: michael@0: if (dynamic_cast(&tz) != NULL michael@0: || dynamic_cast(&tz) != NULL michael@0: || dynamic_cast(&tz) != NULL michael@0: || dynamic_cast(&tz) != NULL) { michael@0: btz = (BasicTimeZone*)&tz; michael@0: } michael@0: michael@0: // Get local millis michael@0: copy->set(UCAL_ZONE_OFFSET, 0); michael@0: copy->set(UCAL_DST_OFFSET, 0); michael@0: UDate localMillis = copy->getTime(status); michael@0: michael@0: // Make sure parsed time zone type (Standard or Daylight) michael@0: // matches the rule used by the parsed time zone. michael@0: int32_t raw, dst; michael@0: if (btz != NULL) { michael@0: if (tztype == UTZFMT_TIME_TYPE_STANDARD) { michael@0: btz->getOffsetFromLocal(localMillis, michael@0: BasicTimeZone::kStandard, BasicTimeZone::kStandard, raw, dst, status); michael@0: } else { michael@0: btz->getOffsetFromLocal(localMillis, michael@0: BasicTimeZone::kDaylight, BasicTimeZone::kDaylight, raw, dst, status); michael@0: } michael@0: } else { michael@0: // No good way to resolve ambiguous time at transition, michael@0: // but following code work in most case. michael@0: tz.getOffset(localMillis, TRUE, raw, dst, status); michael@0: } michael@0: michael@0: // Now, compare the results with parsed type, either standard or daylight saving time michael@0: int32_t resolvedSavings = dst; michael@0: if (tztype == UTZFMT_TIME_TYPE_STANDARD) { michael@0: if (dst != 0) { michael@0: // Override DST_OFFSET = 0 in the result calendar michael@0: resolvedSavings = 0; michael@0: } michael@0: } else { // tztype == TZTYPE_DST michael@0: if (dst == 0) { michael@0: if (btz != NULL) { michael@0: UDate time = localMillis + raw; michael@0: // We use the nearest daylight saving time rule. michael@0: TimeZoneTransition beforeTrs, afterTrs; michael@0: UDate beforeT = time, afterT = time; michael@0: int32_t beforeSav = 0, afterSav = 0; michael@0: UBool beforeTrsAvail, afterTrsAvail; michael@0: michael@0: // Search for DST rule before or on the time michael@0: while (TRUE) { michael@0: beforeTrsAvail = btz->getPreviousTransition(beforeT, TRUE, beforeTrs); michael@0: if (!beforeTrsAvail) { michael@0: break; michael@0: } michael@0: beforeT = beforeTrs.getTime() - 1; michael@0: beforeSav = beforeTrs.getFrom()->getDSTSavings(); michael@0: if (beforeSav != 0) { michael@0: break; michael@0: } michael@0: } michael@0: michael@0: // Search for DST rule after the time michael@0: while (TRUE) { michael@0: afterTrsAvail = btz->getNextTransition(afterT, FALSE, afterTrs); michael@0: if (!afterTrsAvail) { michael@0: break; michael@0: } michael@0: afterT = afterTrs.getTime(); michael@0: afterSav = afterTrs.getTo()->getDSTSavings(); michael@0: if (afterSav != 0) { michael@0: break; michael@0: } michael@0: } michael@0: michael@0: if (beforeTrsAvail && afterTrsAvail) { michael@0: if (time - beforeT > afterT - time) { michael@0: resolvedSavings = afterSav; michael@0: } else { michael@0: resolvedSavings = beforeSav; michael@0: } michael@0: } else if (beforeTrsAvail && beforeSav != 0) { michael@0: resolvedSavings = beforeSav; michael@0: } else if (afterTrsAvail && afterSav != 0) { michael@0: resolvedSavings = afterSav; michael@0: } else { michael@0: resolvedSavings = btz->getDSTSavings(); michael@0: } michael@0: } else { michael@0: resolvedSavings = tz.getDSTSavings(); michael@0: } michael@0: if (resolvedSavings == 0) { michael@0: // final fallback michael@0: resolvedSavings = U_MILLIS_PER_HOUR; michael@0: } michael@0: } michael@0: } michael@0: cal.set(UCAL_ZONE_OFFSET, raw); michael@0: cal.set(UCAL_DST_OFFSET, resolvedSavings); michael@0: delete copy; michael@0: } michael@0: } michael@0: ExitParse: michael@0: // Set the parsed result if local calendar is used michael@0: // instead of the input calendar michael@0: if (U_SUCCESS(status) && workCal != &cal) { michael@0: cal.setTimeZone(workCal->getTimeZone()); michael@0: cal.setTime(workCal->getTime(status), status); michael@0: } michael@0: michael@0: if (numericLeapMonthFormatter != NULL) { michael@0: delete numericLeapMonthFormatter; michael@0: } michael@0: if (calClone != NULL) { michael@0: delete calClone; michael@0: } michael@0: michael@0: // If any Calendar calls failed, we pretend that we michael@0: // couldn't parse the string, when in reality this isn't quite accurate-- michael@0: // we did parse it; the Calendar calls just failed. michael@0: if (U_FAILURE(status)) { michael@0: parsePos.setErrorIndex(pos); michael@0: parsePos.setIndex(start); michael@0: } michael@0: } michael@0: michael@0: //---------------------------------------------------------------------- michael@0: michael@0: static UBool michael@0: newBestMatchWithOptionalDot(const UnicodeString &lcaseText, michael@0: const UnicodeString &data, michael@0: UnicodeString &bestMatchName, michael@0: int32_t &bestMatchLength); michael@0: michael@0: int32_t SimpleDateFormat::matchQuarterString(const UnicodeString& text, michael@0: int32_t start, michael@0: UCalendarDateFields field, michael@0: const UnicodeString* data, michael@0: int32_t dataCount, michael@0: Calendar& cal) const michael@0: { michael@0: int32_t i = 0; michael@0: int32_t count = dataCount; michael@0: michael@0: // There may be multiple strings in the data[] array which begin with michael@0: // the same prefix (e.g., Cerven and Cervenec (June and July) in Czech). michael@0: // We keep track of the longest match, and return that. Note that this michael@0: // unfortunately requires us to test all array elements. michael@0: int32_t bestMatchLength = 0, bestMatch = -1; michael@0: UnicodeString bestMatchName; michael@0: michael@0: // {sfb} kludge to support case-insensitive comparison michael@0: // {markus 2002oct11} do not just use caseCompareBetween because we do not know michael@0: // the length of the match after case folding michael@0: // {alan 20040607} don't case change the whole string, since the length michael@0: // can change michael@0: // TODO we need a case-insensitive startsWith function michael@0: UnicodeString lcaseText; michael@0: text.extract(start, INT32_MAX, lcaseText); michael@0: lcaseText.foldCase(); michael@0: michael@0: for (; i < count; ++i) michael@0: { michael@0: // Always compare if we have no match yet; otherwise only compare michael@0: // against potentially better matches (longer strings). michael@0: michael@0: if (newBestMatchWithOptionalDot(lcaseText, data[i], bestMatchName, bestMatchLength)) { michael@0: bestMatch = i; michael@0: } michael@0: } michael@0: if (bestMatch >= 0) michael@0: { michael@0: cal.set(field, bestMatch * 3); michael@0: michael@0: // Once we have a match, we have to determine the length of the michael@0: // original source string. This will usually be == the length of michael@0: // the case folded string, but it may differ (e.g. sharp s). michael@0: michael@0: // Most of the time, the length will be the same as the length michael@0: // of the string from the locale data. Sometimes it will be michael@0: // different, in which case we will have to figure it out by michael@0: // adding a character at a time, until we have a match. We do michael@0: // this all in one loop, where we try 'len' first (at index michael@0: // i==0). michael@0: int32_t len = bestMatchName.length(); // 99+% of the time michael@0: int32_t n = text.length() - start; michael@0: for (i=0; i<=n; ++i) { michael@0: int32_t j=i; michael@0: if (i == 0) { michael@0: j = len; michael@0: } else if (i == len) { michael@0: continue; // already tried this when i was 0 michael@0: } michael@0: text.extract(start, j, lcaseText); michael@0: lcaseText.foldCase(); michael@0: if (bestMatchName == lcaseText) { michael@0: return start + j; michael@0: } michael@0: } michael@0: } michael@0: michael@0: return -start; michael@0: } michael@0: michael@0: //---------------------------------------------------------------------- michael@0: UBool SimpleDateFormat::matchLiterals(const UnicodeString &pattern, michael@0: int32_t &patternOffset, michael@0: const UnicodeString &text, michael@0: int32_t &textOffset, michael@0: UBool lenient) michael@0: { michael@0: UBool inQuote = FALSE; michael@0: UnicodeString literal; michael@0: int32_t i = patternOffset; michael@0: michael@0: // scan pattern looking for contiguous literal characters michael@0: for ( ; i < pattern.length(); i += 1) { michael@0: UChar ch = pattern.charAt(i); michael@0: michael@0: if (!inQuote && ((ch >= 0x41 && ch <= 0x5A) || (ch >= 0x61 && ch <= 0x7A))) { // unquoted [A-Za-z] michael@0: break; michael@0: } michael@0: michael@0: if (ch == QUOTE) { michael@0: // Match a quote literal ('') inside OR outside of quotes michael@0: if ((i + 1) < pattern.length() && pattern.charAt(i + 1) == QUOTE) { michael@0: i += 1; michael@0: } else { michael@0: inQuote = !inQuote; michael@0: continue; michael@0: } michael@0: } michael@0: michael@0: literal += ch; michael@0: } michael@0: michael@0: // at this point, literal contains the literal text michael@0: // and i is the index of the next non-literal pattern character. michael@0: int32_t p; michael@0: int32_t t = textOffset; michael@0: michael@0: if (lenient) { michael@0: // trim leading, trailing whitespace from michael@0: // the literal text michael@0: literal.trim(); michael@0: michael@0: // ignore any leading whitespace in the text michael@0: while (t < text.length() && u_isWhitespace(text.charAt(t))) { michael@0: t += 1; michael@0: } michael@0: } michael@0: michael@0: for (p = 0; p < literal.length() && t < text.length();) { michael@0: UBool needWhitespace = FALSE; michael@0: michael@0: while (p < literal.length() && PatternProps::isWhiteSpace(literal.charAt(p))) { michael@0: needWhitespace = TRUE; michael@0: p += 1; michael@0: } michael@0: michael@0: if (needWhitespace) { michael@0: int32_t tStart = t; michael@0: michael@0: while (t < text.length()) { michael@0: UChar tch = text.charAt(t); michael@0: michael@0: if (!u_isUWhiteSpace(tch) && !PatternProps::isWhiteSpace(tch)) { michael@0: break; michael@0: } michael@0: michael@0: t += 1; michael@0: } michael@0: michael@0: // TODO: should we require internal spaces michael@0: // in lenient mode? (There won't be any michael@0: // leading or trailing spaces) michael@0: if (!lenient && t == tStart) { michael@0: // didn't find matching whitespace: michael@0: // an error in strict mode michael@0: return FALSE; michael@0: } michael@0: michael@0: // In strict mode, this run of whitespace michael@0: // may have been at the end. michael@0: if (p >= literal.length()) { michael@0: break; michael@0: } michael@0: } michael@0: michael@0: if (t >= text.length() || literal.charAt(p) != text.charAt(t)) { michael@0: // Ran out of text, or found a non-matching character: michael@0: // OK in lenient mode, an error in strict mode. michael@0: if (lenient) { michael@0: if (t == textOffset && text.charAt(t) == 0x2e && michael@0: isAfterNonNumericField(pattern, patternOffset)) { michael@0: // Lenient mode and the literal input text begins with a "." and michael@0: // we are after a non-numeric field: We skip the "." michael@0: ++t; michael@0: continue; // Do not update p. michael@0: } michael@0: break; michael@0: } michael@0: michael@0: return FALSE; michael@0: } michael@0: ++p; michael@0: ++t; michael@0: } michael@0: michael@0: // At this point if we're in strict mode we have a complete match. michael@0: // If we're in lenient mode we may have a partial match, or no michael@0: // match at all. michael@0: if (p <= 0) { michael@0: // no match. Pretend it matched a run of whitespace michael@0: // and ignorables in the text. michael@0: const UnicodeSet *ignorables = NULL; michael@0: UDateFormatField patternCharIndex = DateFormatSymbols::getPatternCharIndex(pattern.charAt(i)); michael@0: if (patternCharIndex != UDAT_FIELD_COUNT) { michael@0: ignorables = SimpleDateFormatStaticSets::getIgnorables(patternCharIndex); michael@0: } michael@0: michael@0: for (t = textOffset; t < text.length(); t += 1) { michael@0: UChar ch = text.charAt(t); michael@0: michael@0: if (ignorables == NULL || !ignorables->contains(ch)) { michael@0: break; michael@0: } michael@0: } michael@0: } michael@0: michael@0: // if we get here, we've got a complete match. michael@0: patternOffset = i - 1; michael@0: textOffset = t; michael@0: michael@0: return TRUE; michael@0: } michael@0: michael@0: //---------------------------------------------------------------------- michael@0: michael@0: int32_t SimpleDateFormat::matchString(const UnicodeString& text, michael@0: int32_t start, michael@0: UCalendarDateFields field, michael@0: const UnicodeString* data, michael@0: int32_t dataCount, michael@0: const UnicodeString* monthPattern, michael@0: Calendar& cal) const michael@0: { michael@0: int32_t i = 0; michael@0: int32_t count = dataCount; michael@0: michael@0: if (field == UCAL_DAY_OF_WEEK) i = 1; michael@0: michael@0: // There may be multiple strings in the data[] array which begin with michael@0: // the same prefix (e.g., Cerven and Cervenec (June and July) in Czech). michael@0: // We keep track of the longest match, and return that. Note that this michael@0: // unfortunately requires us to test all array elements. michael@0: int32_t bestMatchLength = 0, bestMatch = -1; michael@0: UnicodeString bestMatchName; michael@0: int32_t isLeapMonth = 0; michael@0: michael@0: // {sfb} kludge to support case-insensitive comparison michael@0: // {markus 2002oct11} do not just use caseCompareBetween because we do not know michael@0: // the length of the match after case folding michael@0: // {alan 20040607} don't case change the whole string, since the length michael@0: // can change michael@0: // TODO we need a case-insensitive startsWith function michael@0: UnicodeString lcaseText; michael@0: text.extract(start, INT32_MAX, lcaseText); michael@0: lcaseText.foldCase(); michael@0: michael@0: for (; i < count; ++i) michael@0: { michael@0: // Always compare if we have no match yet; otherwise only compare michael@0: // against potentially better matches (longer strings). michael@0: michael@0: if (newBestMatchWithOptionalDot(lcaseText, data[i], bestMatchName, bestMatchLength)) { michael@0: bestMatch = i; michael@0: isLeapMonth = 0; michael@0: } michael@0: michael@0: if (monthPattern != NULL) { michael@0: UErrorCode status = U_ZERO_ERROR; michael@0: UnicodeString leapMonthName; michael@0: Formattable monthName((const UnicodeString&)(data[i])); michael@0: MessageFormat::format(*monthPattern, &monthName, 1, leapMonthName, status); michael@0: if (U_SUCCESS(status)) { michael@0: if (newBestMatchWithOptionalDot(lcaseText, leapMonthName, bestMatchName, bestMatchLength)) { michael@0: bestMatch = i; michael@0: isLeapMonth = 1; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: if (bestMatch >= 0) michael@0: { michael@0: // Adjustment for Hebrew Calendar month Adar II michael@0: if (!strcmp(cal.getType(),"hebrew") && field==UCAL_MONTH && bestMatch==13) { michael@0: cal.set(field,6); michael@0: } michael@0: else { michael@0: if (field == UCAL_YEAR) { michael@0: bestMatch++; // only get here for cyclic year names, which match 1-based years 1-60 michael@0: } michael@0: cal.set(field, bestMatch); michael@0: } michael@0: if (monthPattern != NULL) { michael@0: cal.set(UCAL_IS_LEAP_MONTH, isLeapMonth); michael@0: } michael@0: michael@0: // Once we have a match, we have to determine the length of the michael@0: // original source string. This will usually be == the length of michael@0: // the case folded string, but it may differ (e.g. sharp s). michael@0: michael@0: // Most of the time, the length will be the same as the length michael@0: // of the string from the locale data. Sometimes it will be michael@0: // different, in which case we will have to figure it out by michael@0: // adding a character at a time, until we have a match. We do michael@0: // this all in one loop, where we try 'len' first (at index michael@0: // i==0). michael@0: int32_t len = bestMatchName.length(); // 99+% of the time michael@0: int32_t n = text.length() - start; michael@0: for (i=0; i<=n; ++i) { michael@0: int32_t j=i; michael@0: if (i == 0) { michael@0: j = len; michael@0: } else if (i == len) { michael@0: continue; // already tried this when i was 0 michael@0: } michael@0: text.extract(start, j, lcaseText); michael@0: lcaseText.foldCase(); michael@0: if (bestMatchName == lcaseText) { michael@0: return start + j; michael@0: } michael@0: } michael@0: } michael@0: michael@0: return -start; michael@0: } michael@0: michael@0: static UBool michael@0: newBestMatchWithOptionalDot(const UnicodeString &lcaseText, michael@0: const UnicodeString &data, michael@0: UnicodeString &bestMatchName, michael@0: int32_t &bestMatchLength) { michael@0: UnicodeString lcase; michael@0: lcase.fastCopyFrom(data).foldCase(); michael@0: int32_t length = lcase.length(); michael@0: if (length <= bestMatchLength) { michael@0: // data cannot provide a better match. michael@0: return FALSE; michael@0: } michael@0: michael@0: if (lcaseText.compareBetween(0, length, lcase, 0, length) == 0) { michael@0: // normal match michael@0: bestMatchName = lcase; michael@0: bestMatchLength = length; michael@0: return TRUE; michael@0: } michael@0: if (lcase.charAt(--length) == 0x2e) { michael@0: if (lcaseText.compareBetween(0, length, lcase, 0, length) == 0) { michael@0: // The input text matches the data except for data's trailing dot. michael@0: bestMatchName = lcase; michael@0: bestMatchName.truncate(length); michael@0: bestMatchLength = length; michael@0: return TRUE; michael@0: } michael@0: } michael@0: return FALSE; michael@0: } michael@0: michael@0: //---------------------------------------------------------------------- michael@0: michael@0: void michael@0: SimpleDateFormat::set2DigitYearStart(UDate d, UErrorCode& status) michael@0: { michael@0: parseAmbiguousDatesAsAfter(d, status); michael@0: } michael@0: michael@0: /** michael@0: * Private member function that converts the parsed date strings into michael@0: * timeFields. Returns -start (for ParsePosition) if failed. michael@0: * @param text the time text to be parsed. michael@0: * @param start where to start parsing. michael@0: * @param ch the pattern character for the date field text to be parsed. michael@0: * @param count the count of a pattern character. michael@0: * @return the new start position if matching succeeded; a negative number michael@0: * indicating matching failure, otherwise. michael@0: */ michael@0: int32_t SimpleDateFormat::subParse(const UnicodeString& text, int32_t& start, UChar ch, int32_t count, michael@0: UBool obeyCount, UBool allowNegative, UBool ambiguousYear[], int32_t& saveHebrewMonth, Calendar& cal, michael@0: int32_t patLoc, MessageFormat * numericLeapMonthFormatter) const michael@0: { michael@0: Formattable number; michael@0: int32_t value = 0; michael@0: int32_t i; michael@0: int32_t ps = 0; michael@0: UErrorCode status = U_ZERO_ERROR; michael@0: ParsePosition pos(0); michael@0: UDateFormatField patternCharIndex = DateFormatSymbols::getPatternCharIndex(ch); michael@0: NumberFormat *currentNumberFormat; michael@0: UnicodeString temp; michael@0: UBool gotNumber = FALSE; michael@0: michael@0: #if defined (U_DEBUG_CAL) michael@0: //fprintf(stderr, "%s:%d - [%c] st=%d \n", __FILE__, __LINE__, (char) ch, start); michael@0: #endif michael@0: michael@0: if (patternCharIndex == UDAT_FIELD_COUNT) { michael@0: return -start; michael@0: } michael@0: michael@0: currentNumberFormat = getNumberFormatByIndex(patternCharIndex); michael@0: UCalendarDateFields field = fgPatternIndexToCalendarField[patternCharIndex]; michael@0: UnicodeString hebr("hebr", 4, US_INV); michael@0: michael@0: if (numericLeapMonthFormatter != NULL) { michael@0: numericLeapMonthFormatter->setFormats((const Format **)¤tNumberFormat, 1); michael@0: } michael@0: UBool isChineseCalendar = (uprv_strcmp(cal.getType(),"chinese") == 0 || uprv_strcmp(cal.getType(),"dangi") == 0); michael@0: michael@0: // If there are any spaces here, skip over them. If we hit the end michael@0: // of the string, then fail. michael@0: for (;;) { michael@0: if (start >= text.length()) { michael@0: return -start; michael@0: } michael@0: UChar32 c = text.char32At(start); michael@0: if (!u_isUWhiteSpace(c) /*||*/ && !PatternProps::isWhiteSpace(c)) { michael@0: break; michael@0: } michael@0: start += U16_LENGTH(c); michael@0: } michael@0: pos.setIndex(start); michael@0: michael@0: // We handle a few special cases here where we need to parse michael@0: // a number value. We handle further, more generic cases below. We need michael@0: // to handle some of them here because some fields require extra processing on michael@0: // the parsed value. michael@0: if (patternCharIndex == UDAT_HOUR_OF_DAY1_FIELD || // k michael@0: patternCharIndex == UDAT_HOUR_OF_DAY0_FIELD || // H michael@0: patternCharIndex == UDAT_HOUR1_FIELD || // h michael@0: patternCharIndex == UDAT_HOUR0_FIELD || // K michael@0: (patternCharIndex == UDAT_DOW_LOCAL_FIELD && count <= 2) || // e michael@0: (patternCharIndex == UDAT_STANDALONE_DAY_FIELD && count <= 2) || // c michael@0: (patternCharIndex == UDAT_MONTH_FIELD && count <= 2) || // M michael@0: (patternCharIndex == UDAT_STANDALONE_MONTH_FIELD && count <= 2) || // L michael@0: (patternCharIndex == UDAT_QUARTER_FIELD && count <= 2) || // Q michael@0: (patternCharIndex == UDAT_STANDALONE_QUARTER_FIELD && count <= 2) || // q michael@0: patternCharIndex == UDAT_YEAR_FIELD || // y michael@0: patternCharIndex == UDAT_YEAR_WOY_FIELD || // Y michael@0: patternCharIndex == UDAT_YEAR_NAME_FIELD || // U (falls back to numeric) michael@0: (patternCharIndex == UDAT_ERA_FIELD && isChineseCalendar) || // G michael@0: patternCharIndex == UDAT_FRACTIONAL_SECOND_FIELD) // S michael@0: { michael@0: int32_t parseStart = pos.getIndex(); michael@0: // It would be good to unify this with the obeyCount logic below, michael@0: // but that's going to be difficult. michael@0: const UnicodeString* src; michael@0: michael@0: UBool parsedNumericLeapMonth = FALSE; michael@0: if (numericLeapMonthFormatter != NULL && (patternCharIndex == UDAT_MONTH_FIELD || patternCharIndex == UDAT_STANDALONE_MONTH_FIELD)) { michael@0: int32_t argCount; michael@0: Formattable * args = numericLeapMonthFormatter->parse(text, pos, argCount); michael@0: if (args != NULL && argCount == 1 && pos.getIndex() > parseStart && args[0].isNumeric()) { michael@0: parsedNumericLeapMonth = TRUE; michael@0: number.setLong(args[0].getLong()); michael@0: cal.set(UCAL_IS_LEAP_MONTH, 1); michael@0: delete[] args; michael@0: } else { michael@0: pos.setIndex(parseStart); michael@0: cal.set(UCAL_IS_LEAP_MONTH, 0); michael@0: } michael@0: } michael@0: michael@0: if (!parsedNumericLeapMonth) { michael@0: if (obeyCount) { michael@0: if ((start+count) > text.length()) { michael@0: return -start; michael@0: } michael@0: michael@0: text.extractBetween(0, start + count, temp); michael@0: src = &temp; michael@0: } else { michael@0: src = &text; michael@0: } michael@0: michael@0: parseInt(*src, number, pos, allowNegative,currentNumberFormat); michael@0: } michael@0: michael@0: int32_t txtLoc = pos.getIndex(); michael@0: michael@0: if (txtLoc > parseStart) { michael@0: value = number.getLong(); michael@0: gotNumber = TRUE; michael@0: michael@0: // suffix processing michael@0: if (value < 0 ) { michael@0: txtLoc = checkIntSuffix(text, txtLoc, patLoc+1, TRUE); michael@0: if (txtLoc != pos.getIndex()) { michael@0: value *= -1; michael@0: } michael@0: } michael@0: else { michael@0: txtLoc = checkIntSuffix(text, txtLoc, patLoc+1, FALSE); michael@0: } michael@0: michael@0: if (!getBooleanAttribute(UDAT_PARSE_ALLOW_WHITESPACE, status)) { michael@0: // Check the range of the value michael@0: int32_t bias = gFieldRangeBias[patternCharIndex]; michael@0: if (bias >= 0 && (value > cal.getMaximum(field) + bias || value < cal.getMinimum(field) + bias)) { michael@0: return -start; michael@0: } michael@0: } michael@0: michael@0: pos.setIndex(txtLoc); michael@0: } michael@0: } michael@0: michael@0: // Make sure that we got a number if michael@0: // we want one, and didn't get one michael@0: // if we don't want one. michael@0: switch (patternCharIndex) { michael@0: case UDAT_HOUR_OF_DAY1_FIELD: michael@0: case UDAT_HOUR_OF_DAY0_FIELD: michael@0: case UDAT_HOUR1_FIELD: michael@0: case UDAT_HOUR0_FIELD: michael@0: // special range check for hours: michael@0: if (value < 0 || value > 24) { michael@0: return -start; michael@0: } michael@0: michael@0: // fall through to gotNumber check michael@0: michael@0: case UDAT_YEAR_FIELD: michael@0: case UDAT_YEAR_WOY_FIELD: michael@0: case UDAT_FRACTIONAL_SECOND_FIELD: michael@0: // these must be a number michael@0: if (! gotNumber) { michael@0: return -start; michael@0: } michael@0: michael@0: break; michael@0: michael@0: default: michael@0: // we check the rest of the fields below. michael@0: break; michael@0: } michael@0: michael@0: switch (patternCharIndex) { michael@0: case UDAT_ERA_FIELD: michael@0: if (isChineseCalendar) { michael@0: if (!gotNumber) { michael@0: return -start; michael@0: } michael@0: cal.set(UCAL_ERA, value); michael@0: return pos.getIndex(); michael@0: } michael@0: if (count == 5) { michael@0: ps = matchString(text, start, UCAL_ERA, fSymbols->fNarrowEras, fSymbols->fNarrowErasCount, NULL, cal); michael@0: } else if (count == 4) { michael@0: ps = matchString(text, start, UCAL_ERA, fSymbols->fEraNames, fSymbols->fEraNamesCount, NULL, cal); michael@0: } else { michael@0: ps = matchString(text, start, UCAL_ERA, fSymbols->fEras, fSymbols->fErasCount, NULL, cal); michael@0: } michael@0: michael@0: // check return position, if it equals -start, then matchString error michael@0: // special case the return code so we don't necessarily fail out until we michael@0: // verify no year information also michael@0: if (ps == -start) michael@0: ps--; michael@0: michael@0: return ps; michael@0: michael@0: case UDAT_YEAR_FIELD: michael@0: // If there are 3 or more YEAR pattern characters, this indicates michael@0: // that the year value is to be treated literally, without any michael@0: // two-digit year adjustments (e.g., from "01" to 2001). Otherwise michael@0: // we made adjustments to place the 2-digit year in the proper michael@0: // century, for parsed strings from "00" to "99". Any other string michael@0: // is treated literally: "2250", "-1", "1", "002". michael@0: if (fDateOverride.compare(hebr)==0 && value < 1000) { michael@0: value += HEBREW_CAL_CUR_MILLENIUM_START_YEAR; michael@0: } else if ((pos.getIndex() - start) == 2 && !isChineseCalendar michael@0: && u_isdigit(text.charAt(start)) michael@0: && u_isdigit(text.charAt(start+1))) michael@0: { michael@0: // only adjust year for patterns less than 3. michael@0: if(count < 3) { michael@0: // Assume for example that the defaultCenturyStart is 6/18/1903. michael@0: // This means that two-digit years will be forced into the range michael@0: // 6/18/1903 to 6/17/2003. As a result, years 00, 01, and 02 michael@0: // correspond to 2000, 2001, and 2002. Years 04, 05, etc. correspond michael@0: // to 1904, 1905, etc. If the year is 03, then it is 2003 if the michael@0: // other fields specify a date before 6/18, or 1903 if they specify a michael@0: // date afterwards. As a result, 03 is an ambiguous year. All other michael@0: // two-digit years are unambiguous. michael@0: if(fHaveDefaultCentury) { // check if this formatter even has a pivot year michael@0: int32_t ambiguousTwoDigitYear = fDefaultCenturyStartYear % 100; michael@0: ambiguousYear[0] = (value == ambiguousTwoDigitYear); michael@0: value += (fDefaultCenturyStartYear/100)*100 + michael@0: (value < ambiguousTwoDigitYear ? 100 : 0); michael@0: } michael@0: } michael@0: } michael@0: cal.set(UCAL_YEAR, value); michael@0: michael@0: // Delayed checking for adjustment of Hebrew month numbers in non-leap years. michael@0: if (saveHebrewMonth >= 0) { michael@0: HebrewCalendar *hc = (HebrewCalendar*)&cal; michael@0: if (!hc->isLeapYear(value) && saveHebrewMonth >= 6) { michael@0: cal.set(UCAL_MONTH,saveHebrewMonth); michael@0: } else { michael@0: cal.set(UCAL_MONTH,saveHebrewMonth-1); michael@0: } michael@0: saveHebrewMonth = -1; michael@0: } michael@0: return pos.getIndex(); michael@0: michael@0: case UDAT_YEAR_WOY_FIELD: michael@0: // Comment is the same as for UDAT_Year_FIELDs - look above michael@0: if (fDateOverride.compare(hebr)==0 && value < 1000) { michael@0: value += HEBREW_CAL_CUR_MILLENIUM_START_YEAR; michael@0: } else if ((pos.getIndex() - start) == 2 michael@0: && u_isdigit(text.charAt(start)) michael@0: && u_isdigit(text.charAt(start+1)) michael@0: && fHaveDefaultCentury ) michael@0: { michael@0: int32_t ambiguousTwoDigitYear = fDefaultCenturyStartYear % 100; michael@0: ambiguousYear[0] = (value == ambiguousTwoDigitYear); michael@0: value += (fDefaultCenturyStartYear/100)*100 + michael@0: (value < ambiguousTwoDigitYear ? 100 : 0); michael@0: } michael@0: cal.set(UCAL_YEAR_WOY, value); michael@0: return pos.getIndex(); michael@0: michael@0: case UDAT_YEAR_NAME_FIELD: michael@0: if (fSymbols->fShortYearNames != NULL) { michael@0: int32_t newStart = matchString(text, start, UCAL_YEAR, fSymbols->fShortYearNames, fSymbols->fShortYearNamesCount, NULL, cal); michael@0: if (newStart > 0) { michael@0: return newStart; michael@0: } michael@0: } michael@0: if (gotNumber && (getBooleanAttribute(UDAT_PARSE_ALLOW_NUMERIC,status) || value > fSymbols->fShortYearNamesCount)) { michael@0: cal.set(UCAL_YEAR, value); michael@0: return pos.getIndex(); michael@0: } michael@0: return -start; michael@0: michael@0: case UDAT_MONTH_FIELD: michael@0: case UDAT_STANDALONE_MONTH_FIELD: michael@0: if (gotNumber) // i.e., M or MM. michael@0: { michael@0: // When parsing month numbers from the Hebrew Calendar, we might need to adjust the month depending on whether michael@0: // or not it was a leap year. We may or may not yet know what year it is, so might have to delay checking until michael@0: // the year is parsed. michael@0: if (!strcmp(cal.getType(),"hebrew")) { michael@0: HebrewCalendar *hc = (HebrewCalendar*)&cal; michael@0: if (cal.isSet(UCAL_YEAR)) { michael@0: UErrorCode status = U_ZERO_ERROR; michael@0: if (!hc->isLeapYear(hc->get(UCAL_YEAR,status)) && value >= 6) { michael@0: cal.set(UCAL_MONTH, value); michael@0: } else { michael@0: cal.set(UCAL_MONTH, value - 1); michael@0: } michael@0: } else { michael@0: saveHebrewMonth = value; michael@0: } michael@0: } else { michael@0: // Don't want to parse the month if it is a string michael@0: // while pattern uses numeric style: M/MM, L/LL michael@0: // [We computed 'value' above.] michael@0: cal.set(UCAL_MONTH, value - 1); michael@0: } michael@0: return pos.getIndex(); michael@0: } else { michael@0: // count >= 3 // i.e., MMM/MMMM, LLL/LLLL michael@0: // Want to be able to parse both short and long forms. michael@0: // Try count == 4 first: michael@0: UnicodeString * wideMonthPat = NULL; michael@0: UnicodeString * shortMonthPat = NULL; michael@0: if (fSymbols->fLeapMonthPatterns != NULL && fSymbols->fLeapMonthPatternsCount >= DateFormatSymbols::kMonthPatternsCount) { michael@0: if (patternCharIndex==UDAT_MONTH_FIELD) { michael@0: wideMonthPat = &fSymbols->fLeapMonthPatterns[DateFormatSymbols::kLeapMonthPatternFormatWide]; michael@0: shortMonthPat = &fSymbols->fLeapMonthPatterns[DateFormatSymbols::kLeapMonthPatternFormatAbbrev]; michael@0: } else { michael@0: wideMonthPat = &fSymbols->fLeapMonthPatterns[DateFormatSymbols::kLeapMonthPatternStandaloneWide]; michael@0: shortMonthPat = &fSymbols->fLeapMonthPatterns[DateFormatSymbols::kLeapMonthPatternStandaloneAbbrev]; michael@0: } michael@0: } michael@0: int32_t newStart = 0; michael@0: if (patternCharIndex==UDAT_MONTH_FIELD) { michael@0: newStart = matchString(text, start, UCAL_MONTH, fSymbols->fMonths, fSymbols->fMonthsCount, wideMonthPat, cal); // try MMMM michael@0: if (newStart > 0) { michael@0: return newStart; michael@0: } michael@0: newStart = matchString(text, start, UCAL_MONTH, fSymbols->fShortMonths, fSymbols->fShortMonthsCount, shortMonthPat, cal); // try MMM michael@0: } else { michael@0: newStart = matchString(text, start, UCAL_MONTH, fSymbols->fStandaloneMonths, fSymbols->fStandaloneMonthsCount, wideMonthPat, cal); // try LLLL michael@0: if (newStart > 0) { michael@0: return newStart; michael@0: } michael@0: newStart = matchString(text, start, UCAL_MONTH, fSymbols->fStandaloneShortMonths, fSymbols->fStandaloneShortMonthsCount, shortMonthPat, cal); // try LLL michael@0: } michael@0: if (newStart > 0 || !getBooleanAttribute(UDAT_PARSE_ALLOW_NUMERIC, status)) // currently we do not try to parse MMMMM/LLLLL: #8860 michael@0: return newStart; michael@0: // else we allowing parsing as number, below michael@0: } michael@0: break; michael@0: michael@0: case UDAT_HOUR_OF_DAY1_FIELD: michael@0: // [We computed 'value' above.] michael@0: if (value == cal.getMaximum(UCAL_HOUR_OF_DAY) + 1) michael@0: value = 0; michael@0: michael@0: // fall through to set field michael@0: michael@0: case UDAT_HOUR_OF_DAY0_FIELD: michael@0: cal.set(UCAL_HOUR_OF_DAY, value); michael@0: return pos.getIndex(); michael@0: michael@0: case UDAT_FRACTIONAL_SECOND_FIELD: michael@0: // Fractional seconds left-justify michael@0: i = pos.getIndex() - start; michael@0: if (i < 3) { michael@0: while (i < 3) { michael@0: value *= 10; michael@0: i++; michael@0: } michael@0: } else { michael@0: int32_t a = 1; michael@0: while (i > 3) { michael@0: a *= 10; michael@0: i--; michael@0: } michael@0: value /= a; michael@0: } michael@0: cal.set(UCAL_MILLISECOND, value); michael@0: return pos.getIndex(); michael@0: michael@0: case UDAT_DOW_LOCAL_FIELD: michael@0: if (gotNumber) // i.e., e or ee michael@0: { michael@0: // [We computed 'value' above.] michael@0: cal.set(UCAL_DOW_LOCAL, value); michael@0: return pos.getIndex(); michael@0: } michael@0: // else for eee-eeeee fall through to handling of EEE-EEEEE michael@0: // fall through, do not break here michael@0: case UDAT_DAY_OF_WEEK_FIELD: michael@0: { michael@0: // Want to be able to parse both short and long forms. michael@0: // Try count == 4 (EEEE) wide first: michael@0: int32_t newStart = 0; michael@0: if ((newStart = matchString(text, start, UCAL_DAY_OF_WEEK, michael@0: fSymbols->fWeekdays, fSymbols->fWeekdaysCount, NULL, cal)) > 0) michael@0: return newStart; michael@0: // EEEE wide failed, now try EEE abbreviated michael@0: else if ((newStart = matchString(text, start, UCAL_DAY_OF_WEEK, michael@0: fSymbols->fShortWeekdays, fSymbols->fShortWeekdaysCount, NULL, cal)) > 0) michael@0: return newStart; michael@0: // EEE abbreviated failed, now try EEEEEE short michael@0: else if ((newStart = matchString(text, start, UCAL_DAY_OF_WEEK, michael@0: fSymbols->fShorterWeekdays, fSymbols->fShorterWeekdaysCount, NULL, cal)) > 0) michael@0: return newStart; michael@0: // EEEEEE short failed, now try EEEEE narrow michael@0: else if ((newStart = matchString(text, start, UCAL_DAY_OF_WEEK, michael@0: fSymbols->fNarrowWeekdays, fSymbols->fNarrowWeekdaysCount, NULL, cal)) > 0) michael@0: return newStart; michael@0: else if (!getBooleanAttribute(UDAT_PARSE_ALLOW_NUMERIC, status) || patternCharIndex == UDAT_DAY_OF_WEEK_FIELD) michael@0: return newStart; michael@0: // else we allowing parsing as number, below michael@0: } michael@0: break; michael@0: michael@0: case UDAT_STANDALONE_DAY_FIELD: michael@0: { michael@0: if (gotNumber) // c or cc michael@0: { michael@0: // [We computed 'value' above.] michael@0: cal.set(UCAL_DOW_LOCAL, value); michael@0: return pos.getIndex(); michael@0: } michael@0: // Want to be able to parse both short and long forms. michael@0: // Try count == 4 (cccc) first: michael@0: int32_t newStart = 0; michael@0: if ((newStart = matchString(text, start, UCAL_DAY_OF_WEEK, michael@0: fSymbols->fStandaloneWeekdays, fSymbols->fStandaloneWeekdaysCount, NULL, cal)) > 0) michael@0: return newStart; michael@0: else if ((newStart = matchString(text, start, UCAL_DAY_OF_WEEK, michael@0: fSymbols->fStandaloneShortWeekdays, fSymbols->fStandaloneShortWeekdaysCount, NULL, cal)) > 0) michael@0: return newStart; michael@0: else if ((newStart = matchString(text, start, UCAL_DAY_OF_WEEK, michael@0: fSymbols->fStandaloneShorterWeekdays, fSymbols->fStandaloneShorterWeekdaysCount, NULL, cal)) > 0) michael@0: return newStart; michael@0: else if (!getBooleanAttribute(UDAT_PARSE_ALLOW_NUMERIC, status)) michael@0: return newStart; michael@0: // else we allowing parsing as number, below michael@0: } michael@0: break; michael@0: michael@0: case UDAT_AM_PM_FIELD: michael@0: return matchString(text, start, UCAL_AM_PM, fSymbols->fAmPms, fSymbols->fAmPmsCount, NULL, cal); michael@0: michael@0: case UDAT_HOUR1_FIELD: michael@0: // [We computed 'value' above.] michael@0: if (value == cal.getLeastMaximum(UCAL_HOUR)+1) michael@0: value = 0; michael@0: michael@0: // fall through to set field michael@0: michael@0: case UDAT_HOUR0_FIELD: michael@0: cal.set(UCAL_HOUR, value); michael@0: return pos.getIndex(); michael@0: michael@0: case UDAT_QUARTER_FIELD: michael@0: if (gotNumber) // i.e., Q or QQ. michael@0: { michael@0: // Don't want to parse the month if it is a string michael@0: // while pattern uses numeric style: Q or QQ. michael@0: // [We computed 'value' above.] michael@0: cal.set(UCAL_MONTH, (value - 1) * 3); michael@0: return pos.getIndex(); michael@0: } else { michael@0: // count >= 3 // i.e., QQQ or QQQQ michael@0: // Want to be able to parse both short and long forms. michael@0: // Try count == 4 first: michael@0: int32_t newStart = 0; michael@0: michael@0: if ((newStart = matchQuarterString(text, start, UCAL_MONTH, michael@0: fSymbols->fQuarters, fSymbols->fQuartersCount, cal)) > 0) michael@0: return newStart; michael@0: else if ((newStart = matchQuarterString(text, start, UCAL_MONTH, michael@0: fSymbols->fShortQuarters, fSymbols->fShortQuartersCount, cal)) > 0) michael@0: return newStart; michael@0: else if (!getBooleanAttribute(UDAT_PARSE_ALLOW_NUMERIC, status)) michael@0: return newStart; michael@0: // else we allowing parsing as number, below michael@0: } michael@0: break; michael@0: michael@0: case UDAT_STANDALONE_QUARTER_FIELD: michael@0: if (gotNumber) // i.e., q or qq. michael@0: { michael@0: // Don't want to parse the month if it is a string michael@0: // while pattern uses numeric style: q or q. michael@0: // [We computed 'value' above.] michael@0: cal.set(UCAL_MONTH, (value - 1) * 3); michael@0: return pos.getIndex(); michael@0: } else { michael@0: // count >= 3 // i.e., qqq or qqqq michael@0: // Want to be able to parse both short and long forms. michael@0: // Try count == 4 first: michael@0: int32_t newStart = 0; michael@0: michael@0: if ((newStart = matchQuarterString(text, start, UCAL_MONTH, michael@0: fSymbols->fStandaloneQuarters, fSymbols->fStandaloneQuartersCount, cal)) > 0) michael@0: return newStart; michael@0: else if ((newStart = matchQuarterString(text, start, UCAL_MONTH, michael@0: fSymbols->fStandaloneShortQuarters, fSymbols->fStandaloneShortQuartersCount, cal)) > 0) michael@0: return newStart; michael@0: else if (!getBooleanAttribute(UDAT_PARSE_ALLOW_NUMERIC, status)) michael@0: return newStart; michael@0: // else we allowing parsing as number, below michael@0: } michael@0: break; michael@0: michael@0: case UDAT_TIMEZONE_FIELD: // 'z' michael@0: { michael@0: UTimeZoneFormatTimeType tzTimeType = UTZFMT_TIME_TYPE_UNKNOWN; michael@0: UTimeZoneFormatStyle style = (count < 4) ? UTZFMT_STYLE_SPECIFIC_SHORT : UTZFMT_STYLE_SPECIFIC_LONG; michael@0: TimeZone *tz = tzFormat()->parse(style, text, pos, &tzTimeType); michael@0: if (tz != NULL) { michael@0: ((SimpleDateFormat*)this)->tztype = tzTimeType; michael@0: cal.adoptTimeZone(tz); michael@0: return pos.getIndex(); michael@0: } michael@0: } michael@0: break; michael@0: case UDAT_TIMEZONE_RFC_FIELD: // 'Z' michael@0: { michael@0: UTimeZoneFormatTimeType tzTimeType = UTZFMT_TIME_TYPE_UNKNOWN; michael@0: UTimeZoneFormatStyle style = (count < 4) ? michael@0: UTZFMT_STYLE_ISO_BASIC_LOCAL_FULL : ((count == 5) ? UTZFMT_STYLE_ISO_EXTENDED_FULL: UTZFMT_STYLE_LOCALIZED_GMT); michael@0: TimeZone *tz = tzFormat()->parse(style, text, pos, &tzTimeType); michael@0: if (tz != NULL) { michael@0: ((SimpleDateFormat*)this)->tztype = tzTimeType; michael@0: cal.adoptTimeZone(tz); michael@0: return pos.getIndex(); michael@0: } michael@0: return -start; michael@0: } michael@0: case UDAT_TIMEZONE_GENERIC_FIELD: // 'v' michael@0: { michael@0: UTimeZoneFormatTimeType tzTimeType = UTZFMT_TIME_TYPE_UNKNOWN; michael@0: UTimeZoneFormatStyle style = (count < 4) ? UTZFMT_STYLE_GENERIC_SHORT : UTZFMT_STYLE_GENERIC_LONG; michael@0: TimeZone *tz = tzFormat()->parse(style, text, pos, &tzTimeType); michael@0: if (tz != NULL) { michael@0: ((SimpleDateFormat*)this)->tztype = tzTimeType; michael@0: cal.adoptTimeZone(tz); michael@0: return pos.getIndex(); michael@0: } michael@0: return -start; michael@0: } michael@0: case UDAT_TIMEZONE_SPECIAL_FIELD: // 'V' michael@0: { michael@0: UTimeZoneFormatTimeType tzTimeType = UTZFMT_TIME_TYPE_UNKNOWN; michael@0: UTimeZoneFormatStyle style; michael@0: switch (count) { michael@0: case 1: michael@0: style = UTZFMT_STYLE_ZONE_ID_SHORT; michael@0: break; michael@0: case 2: michael@0: style = UTZFMT_STYLE_ZONE_ID; michael@0: break; michael@0: case 3: michael@0: style = UTZFMT_STYLE_EXEMPLAR_LOCATION; michael@0: break; michael@0: default: michael@0: style = UTZFMT_STYLE_GENERIC_LOCATION; michael@0: break; michael@0: } michael@0: TimeZone *tz = tzFormat()->parse(style, text, pos, &tzTimeType); michael@0: if (tz != NULL) { michael@0: ((SimpleDateFormat*)this)->tztype = tzTimeType; michael@0: cal.adoptTimeZone(tz); michael@0: return pos.getIndex(); michael@0: } michael@0: return -start; michael@0: } michael@0: case UDAT_TIMEZONE_LOCALIZED_GMT_OFFSET_FIELD: // 'O' michael@0: { michael@0: UTimeZoneFormatTimeType tzTimeType = UTZFMT_TIME_TYPE_UNKNOWN; michael@0: UTimeZoneFormatStyle style = (count < 4) ? UTZFMT_STYLE_LOCALIZED_GMT_SHORT : UTZFMT_STYLE_LOCALIZED_GMT; michael@0: TimeZone *tz = tzFormat()->parse(style, text, pos, &tzTimeType); michael@0: if (tz != NULL) { michael@0: ((SimpleDateFormat*)this)->tztype = tzTimeType; michael@0: cal.adoptTimeZone(tz); michael@0: return pos.getIndex(); michael@0: } michael@0: return -start; michael@0: } michael@0: case UDAT_TIMEZONE_ISO_FIELD: // 'X' michael@0: { michael@0: UTimeZoneFormatTimeType tzTimeType = UTZFMT_TIME_TYPE_UNKNOWN; michael@0: UTimeZoneFormatStyle style; michael@0: switch (count) { michael@0: case 1: michael@0: style = UTZFMT_STYLE_ISO_BASIC_SHORT; michael@0: break; michael@0: case 2: michael@0: style = UTZFMT_STYLE_ISO_BASIC_FIXED; michael@0: break; michael@0: case 3: michael@0: style = UTZFMT_STYLE_ISO_EXTENDED_FIXED; michael@0: break; michael@0: case 4: michael@0: style = UTZFMT_STYLE_ISO_BASIC_FULL; michael@0: break; michael@0: default: michael@0: style = UTZFMT_STYLE_ISO_EXTENDED_FULL; michael@0: break; michael@0: } michael@0: TimeZone *tz = tzFormat()->parse(style, text, pos, &tzTimeType); michael@0: if (tz != NULL) { michael@0: ((SimpleDateFormat*)this)->tztype = tzTimeType; michael@0: cal.adoptTimeZone(tz); michael@0: return pos.getIndex(); michael@0: } michael@0: return -start; michael@0: } michael@0: case UDAT_TIMEZONE_ISO_LOCAL_FIELD: // 'x' michael@0: { michael@0: UTimeZoneFormatTimeType tzTimeType = UTZFMT_TIME_TYPE_UNKNOWN; michael@0: UTimeZoneFormatStyle style; michael@0: switch (count) { michael@0: case 1: michael@0: style = UTZFMT_STYLE_ISO_BASIC_LOCAL_SHORT; michael@0: break; michael@0: case 2: michael@0: style = UTZFMT_STYLE_ISO_BASIC_LOCAL_FIXED; michael@0: break; michael@0: case 3: michael@0: style = UTZFMT_STYLE_ISO_EXTENDED_LOCAL_FIXED; michael@0: break; michael@0: case 4: michael@0: style = UTZFMT_STYLE_ISO_BASIC_LOCAL_FULL; michael@0: break; michael@0: default: michael@0: style = UTZFMT_STYLE_ISO_EXTENDED_LOCAL_FULL; michael@0: break; michael@0: } michael@0: TimeZone *tz = tzFormat()->parse(style, text, pos, &tzTimeType); michael@0: if (tz != NULL) { michael@0: ((SimpleDateFormat*)this)->tztype = tzTimeType; michael@0: cal.adoptTimeZone(tz); michael@0: return pos.getIndex(); michael@0: } michael@0: return -start; michael@0: } michael@0: michael@0: default: michael@0: // Handle "generic" fields michael@0: // this is now handled below, outside the switch block michael@0: break; michael@0: } michael@0: // Handle "generic" fields: michael@0: // switch default case now handled here (outside switch block) to allow michael@0: // parsing of some string fields as digits for lenient case michael@0: michael@0: int32_t parseStart = pos.getIndex(); michael@0: const UnicodeString* src; michael@0: if (obeyCount) { michael@0: if ((start+count) > text.length()) { michael@0: return -start; michael@0: } michael@0: text.extractBetween(0, start + count, temp); michael@0: src = &temp; michael@0: } else { michael@0: src = &text; michael@0: } michael@0: parseInt(*src, number, pos, allowNegative,currentNumberFormat); michael@0: if (pos.getIndex() != parseStart) { michael@0: int32_t value = number.getLong(); michael@0: michael@0: // Don't need suffix processing here (as in number processing at the beginning of the function); michael@0: // the new fields being handled as numeric values (month, weekdays, quarters) should not have suffixes. michael@0: michael@0: if (!getBooleanAttribute(UDAT_PARSE_ALLOW_NUMERIC, status)) { michael@0: // Check the range of the value michael@0: int32_t bias = gFieldRangeBias[patternCharIndex]; michael@0: if (bias >= 0 && (value > cal.getMaximum(field) + bias || value < cal.getMinimum(field) + bias)) { michael@0: return -start; michael@0: } michael@0: } michael@0: michael@0: // For the following, need to repeat some of the "if (gotNumber)" code above: michael@0: // UDAT_[STANDALONE_]MONTH_FIELD, UDAT_DOW_LOCAL_FIELD, UDAT_STANDALONE_DAY_FIELD, michael@0: // UDAT_[STANDALONE_]QUARTER_FIELD michael@0: switch (patternCharIndex) { michael@0: case UDAT_MONTH_FIELD: michael@0: // See notes under UDAT_MONTH_FIELD case above michael@0: if (!strcmp(cal.getType(),"hebrew")) { michael@0: HebrewCalendar *hc = (HebrewCalendar*)&cal; michael@0: if (cal.isSet(UCAL_YEAR)) { michael@0: UErrorCode status = U_ZERO_ERROR; michael@0: if (!hc->isLeapYear(hc->get(UCAL_YEAR,status)) && value >= 6) { michael@0: cal.set(UCAL_MONTH, value); michael@0: } else { michael@0: cal.set(UCAL_MONTH, value - 1); michael@0: } michael@0: } else { michael@0: saveHebrewMonth = value; michael@0: } michael@0: } else { michael@0: cal.set(UCAL_MONTH, value - 1); michael@0: } michael@0: break; michael@0: case UDAT_STANDALONE_MONTH_FIELD: michael@0: cal.set(UCAL_MONTH, value - 1); michael@0: break; michael@0: case UDAT_DOW_LOCAL_FIELD: michael@0: case UDAT_STANDALONE_DAY_FIELD: michael@0: cal.set(UCAL_DOW_LOCAL, value); michael@0: break; michael@0: case UDAT_QUARTER_FIELD: michael@0: case UDAT_STANDALONE_QUARTER_FIELD: michael@0: cal.set(UCAL_MONTH, (value - 1) * 3); michael@0: break; michael@0: default: michael@0: cal.set(field, value); michael@0: break; michael@0: } michael@0: return pos.getIndex(); michael@0: } michael@0: return -start; michael@0: } michael@0: michael@0: /** michael@0: * Parse an integer using fNumberFormat. This method is semantically michael@0: * const, but actually may modify fNumberFormat. michael@0: */ michael@0: void SimpleDateFormat::parseInt(const UnicodeString& text, michael@0: Formattable& number, michael@0: ParsePosition& pos, michael@0: UBool allowNegative, michael@0: NumberFormat *fmt) const { michael@0: parseInt(text, number, -1, pos, allowNegative,fmt); michael@0: } michael@0: michael@0: /** michael@0: * Parse an integer using fNumberFormat up to maxDigits. michael@0: */ michael@0: void SimpleDateFormat::parseInt(const UnicodeString& text, michael@0: Formattable& number, michael@0: int32_t maxDigits, michael@0: ParsePosition& pos, michael@0: UBool allowNegative, michael@0: NumberFormat *fmt) const { michael@0: UnicodeString oldPrefix; michael@0: DecimalFormat* df = NULL; michael@0: if (!allowNegative && (df = dynamic_cast(fmt)) != NULL) { michael@0: df->getNegativePrefix(oldPrefix); michael@0: df->setNegativePrefix(UnicodeString(TRUE, SUPPRESS_NEGATIVE_PREFIX, -1)); michael@0: } michael@0: int32_t oldPos = pos.getIndex(); michael@0: fmt->parse(text, number, pos); michael@0: if (df != NULL) { michael@0: df->setNegativePrefix(oldPrefix); michael@0: } michael@0: michael@0: if (maxDigits > 0) { michael@0: // adjust the result to fit into michael@0: // the maxDigits and move the position back michael@0: int32_t nDigits = pos.getIndex() - oldPos; michael@0: if (nDigits > maxDigits) { michael@0: int32_t val = number.getLong(); michael@0: nDigits -= maxDigits; michael@0: while (nDigits > 0) { michael@0: val /= 10; michael@0: nDigits--; michael@0: } michael@0: pos.setIndex(oldPos + maxDigits); michael@0: number.setLong(val); michael@0: } michael@0: } michael@0: } michael@0: michael@0: //---------------------------------------------------------------------- michael@0: michael@0: void SimpleDateFormat::translatePattern(const UnicodeString& originalPattern, michael@0: UnicodeString& translatedPattern, michael@0: const UnicodeString& from, michael@0: const UnicodeString& to, michael@0: UErrorCode& status) michael@0: { michael@0: // run through the pattern and convert any pattern symbols from the version michael@0: // in "from" to the corresponding character ion "to". This code takes michael@0: // quoted strings into account (it doesn't try to translate them), and it signals michael@0: // an error if a particular "pattern character" doesn't appear in "from". michael@0: // Depending on the values of "from" and "to" this can convert from generic michael@0: // to localized patterns or localized to generic. michael@0: if (U_FAILURE(status)) michael@0: return; michael@0: michael@0: translatedPattern.remove(); michael@0: UBool inQuote = FALSE; michael@0: for (int32_t i = 0; i < originalPattern.length(); ++i) { michael@0: UChar c = originalPattern[i]; michael@0: if (inQuote) { michael@0: if (c == QUOTE) michael@0: inQuote = FALSE; michael@0: } michael@0: else { michael@0: if (c == QUOTE) michael@0: inQuote = TRUE; michael@0: else if ((c >= 0x0061 /*'a'*/ && c <= 0x007A) /*'z'*/ michael@0: || (c >= 0x0041 /*'A'*/ && c <= 0x005A /*'Z'*/)) { michael@0: int32_t ci = from.indexOf(c); michael@0: if (ci == -1) { michael@0: status = U_INVALID_FORMAT_ERROR; michael@0: return; michael@0: } michael@0: c = to[ci]; michael@0: } michael@0: } michael@0: translatedPattern += c; michael@0: } michael@0: if (inQuote) { michael@0: status = U_INVALID_FORMAT_ERROR; michael@0: return; michael@0: } michael@0: } michael@0: michael@0: //---------------------------------------------------------------------- michael@0: michael@0: UnicodeString& michael@0: SimpleDateFormat::toPattern(UnicodeString& result) const michael@0: { michael@0: result = fPattern; michael@0: return result; michael@0: } michael@0: michael@0: //---------------------------------------------------------------------- michael@0: michael@0: UnicodeString& michael@0: SimpleDateFormat::toLocalizedPattern(UnicodeString& result, michael@0: UErrorCode& status) const michael@0: { michael@0: translatePattern(fPattern, result, michael@0: UnicodeString(DateFormatSymbols::getPatternUChars()), michael@0: fSymbols->fLocalPatternChars, status); michael@0: return result; michael@0: } michael@0: michael@0: //---------------------------------------------------------------------- michael@0: michael@0: void michael@0: SimpleDateFormat::applyPattern(const UnicodeString& pattern) michael@0: { michael@0: fPattern = pattern; michael@0: } michael@0: michael@0: //---------------------------------------------------------------------- michael@0: michael@0: void michael@0: SimpleDateFormat::applyLocalizedPattern(const UnicodeString& pattern, michael@0: UErrorCode &status) michael@0: { michael@0: translatePattern(pattern, fPattern, michael@0: fSymbols->fLocalPatternChars, michael@0: UnicodeString(DateFormatSymbols::getPatternUChars()), status); michael@0: } michael@0: michael@0: //---------------------------------------------------------------------- michael@0: michael@0: const DateFormatSymbols* michael@0: SimpleDateFormat::getDateFormatSymbols() const michael@0: { michael@0: return fSymbols; michael@0: } michael@0: michael@0: //---------------------------------------------------------------------- michael@0: michael@0: void michael@0: SimpleDateFormat::adoptDateFormatSymbols(DateFormatSymbols* newFormatSymbols) michael@0: { michael@0: delete fSymbols; michael@0: fSymbols = newFormatSymbols; michael@0: } michael@0: michael@0: //---------------------------------------------------------------------- michael@0: void michael@0: SimpleDateFormat::setDateFormatSymbols(const DateFormatSymbols& newFormatSymbols) michael@0: { michael@0: delete fSymbols; michael@0: fSymbols = new DateFormatSymbols(newFormatSymbols); michael@0: } michael@0: michael@0: //---------------------------------------------------------------------- michael@0: const TimeZoneFormat* michael@0: SimpleDateFormat::getTimeZoneFormat(void) const { michael@0: return (const TimeZoneFormat*)tzFormat(); michael@0: } michael@0: michael@0: //---------------------------------------------------------------------- michael@0: void michael@0: SimpleDateFormat::adoptTimeZoneFormat(TimeZoneFormat* timeZoneFormatToAdopt) michael@0: { michael@0: delete fTimeZoneFormat; michael@0: fTimeZoneFormat = timeZoneFormatToAdopt; michael@0: } michael@0: michael@0: //---------------------------------------------------------------------- michael@0: void michael@0: SimpleDateFormat::setTimeZoneFormat(const TimeZoneFormat& newTimeZoneFormat) michael@0: { michael@0: delete fTimeZoneFormat; michael@0: fTimeZoneFormat = new TimeZoneFormat(newTimeZoneFormat); michael@0: } michael@0: michael@0: //---------------------------------------------------------------------- michael@0: michael@0: michael@0: void SimpleDateFormat::adoptCalendar(Calendar* calendarToAdopt) michael@0: { michael@0: UErrorCode status = U_ZERO_ERROR; michael@0: DateFormat::adoptCalendar(calendarToAdopt); michael@0: delete fSymbols; michael@0: fSymbols=NULL; michael@0: initializeSymbols(fLocale, fCalendar, status); // we need new symbols michael@0: initializeDefaultCentury(); // we need a new century (possibly) michael@0: } michael@0: michael@0: michael@0: //---------------------------------------------------------------------- michael@0: michael@0: michael@0: void SimpleDateFormat::setContext(UDisplayContext value, UErrorCode& status) michael@0: { michael@0: if (U_FAILURE(status)) michael@0: return; michael@0: if ( (UDisplayContextType)((uint32_t)value >> 8) == UDISPCTX_TYPE_CAPITALIZATION ) { michael@0: fCapitalizationContext = value; michael@0: } else { michael@0: status = U_ILLEGAL_ARGUMENT_ERROR; michael@0: } michael@0: } michael@0: michael@0: michael@0: //---------------------------------------------------------------------- michael@0: michael@0: michael@0: UDisplayContext SimpleDateFormat::getContext(UDisplayContextType type, UErrorCode& status) const michael@0: { michael@0: if (U_FAILURE(status)) michael@0: return (UDisplayContext)0; michael@0: if (type != UDISPCTX_TYPE_CAPITALIZATION) { michael@0: status = U_ILLEGAL_ARGUMENT_ERROR; michael@0: return (UDisplayContext)0; michael@0: } michael@0: return fCapitalizationContext; michael@0: } michael@0: michael@0: michael@0: //---------------------------------------------------------------------- michael@0: michael@0: michael@0: UBool michael@0: SimpleDateFormat::isFieldUnitIgnored(UCalendarDateFields field) const { michael@0: return isFieldUnitIgnored(fPattern, field); michael@0: } michael@0: michael@0: michael@0: UBool michael@0: SimpleDateFormat::isFieldUnitIgnored(const UnicodeString& pattern, michael@0: UCalendarDateFields field) { michael@0: int32_t fieldLevel = fgCalendarFieldToLevel[field]; michael@0: int32_t level; michael@0: UChar ch; michael@0: UBool inQuote = FALSE; michael@0: UChar prevCh = 0; michael@0: int32_t count = 0; michael@0: michael@0: for (int32_t i = 0; i < pattern.length(); ++i) { michael@0: ch = pattern[i]; michael@0: if (ch != prevCh && count > 0) { michael@0: level = fgPatternCharToLevel[prevCh - PATTERN_CHAR_BASE]; michael@0: // the larger the level, the smaller the field unit. michael@0: if ( fieldLevel <= level ) { michael@0: return FALSE; michael@0: } michael@0: count = 0; michael@0: } michael@0: if (ch == QUOTE) { michael@0: if ((i+1) < pattern.length() && pattern[i+1] == QUOTE) { michael@0: ++i; michael@0: } else { michael@0: inQuote = ! inQuote; michael@0: } michael@0: } michael@0: else if ( ! inQuote && ((ch >= 0x0061 /*'a'*/ && ch <= 0x007A /*'z'*/) michael@0: || (ch >= 0x0041 /*'A'*/ && ch <= 0x005A /*'Z'*/))) { michael@0: prevCh = ch; michael@0: ++count; michael@0: } michael@0: } michael@0: if ( count > 0 ) { michael@0: // last item michael@0: level = fgPatternCharToLevel[prevCh - PATTERN_CHAR_BASE]; michael@0: if ( fieldLevel <= level ) { michael@0: return FALSE; michael@0: } michael@0: } michael@0: return TRUE; michael@0: } michael@0: michael@0: //---------------------------------------------------------------------- michael@0: michael@0: const Locale& michael@0: SimpleDateFormat::getSmpFmtLocale(void) const { michael@0: return fLocale; michael@0: } michael@0: michael@0: //---------------------------------------------------------------------- michael@0: michael@0: int32_t michael@0: SimpleDateFormat::checkIntSuffix(const UnicodeString& text, int32_t start, michael@0: int32_t patLoc, UBool isNegative) const { michael@0: // local variables michael@0: UnicodeString suf; michael@0: int32_t patternMatch; michael@0: int32_t textPreMatch; michael@0: int32_t textPostMatch; michael@0: michael@0: // check that we are still in range michael@0: if ( (start > text.length()) || michael@0: (start < 0) || michael@0: (patLoc < 0) || michael@0: (patLoc > fPattern.length())) { michael@0: // out of range, don't advance location in text michael@0: return start; michael@0: } michael@0: michael@0: // get the suffix michael@0: DecimalFormat* decfmt = dynamic_cast(fNumberFormat); michael@0: if (decfmt != NULL) { michael@0: if (isNegative) { michael@0: suf = decfmt->getNegativeSuffix(suf); michael@0: } michael@0: else { michael@0: suf = decfmt->getPositiveSuffix(suf); michael@0: } michael@0: } michael@0: michael@0: // check for suffix michael@0: if (suf.length() <= 0) { michael@0: return start; michael@0: } michael@0: michael@0: // check suffix will be encountered in the pattern michael@0: patternMatch = compareSimpleAffix(suf,fPattern,patLoc); michael@0: michael@0: // check if a suffix will be encountered in the text michael@0: textPreMatch = compareSimpleAffix(suf,text,start); michael@0: michael@0: // check if a suffix was encountered in the text michael@0: textPostMatch = compareSimpleAffix(suf,text,start-suf.length()); michael@0: michael@0: // check for suffix match michael@0: if ((textPreMatch >= 0) && (patternMatch >= 0) && (textPreMatch == patternMatch)) { michael@0: return start; michael@0: } michael@0: else if ((textPostMatch >= 0) && (patternMatch >= 0) && (textPostMatch == patternMatch)) { michael@0: return start - suf.length(); michael@0: } michael@0: michael@0: // should not get here michael@0: return start; michael@0: } michael@0: michael@0: //---------------------------------------------------------------------- michael@0: michael@0: int32_t michael@0: SimpleDateFormat::compareSimpleAffix(const UnicodeString& affix, michael@0: const UnicodeString& input, michael@0: int32_t pos) const { michael@0: int32_t start = pos; michael@0: for (int32_t i=0; i(this)->fTimeZoneFormat = tzfmt; michael@0: } michael@0: } michael@0: umtx_unlock(&LOCK); michael@0: } michael@0: return fTimeZoneFormat; michael@0: } michael@0: michael@0: U_NAMESPACE_END michael@0: michael@0: #endif /* #if !UCONFIG_NO_FORMATTING */ michael@0: michael@0: //eof