2 *******************************************************************************
3 * Copyright (C) 1997-2006, International Business Machines Corporation and *
4 * others. All Rights Reserved. *
5 *******************************************************************************
9 * Modification History:
11 * Date Name Description
12 * 02/19/97 aliu Converted from java.
13 * 03/31/97 aliu Modified extensively to work with 50 locales.
14 * 04/01/97 aliu Added support for centuries.
15 * 07/09/97 helena Made ParsePosition into a class.
16 * 07/21/98 stephen Added initializeDefaultCentury.
17 * Removed getZoneIndex (added in DateFormatSymbols)
18 * Removed subParseLong
20 * 02/22/99 stephen Removed character literals for EBCDIC safety
21 * 10/14/99 aliu Updated 2-digit year parsing so that only "00" thru
22 * "99" are recognized. {j28 4182066}
23 * 11/15/99 weiv Added support for week of year/day of week format
24 ********************************************************************************
27 #include "unicode/utypes.h"
29 #if !UCONFIG_NO_FORMATTING
31 #include "unicode/smpdtfmt.h"
32 #include "unicode/dtfmtsym.h"
33 #include "unicode/ures.h"
34 #include "unicode/msgfmt.h"
35 #include "unicode/calendar.h"
36 #include "unicode/gregocal.h"
37 #include "unicode/timezone.h"
38 #include "unicode/decimfmt.h"
39 #include "unicode/dcfmtsym.h"
40 #include "unicode/uchar.h"
41 #include "unicode/ustring.h"
48 #if defined( U_DEBUG_CALSVC ) || defined (U_DEBUG_CAL)
52 // *****************************************************************************
53 // class SimpleDateFormat
54 // *****************************************************************************
59 * Last-resort string to use for "GMT" when constructing time zone strings.
61 // For time zones that have no names, use strings GMT+minutes and
62 // GMT-minutes. For instance, in France the time zone is GMT+60.
63 // Also accepted are GMT+H:MM or GMT-H:MM.
64 static const UChar gGmt
[] = {0x0047, 0x004D, 0x0054, 0x0000}; // "GMT"
65 static const UChar gGmtPlus
[] = {0x0047, 0x004D, 0x0054, 0x002B, 0x0000}; // "GMT+"
66 static const UChar gGmtMinus
[] = {0x0047, 0x004D, 0x0054, 0x002D, 0x0000}; // "GMT-"
68 // This is a pattern-of-last-resort used when we can't load a usable pattern out
70 static const UChar gDefaultPattern
[] =
72 0x79, 0x79, 0x79, 0x79, 0x4D, 0x4D, 0x64, 0x64, 0x20, 0x68, 0x68, 0x3A, 0x6D, 0x6D, 0x20, 0x61, 0
73 }; /* "yyyyMMdd hh:mm a" */
75 // This prefix is designed to NEVER MATCH real text, in order to
76 // suppress the parsing of negative numbers. Adjust as needed (if
77 // this becomes valid Unicode).
78 static const UChar SUPPRESS_NEGATIVE_PREFIX
[] = {0xAB00, 0};
81 * These are the tags we expect to see in normal resource bundle files associated
84 static const char gDateTimePatternsTag
[]="DateTimePatterns";
86 UOBJECT_DEFINE_RTTI_IMPLEMENTATION(SimpleDateFormat
)
88 static const UChar QUOTE
= 0x27; // Single quote
90 //----------------------------------------------------------------------
92 SimpleDateFormat::~SimpleDateFormat()
95 delete parsedTimeZone
; // sanity check
98 //----------------------------------------------------------------------
100 SimpleDateFormat::SimpleDateFormat(UErrorCode
& status
)
101 : fLocale(Locale::getDefault()),
105 construct(kShort
, (EStyle
) (kShort
+ kDateOffset
), fLocale
, status
);
106 initializeDefaultCentury();
109 //----------------------------------------------------------------------
111 SimpleDateFormat::SimpleDateFormat(const UnicodeString
& pattern
,
114 fLocale(Locale::getDefault()),
118 initializeSymbols(fLocale
, initializeCalendar(NULL
,fLocale
,status
), status
);
119 initialize(fLocale
, status
);
120 initializeDefaultCentury();
123 //----------------------------------------------------------------------
125 SimpleDateFormat::SimpleDateFormat(const UnicodeString
& pattern
,
126 const Locale
& locale
,
132 initializeSymbols(fLocale
, initializeCalendar(NULL
,fLocale
,status
), status
);
133 initialize(fLocale
, status
);
134 initializeDefaultCentury();
137 //----------------------------------------------------------------------
139 SimpleDateFormat::SimpleDateFormat(const UnicodeString
& pattern
,
140 DateFormatSymbols
* symbolsToAdopt
,
143 fLocale(Locale::getDefault()),
144 fSymbols(symbolsToAdopt
),
147 initializeCalendar(NULL
,fLocale
,status
);
148 initialize(fLocale
, status
);
149 initializeDefaultCentury();
152 //----------------------------------------------------------------------
154 SimpleDateFormat::SimpleDateFormat(const UnicodeString
& pattern
,
155 const DateFormatSymbols
& symbols
,
158 fLocale(Locale::getDefault()),
159 fSymbols(new DateFormatSymbols(symbols
)),
162 initializeCalendar(NULL
, fLocale
, status
);
163 initialize(fLocale
, status
);
164 initializeDefaultCentury();
167 //----------------------------------------------------------------------
169 // Not for public consumption; used by DateFormat
170 SimpleDateFormat::SimpleDateFormat(EStyle timeStyle
,
172 const Locale
& locale
,
178 construct(timeStyle
, dateStyle
, fLocale
, status
);
179 if(U_SUCCESS(status
)) {
180 initializeDefaultCentury();
184 //----------------------------------------------------------------------
187 * Not for public consumption; used by DateFormat. This constructor
188 * never fails. If the resource data is not available, it uses the
189 * the last resort symbols.
191 SimpleDateFormat::SimpleDateFormat(const Locale
& locale
,
193 : fPattern(gDefaultPattern
),
198 if (U_FAILURE(status
)) return;
199 initializeSymbols(fLocale
, initializeCalendar(NULL
, fLocale
, status
),status
);
200 if (U_FAILURE(status
))
202 status
= U_ZERO_ERROR
;
204 // This constructor doesn't fail; it uses last resort data
205 fSymbols
= new DateFormatSymbols(status
);
208 status
= U_MEMORY_ALLOCATION_ERROR
;
213 initialize(fLocale
, status
);
214 if(U_SUCCESS(status
)) {
215 initializeDefaultCentury();
219 //----------------------------------------------------------------------
221 SimpleDateFormat::SimpleDateFormat(const SimpleDateFormat
& other
)
229 //----------------------------------------------------------------------
231 SimpleDateFormat
& SimpleDateFormat::operator=(const SimpleDateFormat
& other
)
233 DateFormat::operator=(other
);
238 delete parsedTimeZone
; parsedTimeZone
= NULL
;
241 fSymbols
= new DateFormatSymbols(*other
.fSymbols
);
243 fDefaultCenturyStart
= other
.fDefaultCenturyStart
;
244 fDefaultCenturyStartYear
= other
.fDefaultCenturyStartYear
;
245 fHaveDefaultCentury
= other
.fHaveDefaultCentury
;
247 fPattern
= other
.fPattern
;
252 //----------------------------------------------------------------------
255 SimpleDateFormat::clone() const
257 return new SimpleDateFormat(*this);
260 //----------------------------------------------------------------------
263 SimpleDateFormat::operator==(const Format
& other
) const
265 if (DateFormat::operator==(other
)) {
266 // DateFormat::operator== guarantees following cast is safe
267 SimpleDateFormat
* that
= (SimpleDateFormat
*)&other
;
268 return (fPattern
== that
->fPattern
&&
269 fSymbols
!= NULL
&& // Check for pathological object
270 that
->fSymbols
!= NULL
&& // Check for pathological object
271 *fSymbols
== *that
->fSymbols
&&
272 fHaveDefaultCentury
== that
->fHaveDefaultCentury
&&
273 fDefaultCenturyStart
== that
->fDefaultCenturyStart
);
278 //----------------------------------------------------------------------
280 void SimpleDateFormat::construct(EStyle timeStyle
,
282 const Locale
& locale
,
285 // called by several constructors to load pattern data from the resources
286 if (U_FAILURE(status
)) return;
288 // We will need the calendar to know what type of symbols to load.
289 initializeCalendar(NULL
, locale
, status
);
290 if (U_FAILURE(status
)) return;
292 CalendarData
calData(locale
, fCalendar
?fCalendar
->getType():NULL
, status
);
293 UResourceBundle
*dateTimePatterns
= calData
.getByKey(gDateTimePatternsTag
, status
);
294 if (U_FAILURE(status
)) return;
296 if (ures_getSize(dateTimePatterns
) <= kDateTime
)
298 status
= U_INVALID_FORMAT_ERROR
;
302 setLocaleIDs(ures_getLocaleByType(dateTimePatterns
, ULOC_VALID_LOCALE
, &status
),
303 ures_getLocaleByType(dateTimePatterns
, ULOC_ACTUAL_LOCALE
, &status
));
305 // create a symbols object from the locale
306 initializeSymbols(locale
,fCalendar
, status
);
307 if (U_FAILURE(status
)) return;
310 status
= U_MEMORY_ALLOCATION_ERROR
;
315 int32_t resStrLen
= 0;
317 // if the pattern should include both date and time information, use the date/time
318 // pattern string as a guide to tell use how to glue together the appropriate date
319 // and time pattern strings. The actual gluing-together is handled by a convenience
320 // method on MessageFormat.
321 if ((timeStyle
!= kNone
) && (dateStyle
!= kNone
))
323 Formattable timeDateArray
[2];
325 // use Formattable::adoptString() so that we can use fastCopyFrom()
326 // instead of Formattable::setString()'s unaware, safe, deep string clone
327 // see Jitterbug 2296
328 resStr
= ures_getStringByIndex(dateTimePatterns
, (int32_t)timeStyle
, &resStrLen
, &status
);
329 timeDateArray
[0].adoptString(new UnicodeString(TRUE
, resStr
, resStrLen
));
330 resStr
= ures_getStringByIndex(dateTimePatterns
, (int32_t)dateStyle
, &resStrLen
, &status
);
331 timeDateArray
[1].adoptString(new UnicodeString(TRUE
, resStr
, resStrLen
));
333 resStr
= ures_getStringByIndex(dateTimePatterns
, (int32_t)kDateTime
, &resStrLen
, &status
);
334 MessageFormat::format(UnicodeString(TRUE
, resStr
, resStrLen
), timeDateArray
, 2, fPattern
, status
);
336 // if the pattern includes just time data or just date date, load the appropriate
337 // pattern string from the resources
338 // setTo() - see DateFormatSymbols::assignArray comments
339 else if (timeStyle
!= kNone
) {
340 resStr
= ures_getStringByIndex(dateTimePatterns
, (int32_t)timeStyle
, &resStrLen
, &status
);
341 fPattern
.setTo(TRUE
, resStr
, resStrLen
);
343 else if (dateStyle
!= kNone
) {
344 resStr
= ures_getStringByIndex(dateTimePatterns
, (int32_t)dateStyle
, &resStrLen
, &status
);
345 fPattern
.setTo(TRUE
, resStr
, resStrLen
);
348 // and if it includes _neither_, that's an error
350 status
= U_INVALID_FORMAT_ERROR
;
352 // finally, finish initializing by creating a Calendar and a NumberFormat
353 initialize(locale
, status
);
356 //----------------------------------------------------------------------
359 SimpleDateFormat::initializeCalendar(TimeZone
* adoptZone
, const Locale
& locale
, UErrorCode
& status
)
361 if(!U_FAILURE(status
)) {
362 fCalendar
= Calendar::createInstance(adoptZone
?adoptZone
:TimeZone::createDefault(), locale
, status
);
364 if (U_SUCCESS(status
) && fCalendar
== NULL
) {
365 status
= U_MEMORY_ALLOCATION_ERROR
;
371 SimpleDateFormat::initializeSymbols(const Locale
& locale
, Calendar
* calendar
, UErrorCode
& status
)
373 if(U_FAILURE(status
)) {
376 // pass in calendar type - use NULL (default) if no calendar set (or err).
377 fSymbols
= new DateFormatSymbols(locale
, calendar
?calendar
->getType() :NULL
, status
);
382 SimpleDateFormat::initialize(const Locale
& locale
,
385 if (U_FAILURE(status
)) return;
387 // We don't need to check that the row count is >= 1, since all 2d arrays have at
389 fNumberFormat
= NumberFormat::createInstance(locale
, status
);
390 if (fNumberFormat
!= NULL
&& U_SUCCESS(status
))
392 // no matter what the locale's default number format looked like, we want
393 // to modify it so that it doesn't use thousands separators, doesn't always
394 // show the decimal point, and recognizes integers only when parsing
396 fNumberFormat
->setGroupingUsed(FALSE
);
397 if (fNumberFormat
->getDynamicClassID() == DecimalFormat::getStaticClassID())
398 ((DecimalFormat
*)fNumberFormat
)->setDecimalSeparatorAlwaysShown(FALSE
);
399 fNumberFormat
->setParseIntegerOnly(TRUE
);
400 fNumberFormat
->setMinimumFractionDigits(0); // To prevent "Jan 1.00, 1997.00"
402 else if (U_SUCCESS(status
))
404 status
= U_MISSING_RESOURCE_ERROR
;
408 /* Initialize the fields we use to disambiguate ambiguous years. Separate
409 * so we can call it from readObject().
411 void SimpleDateFormat::initializeDefaultCentury()
414 fHaveDefaultCentury
= fCalendar
->haveDefaultCentury();
415 if(fHaveDefaultCentury
) {
416 fDefaultCenturyStart
= fCalendar
->defaultCenturyStart();
417 fDefaultCenturyStartYear
= fCalendar
->defaultCenturyStartYear();
419 fDefaultCenturyStart
= DBL_MIN
;
420 fDefaultCenturyStartYear
= -1;
425 /* Define one-century window into which to disambiguate dates using
426 * two-digit years. Make public in JDK 1.2.
428 void SimpleDateFormat::parseAmbiguousDatesAsAfter(UDate startDate
, UErrorCode
& status
)
430 if(U_FAILURE(status
)) {
434 status
= U_ILLEGAL_ARGUMENT_ERROR
;
438 fCalendar
->setTime(startDate
, status
);
439 if(U_SUCCESS(status
)) {
440 fHaveDefaultCentury
= TRUE
;
441 fDefaultCenturyStart
= startDate
;
442 fDefaultCenturyStartYear
= fCalendar
->get(UCAL_YEAR
, status
);
446 //----------------------------------------------------------------------
449 SimpleDateFormat::format(Calendar
& cal
, UnicodeString
& appendTo
, FieldPosition
& pos
) const
451 UErrorCode status
= U_ZERO_ERROR
;
452 pos
.setBeginIndex(0);
455 UBool inQuote
= FALSE
;
459 // loop through the pattern string character by character
460 for (int32_t i
= 0; i
< fPattern
.length() && U_SUCCESS(status
); ++i
) {
461 UChar ch
= fPattern
[i
];
463 // Use subFormat() to format a repeated pattern character
464 // when a different pattern or non-pattern character is seen
465 if (ch
!= prevCh
&& count
> 0) {
466 subFormat(appendTo
, prevCh
, count
, pos
, cal
, status
);
470 // Consecutive single quotes are a single quote literal,
471 // either outside of quotes or between quotes
472 if ((i
+1) < fPattern
.length() && fPattern
[i
+1] == QUOTE
) {
473 appendTo
+= (UChar
)QUOTE
;
479 else if ( ! inQuote
&& ((ch
>= 0x0061 /*'a'*/ && ch
<= 0x007A /*'z'*/)
480 || (ch
>= 0x0041 /*'A'*/ && ch
<= 0x005A /*'Z'*/))) {
481 // ch is a date-time pattern character to be interpreted
482 // by subFormat(); count the number of times it is repeated
487 // Append quoted characters and unquoted non-pattern characters
492 // Format the last item in the pattern, if any
494 subFormat(appendTo
, prevCh
, count
, pos
, cal
, status
);
497 // and if something failed (e.g., an invalid format character), reset our FieldPosition
498 // to (0, 0) to show that
499 // {sfb} look at this later- are these being set correctly?
500 if (U_FAILURE(status
)) {
501 pos
.setBeginIndex(0);
509 SimpleDateFormat::format(const Formattable
& obj
,
510 UnicodeString
& appendTo
,
512 UErrorCode
& status
) const
514 // this is just here to get around the hiding problem
515 // (the previous format() override would hide the version of
516 // format() on DateFormat that this function correspond to, so we
517 // have to redefine it here)
518 return DateFormat::format(obj
, appendTo
, pos
, status
);
521 //----------------------------------------------------------------------
523 // Map index into pattern character string to Calendar field number.
524 const UCalendarDateFields
525 SimpleDateFormat::fgPatternIndexToCalendarField
[] =
527 /*GyM*/ UCAL_ERA
, UCAL_YEAR
, UCAL_MONTH
,
528 /*dkH*/ UCAL_DATE
, UCAL_HOUR_OF_DAY
, UCAL_HOUR_OF_DAY
,
529 /*msS*/ UCAL_MINUTE
, UCAL_SECOND
, UCAL_MILLISECOND
,
530 /*EDF*/ UCAL_DAY_OF_WEEK
, UCAL_DAY_OF_YEAR
, UCAL_DAY_OF_WEEK_IN_MONTH
,
531 /*wWa*/ UCAL_WEEK_OF_YEAR
, UCAL_WEEK_OF_MONTH
, UCAL_AM_PM
,
532 /*hKz*/ UCAL_HOUR
, UCAL_HOUR
, UCAL_ZONE_OFFSET
,
533 /*Yeu*/ UCAL_YEAR_WOY
, UCAL_DOW_LOCAL
, UCAL_EXTENDED_YEAR
,
534 /*gAZ*/ UCAL_JULIAN_DAY
, UCAL_MILLISECONDS_IN_DAY
, UCAL_ZONE_OFFSET
,
535 /*v*/ UCAL_ZONE_OFFSET
,
536 /*c*/ UCAL_DAY_OF_WEEK
,
542 // Map index into pattern character string to DateFormat field number
543 const UDateFormatField
544 SimpleDateFormat::fgPatternIndexToDateFormatField
[] = {
545 /*GyM*/ UDAT_ERA_FIELD
, UDAT_YEAR_FIELD
, UDAT_MONTH_FIELD
,
546 /*dkH*/ UDAT_DATE_FIELD
, UDAT_HOUR_OF_DAY1_FIELD
, UDAT_HOUR_OF_DAY0_FIELD
,
547 /*msS*/ UDAT_MINUTE_FIELD
, UDAT_SECOND_FIELD
, UDAT_FRACTIONAL_SECOND_FIELD
,
548 /*EDF*/ UDAT_DAY_OF_WEEK_FIELD
, UDAT_DAY_OF_YEAR_FIELD
, UDAT_DAY_OF_WEEK_IN_MONTH_FIELD
,
549 /*wWa*/ UDAT_WEEK_OF_YEAR_FIELD
, UDAT_WEEK_OF_MONTH_FIELD
, UDAT_AM_PM_FIELD
,
550 /*hKz*/ UDAT_HOUR1_FIELD
, UDAT_HOUR0_FIELD
, UDAT_TIMEZONE_FIELD
,
551 /*Yeu*/ UDAT_YEAR_WOY_FIELD
, UDAT_DOW_LOCAL_FIELD
, UDAT_EXTENDED_YEAR_FIELD
,
552 /*gAZ*/ UDAT_JULIAN_DAY_FIELD
, UDAT_MILLISECONDS_IN_DAY_FIELD
, UDAT_TIMEZONE_RFC_FIELD
,
553 /*v*/ UDAT_TIMEZONE_GENERIC_FIELD
,
554 /*c*/ UDAT_STANDALONE_DAY_FIELD
,
555 /*L*/ UDAT_STANDALONE_MONTH_FIELD
,
556 /*Q*/ UDAT_QUARTER_FIELD
,
557 /*q*/ UDAT_STANDALONE_QUARTER_FIELD
,
560 //----------------------------------------------------------------------
563 * Append symbols[value] to dst. Make sure the array index is not out
567 _appendSymbol(UnicodeString
& dst
,
569 const UnicodeString
* symbols
,
570 int32_t symbolsCount
) {
571 U_ASSERT(0 <= value
&& value
< symbolsCount
);
572 if (0 <= value
&& value
< symbolsCount
) {
573 dst
+= symbols
[value
];
577 //---------------------------------------------------------------------
578 inline void SimpleDateFormat::appendGMT(UnicodeString
&appendTo
, Calendar
& cal
, UErrorCode
& status
) const{
579 int32_t value
= cal
.get(UCAL_ZONE_OFFSET
, status
) +
580 cal
.get(UCAL_DST_OFFSET
, status
);
583 appendTo
+= gGmtMinus
;
584 value
= -value
; // suppress the '-' sign for text display.
586 appendTo
+= gGmtPlus
;
589 zeroPaddingNumber(appendTo
, (int32_t)(value
/U_MILLIS_PER_HOUR
), 2, 2);
590 appendTo
+= (UChar
)0x003A /*':'*/;
591 zeroPaddingNumber(appendTo
, (int32_t)((value%U_MILLIS_PER_HOUR
)/U_MILLIS_PER_MINUTE
), 2, 2);
594 //---------------------------------------------------------------------
596 SimpleDateFormat::subFormat(UnicodeString
&appendTo
,
601 UErrorCode
& status
) const
603 if (U_FAILURE(status
)) {
607 // this function gets called by format() to produce the appropriate substitution
608 // text for an individual pattern symbol (e.g., "HH" or "yyyy")
610 UChar
*patternCharPtr
= u_strchr(DateFormatSymbols::getPatternUChars(), ch
);
611 UDateFormatField patternCharIndex
;
612 const int32_t maxIntCount
= 10;
613 int32_t beginOffset
= appendTo
.length();
615 // if the pattern character is unrecognized, signal an error and dump out
616 if (patternCharPtr
== NULL
)
618 status
= U_INVALID_FORMAT_ERROR
;
622 patternCharIndex
= (UDateFormatField
)(patternCharPtr
- DateFormatSymbols::getPatternUChars());
623 UCalendarDateFields field
= fgPatternIndexToCalendarField
[patternCharIndex
];
624 int32_t value
= cal
.get(field
, status
);
625 if (U_FAILURE(status
)) {
629 switch (patternCharIndex
) {
631 // for any "G" symbol, write out the appropriate era string
632 // "GGGG" is wide era name, anything else is abbreviated name
635 _appendSymbol(appendTo
, value
, fSymbols
->fEraNames
, fSymbols
->fEraNamesCount
);
637 _appendSymbol(appendTo
, value
, fSymbols
->fEras
, fSymbols
->fErasCount
);
640 // for "yyyy", write out the whole year; for "yy", write out the last 2 digits
641 case UDAT_YEAR_FIELD
:
642 case UDAT_YEAR_WOY_FIELD
:
644 zeroPaddingNumber(appendTo
, value
, 4, maxIntCount
);
646 zeroPaddingNumber(appendTo
, value
, count
, maxIntCount
);
648 zeroPaddingNumber(appendTo
, value
, 2, 2);
649 break; // TODO: this needs to be synced with Java, with GCL/Shanghai's work
651 // for "MMMM", write out the whole month name, for "MMM", write out the month
652 // abbreviation, for "M" or "MM", write out the month as a number with the
653 // appropriate number of digits
654 // for "MMMMM", use the narrow form
655 case UDAT_MONTH_FIELD
:
657 _appendSymbol(appendTo
, value
, fSymbols
->fNarrowMonths
,
658 fSymbols
->fNarrowMonthsCount
);
660 _appendSymbol(appendTo
, value
, fSymbols
->fMonths
,
661 fSymbols
->fMonthsCount
);
663 _appendSymbol(appendTo
, value
, fSymbols
->fShortMonths
,
664 fSymbols
->fShortMonthsCount
);
666 zeroPaddingNumber(appendTo
, value
+ 1, count
, maxIntCount
);
669 // for "LLLL", write out the whole month name, for "LLL", write out the month
670 // abbreviation, for "L" or "LL", write out the month as a number with the
671 // appropriate number of digits
672 // for "LLLLL", use the narrow form
673 case UDAT_STANDALONE_MONTH_FIELD
:
675 _appendSymbol(appendTo
, value
, fSymbols
->fStandaloneNarrowMonths
,
676 fSymbols
->fStandaloneNarrowMonthsCount
);
678 _appendSymbol(appendTo
, value
, fSymbols
->fStandaloneMonths
,
679 fSymbols
->fStandaloneMonthsCount
);
681 _appendSymbol(appendTo
, value
, fSymbols
->fStandaloneShortMonths
,
682 fSymbols
->fStandaloneShortMonthsCount
);
684 zeroPaddingNumber(appendTo
, value
+ 1, count
, maxIntCount
);
687 // for "k" and "kk", write out the hour, adjusting midnight to appear as "24"
688 case UDAT_HOUR_OF_DAY1_FIELD
:
690 zeroPaddingNumber(appendTo
, cal
.getMaximum(UCAL_HOUR_OF_DAY
) + 1, count
, maxIntCount
);
692 zeroPaddingNumber(appendTo
, value
, count
, maxIntCount
);
695 case UDAT_FRACTIONAL_SECOND_FIELD
:
696 // Fractional seconds left-justify
698 fNumberFormat
->setMinimumIntegerDigits((count
> 3) ? 3 : count
);
699 fNumberFormat
->setMaximumIntegerDigits(maxIntCount
);
701 value
= (value
+ 50) / 100;
702 } else if (count
== 2) {
703 value
= (value
+ 5) / 10;
706 fNumberFormat
->format(value
, appendTo
, p
);
708 fNumberFormat
->setMinimumIntegerDigits(count
- 3);
709 fNumberFormat
->format((int32_t)0, appendTo
, p
);
714 // for "EEE", write out the abbreviated day-of-the-week name
715 // for "EEEE", write out the wide day-of-the-week name
716 // for "EEEEE", use the narrow day-of-the-week name
717 case UDAT_DAY_OF_WEEK_FIELD
:
719 _appendSymbol(appendTo
, value
, fSymbols
->fNarrowWeekdays
,
720 fSymbols
->fNarrowWeekdaysCount
);
722 _appendSymbol(appendTo
, value
, fSymbols
->fWeekdays
,
723 fSymbols
->fWeekdaysCount
);
725 _appendSymbol(appendTo
, value
, fSymbols
->fShortWeekdays
,
726 fSymbols
->fShortWeekdaysCount
);
729 // for "ccc", write out the abbreviated day-of-the-week name
730 // for "cccc", write out the wide day-of-the-week name
731 // for "ccccc", use the narrow day-of-the-week name
732 case UDAT_STANDALONE_DAY_FIELD
:
734 _appendSymbol(appendTo
, value
, fSymbols
->fStandaloneNarrowWeekdays
,
735 fSymbols
->fStandaloneNarrowWeekdaysCount
);
737 _appendSymbol(appendTo
, value
, fSymbols
->fStandaloneWeekdays
,
738 fSymbols
->fStandaloneWeekdaysCount
);
740 _appendSymbol(appendTo
, value
, fSymbols
->fStandaloneShortWeekdays
,
741 fSymbols
->fStandaloneShortWeekdaysCount
);
743 zeroPaddingNumber(appendTo
, value
, 1, maxIntCount
);
746 // for and "a" symbol, write out the whole AM/PM string
747 case UDAT_AM_PM_FIELD
:
748 _appendSymbol(appendTo
, value
, fSymbols
->fAmPms
,
749 fSymbols
->fAmPmsCount
);
752 // for "h" and "hh", write out the hour, adjusting noon and midnight to show up
754 case UDAT_HOUR1_FIELD
:
756 zeroPaddingNumber(appendTo
, cal
.getLeastMaximum(UCAL_HOUR
) + 1, count
, maxIntCount
);
758 zeroPaddingNumber(appendTo
, value
, count
, maxIntCount
);
761 // for the "z" symbols, we have to check our time zone data first. If we have a
762 // localized name for the time zone, then "zzzz" / "zzz" indicate whether
763 // daylight time is in effect (long/short) and "zz" / "z" do not (long/short).
764 // If we don't have a localized time zone name,
765 // then the time zone shows up as "GMT+hh:mm" or "GMT-hh:mm" (where "hh:mm" is the
766 // offset from GMT) regardless of how many z's were in the pattern symbol
767 case UDAT_TIMEZONE_FIELD
:
768 case UDAT_TIMEZONE_GENERIC_FIELD
: {
771 UnicodeString displayString
;
772 zid
= fSymbols
->getZoneID(cal
.getTimeZone().getID(str
), zid
, status
);
773 if(U_FAILURE(status
)){
776 if (zid
.length() == 0) {
777 appendGMT(appendTo
, cal
, status
);
781 if (patternCharIndex
== UDAT_TIMEZONE_GENERIC_FIELD
) {
783 fSymbols
->getZoneString(zid
, DateFormatSymbols::TIMEZONE_SHORT_GENERIC
, displayString
, status
);
785 fSymbols
->getZoneString(zid
, DateFormatSymbols::TIMEZONE_LONG_GENERIC
, displayString
, status
);
788 if (cal
.get(UCAL_DST_OFFSET
, status
) != 0) {
790 fSymbols
->getZoneString(zid
, DateFormatSymbols::TIMEZONE_SHORT_DAYLIGHT
, displayString
, status
);
792 fSymbols
->getZoneString(zid
, DateFormatSymbols::TIMEZONE_LONG_DAYLIGHT
, displayString
, status
);
796 fSymbols
->getZoneString(zid
, DateFormatSymbols::TIMEZONE_SHORT_STANDARD
, displayString
, status
);
798 fSymbols
->getZoneString(zid
, DateFormatSymbols::TIMEZONE_LONG_STANDARD
, displayString
, status
);
802 if(displayString
.length()==0){
803 appendGMT(appendTo
, cal
, status
);
805 appendTo
+= displayString
;
811 case 23: // 'Z' - TIMEZONE_RFC
813 UChar sign
= 43/*'+'*/;
814 value
= (cal
.get(UCAL_ZONE_OFFSET
, status
) +
815 cal
.get(UCAL_DST_OFFSET
, status
)) / U_MILLIS_PER_MINUTE
;
820 value
= (value
/ 60) * 100 + (value
% 60); // minutes => KKmm
822 zeroPaddingNumber(appendTo
, value
, 4, 4);
826 case UDAT_QUARTER_FIELD
:
828 _appendSymbol(appendTo
, value
/3, fSymbols
->fQuarters
,
829 fSymbols
->fQuartersCount
);
831 _appendSymbol(appendTo
, value
/3, fSymbols
->fShortQuarters
,
832 fSymbols
->fShortQuartersCount
);
834 zeroPaddingNumber(appendTo
, (value
/3) + 1, count
, maxIntCount
);
837 case UDAT_STANDALONE_QUARTER_FIELD
:
839 _appendSymbol(appendTo
, value
/3, fSymbols
->fStandaloneQuarters
,
840 fSymbols
->fStandaloneQuartersCount
);
842 _appendSymbol(appendTo
, value
/3, fSymbols
->fStandaloneShortQuarters
,
843 fSymbols
->fStandaloneShortQuartersCount
);
845 zeroPaddingNumber(appendTo
, (value
/3) + 1, count
, maxIntCount
);
849 // all of the other pattern symbols can be formatted as simple numbers with
850 // appropriate zero padding
852 zeroPaddingNumber(appendTo
, value
, count
, maxIntCount
);
856 // if the field we're formatting is the one the FieldPosition says it's interested
857 // in, fill in the FieldPosition with this field's positions
858 if (pos
.getBeginIndex() == pos
.getEndIndex() &&
859 pos
.getField() == fgPatternIndexToDateFormatField
[patternCharIndex
]) {
860 pos
.setBeginIndex(beginOffset
);
861 pos
.setEndIndex(appendTo
.length());
865 //----------------------------------------------------------------------
868 SimpleDateFormat::zeroPaddingNumber(UnicodeString
&appendTo
, int32_t value
, int32_t minDigits
, int32_t maxDigits
) const
870 FieldPosition
pos(0);
872 fNumberFormat
->setMinimumIntegerDigits(minDigits
);
873 fNumberFormat
->setMaximumIntegerDigits(maxDigits
);
874 fNumberFormat
->format(value
, appendTo
, pos
); // 3rd arg is there to speed up processing
877 //----------------------------------------------------------------------
880 * Format characters that indicate numeric fields. The character
881 * at index 0 is treated specially.
883 static const UChar NUMERIC_FORMAT_CHARS
[] = {0x4D, 0x79, 0x75, 0x64, 0x68, 0x48, 0x6D, 0x73, 0x53, 0x44, 0x46, 0x77, 0x57, 0x6B, 0x4B, 0x00}; /* "MyudhHmsSDFwWkK" */
886 * Return true if the given format character, occuring count
887 * times, represents a numeric field.
889 UBool
SimpleDateFormat::isNumeric(UChar formatChar
, int32_t count
) {
890 UnicodeString
s(NUMERIC_FORMAT_CHARS
);
891 int32_t i
= s
.indexOf(formatChar
);
892 return (i
> 0 || (i
== 0 && count
< 3));
896 SimpleDateFormat::parse(const UnicodeString
& text
, Calendar
& cal
, ParsePosition
& parsePos
) const
898 int32_t pos
= parsePos
.getIndex();
900 UBool ambiguousYear
[] = { FALSE
};
903 // hack, clear parsedTimeZone, cast away const
904 delete parsedTimeZone
;
905 ((SimpleDateFormat
*)this)->parsedTimeZone
= NULL
;
907 // For parsing abutting numeric fields. 'abutPat' is the
908 // offset into 'pattern' of the first of 2 or more abutting
909 // numeric fields. 'abutStart' is the offset into 'text'
910 // where parsing the fields begins. 'abutPass' starts off as 0
911 // and increments each time we try to parse the fields.
912 int32_t abutPat
= -1; // If >=0, we are in a run of abutting numeric fields
913 int32_t abutStart
= 0;
914 int32_t abutPass
= 0;
915 UBool inQuote
= FALSE
;
917 const UnicodeString
numericFormatChars(NUMERIC_FORMAT_CHARS
);
919 for (int32_t i
=0; i
<fPattern
.length(); ++i
) {
920 UChar ch
= fPattern
.charAt(i
);
922 // Handle alphabetic field characters.
923 if (!inQuote
&& ((ch
>= 0x41 && ch
<= 0x5A) || (ch
>= 0x61 && ch
<= 0x7A))) { // [A-Za-z]
924 int32_t fieldPat
= i
;
926 // Count the length of this field specifier
928 while ((i
+1)<fPattern
.length() &&
929 fPattern
.charAt(i
+1) == ch
) {
934 if (isNumeric(ch
, count
)) {
936 // Determine if there is an abutting numeric field. For
937 // most fields we can just look at the next characters,
938 // but the 'm' field is either numeric or text,
939 // depending on the count, so we have to look ahead for
941 if ((i
+1)<fPattern
.length()) {
943 UChar nextCh
= fPattern
.charAt(i
+1);
944 int32_t k
= numericFormatChars
.indexOf(nextCh
);
947 while (j
<fPattern
.length() &&
948 fPattern
.charAt(j
) == nextCh
) {
951 abutting
= (j
-i
) < 4; // nextCount < 3
956 // Record the start of a set of abutting numeric
966 abutPat
= -1; // End of any abutting fields
969 // Handle fields within a run of abutting numeric fields. Take
970 // the pattern "HHmmss" as an example. We will try to parse
971 // 2/2/2 characters of the input text, then if that fails,
972 // 1/2/2. We only adjust the width of the leftmost field; the
973 // others remain fixed. This allows "123456" => 12:34:56, but
974 // "12345" => 1:23:45. Likewise, for the pattern "yyyyMMdd" we
975 // try 4/2/2, 3/2/2, 2/2/2, and finally 1/2/2.
977 // If we are at the start of a run of abutting fields, then
978 // shorten this field in each pass. If we can't shorten
979 // this field any more, then the parse of this set of
980 // abutting numeric fields has failed.
981 if (fieldPat
== abutPat
) {
984 parsePos
.setIndex(start
);
985 parsePos
.setErrorIndex(pos
);
990 pos
= subParse(text
, pos
, ch
, count
,
991 TRUE
, FALSE
, ambiguousYear
, cal
);
993 // If the parse fails anywhere in the run, back up to the
994 // start of the run and retry.
1002 // Handle non-numeric fields and non-abutting numeric
1006 pos
= subParse(text
, pos
, ch
, count
,
1007 FALSE
, TRUE
, ambiguousYear
, cal
);
1010 parsePos
.setErrorIndex(s
);
1011 parsePos
.setIndex(start
);
1017 // Handle literal pattern characters. These are any
1018 // quoted characters and non-alphabetic unquoted
1022 abutPat
= -1; // End of any abutting fields
1024 // Handle quotes. Two consecutive quotes is a quote
1025 // literal, inside or outside of quotes. Otherwise a
1026 // quote indicates entry or exit from a quoted region.
1028 // Match a quote literal '' within OR outside of quotes
1029 if ((i
+1)<fPattern
.length() && fPattern
.charAt(i
+1)==ch
) {
1030 ++i
; // Skip over doubled quote
1031 // Fall through and treat quote as a literal
1033 // Enter or exit quoted region
1039 // A run of white space in the pattern matches a run
1040 // of white space in the input text.
1041 if (uprv_isRuleWhiteSpace(ch
)) {
1042 // Advance over run in pattern
1043 while ((i
+1)<fPattern
.length() &&
1044 uprv_isRuleWhiteSpace(fPattern
.charAt(i
+1))) {
1048 // Advance over run in input text
1050 while (pos
<text
.length() &&
1051 u_isUWhiteSpace(text
.charAt(pos
))) {
1055 // Must see at least one white space char in input
1059 } else if (pos
<text
.length() && text
.charAt(pos
)==ch
) {
1065 // We fall through to this point if the match fails
1066 parsePos
.setIndex(start
);
1067 parsePos
.setErrorIndex(pos
);
1072 // At this point the fields of Calendar have been set. Calendar
1073 // will fill in default values for missing fields when the time
1076 parsePos
.setIndex(pos
);
1078 // This part is a problem: When we call parsedDate.after, we compute the time.
1079 // Take the date April 3 2004 at 2:30 am. When this is first set up, the year
1080 // will be wrong if we're parsing a 2-digit year pattern. It will be 1904.
1081 // April 3 1904 is a Sunday (unlike 2004) so it is the DST onset day. 2:30 am
1082 // is therefore an "impossible" time, since the time goes from 1:59 to 3:00 am
1083 // on that day. It is therefore parsed out to fields as 3:30 am. Then we
1084 // add 100 years, and get April 3 2004 at 3:30 am. Note that April 3 2004 is
1085 // a Saturday, so it can have a 2:30 am -- and it should. [LIU]
1087 UDate parsedDate = calendar.getTime();
1088 if( ambiguousYear[0] && !parsedDate.after(fDefaultCenturyStart) ) {
1089 calendar.add(Calendar.YEAR, 100);
1090 parsedDate = calendar.getTime();
1093 // Because of the above condition, save off the fields in case we need to readjust.
1094 // The procedure we use here is not particularly efficient, but there is no other
1095 // way to do this given the API restrictions present in Calendar. We minimize
1096 // inefficiency by only performing this computation when it might apply, that is,
1097 // when the two-digit year is equal to the start year, and thus might fall at the
1098 // front or the back of the default century. This only works because we adjust
1099 // the year correctly to start with in other cases -- see subParse().
1100 UErrorCode status
= U_ZERO_ERROR
;
1101 if (ambiguousYear
[0] || parsedTimeZone
!= NULL
) // If this is true then the two-digit year == the default start year
1103 // We need a copy of the fields, and we need to avoid triggering a call to
1104 // complete(), which will recalculate the fields. Since we can't access
1105 // the fields[] array in Calendar, we clone the entire object. This will
1106 // stop working if Calendar.clone() is ever rewritten to call complete().
1107 Calendar
*copy
= cal
.clone();
1108 if (ambiguousYear
[0]) {
1109 UDate parsedDate
= copy
->getTime(status
);
1110 // {sfb} check internalGetDefaultCenturyStart
1111 if (fHaveDefaultCentury
&& (parsedDate
< fDefaultCenturyStart
)) {
1112 // We can't use add here because that does a complete() first.
1113 cal
.set(UCAL_YEAR
, fDefaultCenturyStartYear
+ 100);
1117 if (parsedTimeZone
!= NULL
) {
1118 TimeZone
*tz
= parsedTimeZone
;
1120 // the calendar represents the parse as gmt time
1121 // we need to turn this into local time, so we add the raw offset
1122 // then we ask the timezone to handle this local time
1123 int32_t rawOffset
= 0;
1124 int32_t dstOffset
= 0;
1125 tz
->getOffset(copy
->getTime(status
)+tz
->getRawOffset(), TRUE
,
1126 rawOffset
, dstOffset
, status
);
1127 if (U_SUCCESS(status
)) {
1128 cal
.set(UCAL_ZONE_OFFSET
, rawOffset
);
1129 cal
.set(UCAL_DST_OFFSET
, dstOffset
);
1136 // If any Calendar calls failed, we pretend that we
1137 // couldn't parse the string, when in reality this isn't quite accurate--
1138 // we did parse it; the Calendar calls just failed.
1139 if (U_FAILURE(status
)) {
1140 parsePos
.setErrorIndex(pos
);
1141 parsePos
.setIndex(start
);
1146 SimpleDateFormat::parse( const UnicodeString
& text
,
1147 ParsePosition
& pos
) const {
1148 // redefined here because the other parse() function hides this function's
1149 // cunterpart on DateFormat
1150 return DateFormat::parse(text
, pos
);
1154 SimpleDateFormat::parse(const UnicodeString
& text
, UErrorCode
& status
) const
1156 // redefined here because the other parse() function hides this function's
1157 // counterpart on DateFormat
1158 return DateFormat::parse(text
, status
);
1160 //----------------------------------------------------------------------
1162 int32_t SimpleDateFormat::matchQuarterString(const UnicodeString
& text
,
1164 UCalendarDateFields field
,
1165 const UnicodeString
* data
,
1167 Calendar
& cal
) const
1170 int32_t count
= dataCount
;
1172 // There may be multiple strings in the data[] array which begin with
1173 // the same prefix (e.g., Cerven and Cervenec (June and July) in Czech).
1174 // We keep track of the longest match, and return that. Note that this
1175 // unfortunately requires us to test all array elements.
1176 int32_t bestMatchLength
= 0, bestMatch
= -1;
1178 // {sfb} kludge to support case-insensitive comparison
1179 // {markus 2002oct11} do not just use caseCompareBetween because we do not know
1180 // the length of the match after case folding
1181 // {alan 20040607} don't case change the whole string, since the length
1183 // TODO we need a case-insensitive startsWith function
1184 UnicodeString lcase
, lcaseText
;
1185 text
.extract(start
, INT32_MAX
, lcaseText
);
1186 lcaseText
.foldCase();
1188 for (; i
< count
; ++i
)
1190 // Always compare if we have no match yet; otherwise only compare
1191 // against potentially better matches (longer strings).
1193 lcase
.fastCopyFrom(data
[i
]).foldCase();
1194 int32_t length
= lcase
.length();
1196 if (length
> bestMatchLength
&&
1197 lcaseText
.compareBetween(0, length
, lcase
, 0, length
) == 0)
1200 bestMatchLength
= length
;
1205 cal
.set(field
, bestMatch
* 3);
1207 // Once we have a match, we have to determine the length of the
1208 // original source string. This will usually be == the length of
1209 // the case folded string, but it may differ (e.g. sharp s).
1210 lcase
.fastCopyFrom(data
[bestMatch
]).foldCase();
1212 // Most of the time, the length will be the same as the length
1213 // of the string from the locale data. Sometimes it will be
1214 // different, in which case we will have to figure it out by
1215 // adding a character at a time, until we have a match. We do
1216 // this all in one loop, where we try 'len' first (at index
1218 int32_t len
= data
[bestMatch
].length(); // 99+% of the time
1219 int32_t n
= text
.length() - start
;
1220 for (i
=0; i
<=n
; ++i
) {
1224 } else if (i
== len
) {
1225 continue; // already tried this when i was 0
1227 text
.extract(start
, j
, lcaseText
);
1228 lcaseText
.foldCase();
1229 if (lcase
== lcaseText
) {
1238 //----------------------------------------------------------------------
1240 int32_t SimpleDateFormat::matchString(const UnicodeString
& text
,
1242 UCalendarDateFields field
,
1243 const UnicodeString
* data
,
1245 Calendar
& cal
) const
1248 int32_t count
= dataCount
;
1250 if (field
== UCAL_DAY_OF_WEEK
) i
= 1;
1252 // There may be multiple strings in the data[] array which begin with
1253 // the same prefix (e.g., Cerven and Cervenec (June and July) in Czech).
1254 // We keep track of the longest match, and return that. Note that this
1255 // unfortunately requires us to test all array elements.
1256 int32_t bestMatchLength
= 0, bestMatch
= -1;
1258 // {sfb} kludge to support case-insensitive comparison
1259 // {markus 2002oct11} do not just use caseCompareBetween because we do not know
1260 // the length of the match after case folding
1261 // {alan 20040607} don't case change the whole string, since the length
1263 // TODO we need a case-insensitive startsWith function
1264 UnicodeString lcase
, lcaseText
;
1265 text
.extract(start
, INT32_MAX
, lcaseText
);
1266 lcaseText
.foldCase();
1268 for (; i
< count
; ++i
)
1270 // Always compare if we have no match yet; otherwise only compare
1271 // against potentially better matches (longer strings).
1273 lcase
.fastCopyFrom(data
[i
]).foldCase();
1274 int32_t length
= lcase
.length();
1276 if (length
> bestMatchLength
&&
1277 lcaseText
.compareBetween(0, length
, lcase
, 0, length
) == 0)
1280 bestMatchLength
= length
;
1285 cal
.set(field
, bestMatch
);
1287 // Once we have a match, we have to determine the length of the
1288 // original source string. This will usually be == the length of
1289 // the case folded string, but it may differ (e.g. sharp s).
1290 lcase
.fastCopyFrom(data
[bestMatch
]).foldCase();
1292 // Most of the time, the length will be the same as the length
1293 // of the string from the locale data. Sometimes it will be
1294 // different, in which case we will have to figure it out by
1295 // adding a character at a time, until we have a match. We do
1296 // this all in one loop, where we try 'len' first (at index
1298 int32_t len
= data
[bestMatch
].length(); // 99+% of the time
1299 int32_t n
= text
.length() - start
;
1300 for (i
=0; i
<=n
; ++i
) {
1304 } else if (i
== len
) {
1305 continue; // already tried this when i was 0
1307 text
.extract(start
, j
, lcaseText
);
1308 lcaseText
.foldCase();
1309 if (lcase
== lcaseText
) {
1318 //----------------------------------------------------------------------
1321 SimpleDateFormat::set2DigitYearStart(UDate d
, UErrorCode
& status
)
1323 parseAmbiguousDatesAsAfter(d
, status
);
1327 * Private member function that converts the parsed date strings into
1328 * timeFields. Returns -start (for ParsePosition) if failed.
1329 * @param text the time text to be parsed.
1330 * @param start where to start parsing.
1331 * @param ch the pattern character for the date field text to be parsed.
1332 * @param count the count of a pattern character.
1333 * @return the new start position if matching succeeded; a negative number
1334 * indicating matching failure, otherwise.
1336 int32_t SimpleDateFormat::subParse(const UnicodeString
& text
, int32_t& start
, UChar ch
, int32_t count
,
1337 UBool obeyCount
, UBool allowNegative
, UBool ambiguousYear
[], Calendar
& cal
) const
1342 ParsePosition
pos(0);
1343 int32_t patternCharIndex
;
1345 UChar
*patternCharPtr
= u_strchr(DateFormatSymbols::getPatternUChars(), ch
);
1347 #if defined (U_DEBUG_CAL)
1348 //fprintf(stderr, "%s:%d - [%c] st=%d \n", __FILE__, __LINE__, (char) ch, start);
1351 if (patternCharPtr
== NULL
) {
1355 patternCharIndex
= (UDateFormatField
)(patternCharPtr
- DateFormatSymbols::getPatternUChars());
1357 UCalendarDateFields field
= fgPatternIndexToCalendarField
[patternCharIndex
];
1359 // If there are any spaces here, skip over them. If we hit the end
1360 // of the string, then fail.
1362 if (start
>= text
.length()) {
1365 UChar32 c
= text
.char32At(start
);
1366 if (!u_isUWhiteSpace(c
)) {
1369 start
+= UTF_CHAR_LENGTH(c
);
1371 pos
.setIndex(start
);
1373 // We handle a few special cases here where we need to parse
1374 // a number value. We handle further, more generic cases below. We need
1375 // to handle some of them here because some fields require extra processing on
1376 // the parsed value.
1377 if (patternCharIndex
== UDAT_HOUR_OF_DAY1_FIELD
||
1378 patternCharIndex
== UDAT_HOUR1_FIELD
||
1379 (patternCharIndex
== UDAT_MONTH_FIELD
&& count
<= 2) ||
1380 (patternCharIndex
== UDAT_STANDALONE_MONTH_FIELD
&& count
<= 2) ||
1381 (patternCharIndex
== UDAT_QUARTER_FIELD
&& count
<= 2) ||
1382 (patternCharIndex
== UDAT_STANDALONE_QUARTER_FIELD
&& count
<= 2) ||
1383 patternCharIndex
== UDAT_YEAR_FIELD
||
1384 patternCharIndex
== UDAT_YEAR_WOY_FIELD
||
1385 patternCharIndex
== UDAT_FRACTIONAL_SECOND_FIELD
)
1387 int32_t parseStart
= pos
.getIndex();
1388 // It would be good to unify this with the obeyCount logic below,
1389 // but that's going to be difficult.
1390 const UnicodeString
* src
;
1393 if ((start
+count
) > text
.length()) {
1397 text
.extractBetween(0, start
+ count
, temp
);
1403 parseInt(*src
, number
, pos
, allowNegative
);
1405 if (pos
.getIndex() == parseStart
)
1407 value
= number
.getLong();
1410 switch (patternCharIndex
) {
1411 case UDAT_ERA_FIELD
:
1413 return matchString(text
, start
, UCAL_ERA
, fSymbols
->fEraNames
, fSymbols
->fEraNamesCount
, cal
);
1416 return matchString(text
, start
, UCAL_ERA
, fSymbols
->fEras
, fSymbols
->fErasCount
, cal
);
1418 case UDAT_YEAR_FIELD
:
1419 // If there are 3 or more YEAR pattern characters, this indicates
1420 // that the year value is to be treated literally, without any
1421 // two-digit year adjustments (e.g., from "01" to 2001). Otherwise
1422 // we made adjustments to place the 2-digit year in the proper
1423 // century, for parsed strings from "00" to "99". Any other string
1424 // is treated literally: "2250", "-1", "1", "002".
1425 if (count
<= 2 && (pos
.getIndex() - start
) == 2
1426 && u_isdigit(text
.charAt(start
))
1427 && u_isdigit(text
.charAt(start
+1)))
1429 // Assume for example that the defaultCenturyStart is 6/18/1903.
1430 // This means that two-digit years will be forced into the range
1431 // 6/18/1903 to 6/17/2003. As a result, years 00, 01, and 02
1432 // correspond to 2000, 2001, and 2002. Years 04, 05, etc. correspond
1433 // to 1904, 1905, etc. If the year is 03, then it is 2003 if the
1434 // other fields specify a date before 6/18, or 1903 if they specify a
1435 // date afterwards. As a result, 03 is an ambiguous year. All other
1436 // two-digit years are unambiguous.
1437 if(fHaveDefaultCentury
) { // check if this formatter even has a pivot year
1438 int32_t ambiguousTwoDigitYear
= fDefaultCenturyStartYear
% 100;
1439 ambiguousYear
[0] = (value
== ambiguousTwoDigitYear
);
1440 value
+= (fDefaultCenturyStartYear
/100)*100 +
1441 (value
< ambiguousTwoDigitYear
? 100 : 0);
1444 cal
.set(UCAL_YEAR
, value
);
1445 return pos
.getIndex();
1447 case UDAT_YEAR_WOY_FIELD
:
1448 // Comment is the same as for UDAT_Year_FIELDs - look above
1449 if (count
<= 2 && (pos
.getIndex() - start
) == 2
1450 && u_isdigit(text
.charAt(start
))
1451 && u_isdigit(text
.charAt(start
+1))
1452 && fHaveDefaultCentury
)
1454 int32_t ambiguousTwoDigitYear
= fDefaultCenturyStartYear
% 100;
1455 ambiguousYear
[0] = (value
== ambiguousTwoDigitYear
);
1456 value
+= (fDefaultCenturyStartYear
/100)*100 +
1457 (value
< ambiguousTwoDigitYear
? 100 : 0);
1459 cal
.set(UCAL_YEAR_WOY
, value
);
1460 return pos
.getIndex();
1462 case UDAT_MONTH_FIELD
:
1463 if (count
<= 2) // i.e., M or MM.
1465 // Don't want to parse the month if it is a string
1466 // while pattern uses numeric style: M or MM.
1467 // [We computed 'value' above.]
1468 cal
.set(UCAL_MONTH
, value
- 1);
1469 return pos
.getIndex();
1471 // count >= 3 // i.e., MMM or MMMM
1472 // Want to be able to parse both short and long forms.
1473 // Try count == 4 first:
1474 int32_t newStart
= 0;
1476 if ((newStart
= matchString(text
, start
, UCAL_MONTH
,
1477 fSymbols
->fMonths
, fSymbols
->fMonthsCount
, cal
)) > 0)
1479 else // count == 4 failed, now try count == 3
1480 return matchString(text
, start
, UCAL_MONTH
,
1481 fSymbols
->fShortMonths
, fSymbols
->fShortMonthsCount
, cal
);
1484 case UDAT_STANDALONE_MONTH_FIELD
:
1485 if (count
<= 2) // i.e., L or LL.
1487 // Don't want to parse the month if it is a string
1488 // while pattern uses numeric style: M or MM.
1489 // [We computed 'value' above.]
1490 cal
.set(UCAL_MONTH
, value
- 1);
1491 return pos
.getIndex();
1493 // count >= 3 // i.e., LLL or LLLL
1494 // Want to be able to parse both short and long forms.
1495 // Try count == 4 first:
1496 int32_t newStart
= 0;
1498 if ((newStart
= matchString(text
, start
, UCAL_MONTH
,
1499 fSymbols
->fStandaloneMonths
, fSymbols
->fStandaloneMonthsCount
, cal
)) > 0)
1501 else // count == 4 failed, now try count == 3
1502 return matchString(text
, start
, UCAL_MONTH
,
1503 fSymbols
->fStandaloneShortMonths
, fSymbols
->fStandaloneShortMonthsCount
, cal
);
1506 case UDAT_HOUR_OF_DAY1_FIELD
:
1507 // [We computed 'value' above.]
1508 if (value
== cal
.getMaximum(UCAL_HOUR_OF_DAY
) + 1)
1510 cal
.set(UCAL_HOUR_OF_DAY
, value
);
1511 return pos
.getIndex();
1513 case UDAT_FRACTIONAL_SECOND_FIELD
:
1514 // Fractional seconds left-justify
1515 i
= pos
.getIndex() - start
;
1527 value
= (value
+ (a
>>1)) / a
;
1529 cal
.set(UCAL_MILLISECOND
, value
);
1530 return pos
.getIndex();
1532 case UDAT_DAY_OF_WEEK_FIELD
:
1534 // Want to be able to parse both short and long forms.
1535 // Try count == 4 (DDDD) first:
1536 int32_t newStart
= 0;
1537 if ((newStart
= matchString(text
, start
, UCAL_DAY_OF_WEEK
,
1538 fSymbols
->fWeekdays
, fSymbols
->fWeekdaysCount
, cal
)) > 0)
1540 else // DDDD failed, now try DDD
1541 return matchString(text
, start
, UCAL_DAY_OF_WEEK
,
1542 fSymbols
->fShortWeekdays
, fSymbols
->fShortWeekdaysCount
, cal
);
1545 case UDAT_STANDALONE_DAY_FIELD
:
1547 // Want to be able to parse both short and long forms.
1548 // Try count == 4 (DDDD) first:
1549 int32_t newStart
= 0;
1550 if ((newStart
= matchString(text
, start
, UCAL_DAY_OF_WEEK
,
1551 fSymbols
->fStandaloneWeekdays
, fSymbols
->fStandaloneWeekdaysCount
, cal
)) > 0)
1553 else // DDDD failed, now try DDD
1554 return matchString(text
, start
, UCAL_DAY_OF_WEEK
,
1555 fSymbols
->fStandaloneShortWeekdays
, fSymbols
->fStandaloneShortWeekdaysCount
, cal
);
1558 case UDAT_AM_PM_FIELD
:
1559 return matchString(text
, start
, UCAL_AM_PM
, fSymbols
->fAmPms
, fSymbols
->fAmPmsCount
, cal
);
1561 case UDAT_HOUR1_FIELD
:
1562 // [We computed 'value' above.]
1563 if (value
== cal
.getLeastMaximum(UCAL_HOUR
)+1)
1565 cal
.set(UCAL_HOUR
, value
);
1566 return pos
.getIndex();
1568 case UDAT_QUARTER_FIELD
:
1569 if (count
<= 2) // i.e., Q or QQ.
1571 // Don't want to parse the month if it is a string
1572 // while pattern uses numeric style: Q or QQ.
1573 // [We computed 'value' above.]
1574 cal
.set(UCAL_MONTH
, (value
- 1) * 3);
1575 return pos
.getIndex();
1577 // count >= 3 // i.e., QQQ or QQQQ
1578 // Want to be able to parse both short and long forms.
1579 // Try count == 4 first:
1580 int32_t newStart
= 0;
1582 if ((newStart
= matchQuarterString(text
, start
, UCAL_MONTH
,
1583 fSymbols
->fQuarters
, fSymbols
->fQuartersCount
, cal
)) > 0)
1585 else // count == 4 failed, now try count == 3
1586 return matchQuarterString(text
, start
, UCAL_MONTH
,
1587 fSymbols
->fShortQuarters
, fSymbols
->fShortQuartersCount
, cal
);
1590 case UDAT_STANDALONE_QUARTER_FIELD
:
1591 if (count
<= 2) // i.e., q or qq.
1593 // Don't want to parse the month if it is a string
1594 // while pattern uses numeric style: q or q.
1595 // [We computed 'value' above.]
1596 cal
.set(UCAL_MONTH
, (value
- 1) * 3);
1597 return pos
.getIndex();
1599 // count >= 3 // i.e., qqq or qqqq
1600 // Want to be able to parse both short and long forms.
1601 // Try count == 4 first:
1602 int32_t newStart
= 0;
1604 if ((newStart
= matchQuarterString(text
, start
, UCAL_MONTH
,
1605 fSymbols
->fStandaloneQuarters
, fSymbols
->fStandaloneQuartersCount
, cal
)) > 0)
1607 else // count == 4 failed, now try count == 3
1608 return matchQuarterString(text
, start
, UCAL_MONTH
,
1609 fSymbols
->fStandaloneShortQuarters
, fSymbols
->fStandaloneShortQuartersCount
, cal
);
1612 case UDAT_TIMEZONE_FIELD
:
1613 case UDAT_TIMEZONE_RFC_FIELD
:
1614 case UDAT_TIMEZONE_GENERIC_FIELD
:
1616 // First try to parse generic forms such as GMT-07:00. Do this first
1617 // in case localized DateFormatZoneData contains the string "GMT"
1618 // for a zone; in that case, we don't want to match the first three
1619 // characters of GMT+/-HH:MM etc.
1623 int32_t gmtLen
= u_strlen(gGmt
);
1625 // For time zones that have no known names, look for strings
1627 // GMT[+-]hours:minutes or
1631 if ((text
.length() - start
) >= gmtLen
&&
1632 (text
.caseCompare(start
, gmtLen
, gGmt
, 0, gmtLen
, U_FOLD_CASE_DEFAULT
)) == 0)
1634 cal
.set(UCAL_DST_OFFSET
, 0);
1636 pos
.setIndex(start
+ gmtLen
);
1638 if( text
[pos
.getIndex()] == 0x002B /*'+'*/ )
1640 else if( text
[pos
.getIndex()] == 0x002D /*'-'*/ )
1643 cal
.set(UCAL_ZONE_OFFSET
, 0 );
1644 return pos
.getIndex();
1647 // Look for hours:minutes or hhmm.
1648 pos
.setIndex(pos
.getIndex() + 1);
1649 int32_t parseStart
= pos
.getIndex();
1650 Formattable tzNumber
;
1651 fNumberFormat
->parse(text
, tzNumber
, pos
);
1652 if( pos
.getIndex() == parseStart
) {
1655 if( text
[pos
.getIndex()] == 0x003A /*':'*/ ) {
1656 // This is the hours:minutes case
1657 offset
= tzNumber
.getLong() * 60;
1658 pos
.setIndex(pos
.getIndex() + 1);
1659 parseStart
= pos
.getIndex();
1660 fNumberFormat
->parse(text
, tzNumber
, pos
);
1661 if( pos
.getIndex() == parseStart
) {
1664 offset
+= tzNumber
.getLong();
1667 // This is the hhmm case.
1668 offset
= tzNumber
.getLong();
1672 offset
= offset
% 100 + offset
/ 100 * 60;
1675 // Fall through for final processing below of 'offset' and 'sign'.
1678 // At this point, check for named time zones by looking through
1679 // the locale data from the DateFormatZoneData strings.
1680 // Want to be able to parse both short and long forms.
1681 // !!! side effect, might set parsedZoneString
1682 UErrorCode status
= U_ZERO_ERROR
;
1683 int32_t result
= subParseZoneString(text
, start
, cal
, status
);
1688 // As a last resort, look for numeric timezones of the form
1689 // [+-]hhmm as specified by RFC 822. This code is actually
1690 // a little more permissive than RFC 822. It will try to do
1691 // its best with numbers that aren't strictly 4 digits long
1692 DecimalFormat
fmt(UNICODE_STRING_SIMPLE("+####;-####"), status
);
1693 if(U_FAILURE(status
))
1695 fmt
.setParseIntegerOnly(TRUE
);
1696 int32_t parseStart
= pos
.getIndex();
1697 Formattable tzNumber
;
1698 fmt
.parse( text
, tzNumber
, pos
);
1699 if( pos
.getIndex() == parseStart
) {
1700 return -start
; // Wasn't actually a number.
1702 offset
= tzNumber
.getLong();
1709 offset
= offset
* 60;
1711 offset
= offset
% 100 + offset
/ 100 * 60;
1713 // Fall through for final processing below of 'offset' and 'sign'.
1716 // Do the final processing for both of the above cases. We only
1717 // arrive here if the form GMT+/-... or an RFC 822 form was seen.
1720 offset
*= U_MILLIS_PER_MINUTE
* sign
;
1722 if (cal
.getTimeZone().useDaylightTime())
1724 cal
.set(UCAL_DST_OFFSET
, U_MILLIS_PER_HOUR
);
1725 offset
-= U_MILLIS_PER_HOUR
;
1727 cal
.set(UCAL_ZONE_OFFSET
, offset
);
1729 return pos
.getIndex();
1732 // All efforts to parse a zone failed.
1737 // Handle "generic" fields
1738 int32_t parseStart
= pos
.getIndex();
1739 const UnicodeString
* src
;
1741 if ((start
+count
) > text
.length()) {
1744 text
.extractBetween(0, start
+ count
, temp
);
1749 parseInt(*src
, number
, pos
, allowNegative
);
1750 if (pos
.getIndex() != parseStart
) {
1751 cal
.set(field
, number
.getLong());
1752 return pos
.getIndex();
1759 SimpleDateFormat::subParseZoneString(const UnicodeString
& text
, int32_t start
, Calendar
& cal
, UErrorCode
& status
) const
1761 // At this point, check for named time zones by looking through
1762 // the locale data from the DateFormatZoneData strings.
1763 // Want to be able to parse both short and long forms.
1765 // optimize for calendar's current time zone
1766 TimeZone
*tz
= NULL
;
1768 UnicodeString zid
, value
;
1769 DateFormatSymbols::TimeZoneTranslationType type
= DateFormatSymbols::TIMEZONE_COUNT
;
1770 fSymbols
->getZoneID(getTimeZone().getID(id
), zid
, status
);
1771 if(zid
.length() > 0){
1772 fSymbols
->findZoneIDTypeValue(zid
, text
, start
, type
, value
, status
);
1773 if(type
!= DateFormatSymbols::TIMEZONE_COUNT
) {
1774 tz
= TimeZone::createTimeZone(zid
);
1778 if(U_FAILURE(status
)){
1781 if (tz
!= NULL
) { // Matched any ?
1782 // always set zone offset, needed to get correct hour in wall time
1783 // when checking daylight savings
1784 cal
.set(UCAL_ZONE_OFFSET
, tz
->getRawOffset());
1785 if (type
==DateFormatSymbols::TIMEZONE_SHORT_STANDARD
|| type
==DateFormatSymbols::TIMEZONE_LONG_STANDARD
) {
1787 cal
.set(UCAL_DST_OFFSET
, 0);
1788 delete tz
; tz
= NULL
;
1789 } else if (type
==DateFormatSymbols::TIMEZONE_SHORT_DAYLIGHT
|| type
==DateFormatSymbols::TIMEZONE_LONG_DAYLIGHT
) {
1791 // !!! todo - no getDSTSavings() in ICU's timezone
1792 // use the correct DST SAVINGS for the zone.
1793 // cal.set(UCAL_DST_OFFSET, tz->getDSTSavings());
1794 cal
.set(UCAL_DST_OFFSET
, U_MILLIS_PER_HOUR
);
1795 delete tz
; tz
= NULL
;
1797 // either standard or daylight
1798 // need to finish getting the date, then compute dst offset as appropriate
1800 // !!! hack for api compatibility, can't modify subParse(...) so can't
1801 // pass this back any other way. cast away const.
1802 ((SimpleDateFormat
*)this)->parsedTimeZone
= tz
;
1805 return start
+ value
.length();
1814 * Parse an integer using fNumberFormat. This method is semantically
1815 * const, but actually may modify fNumberFormat.
1817 void SimpleDateFormat::parseInt(const UnicodeString
& text
,
1818 Formattable
& number
,
1820 UBool allowNegative
) const {
1821 UnicodeString oldPrefix
;
1822 DecimalFormat
* df
= NULL
;
1823 if (!allowNegative
&&
1824 fNumberFormat
->getDynamicClassID() == DecimalFormat::getStaticClassID()) {
1825 df
= (DecimalFormat
*)fNumberFormat
;
1826 df
->getNegativePrefix(oldPrefix
);
1827 df
->setNegativePrefix(SUPPRESS_NEGATIVE_PREFIX
);
1829 fNumberFormat
->parse(text
, number
, pos
);
1831 df
->setNegativePrefix(oldPrefix
);
1835 //----------------------------------------------------------------------
1837 void SimpleDateFormat::translatePattern(const UnicodeString
& originalPattern
,
1838 UnicodeString
& translatedPattern
,
1839 const UnicodeString
& from
,
1840 const UnicodeString
& to
,
1843 // run through the pattern and convert any pattern symbols from the version
1844 // in "from" to the corresponding character ion "to". This code takes
1845 // quoted strings into account (it doesn't try to translate them), and it signals
1846 // an error if a particular "pattern character" doesn't appear in "from".
1847 // Depending on the values of "from" and "to" this can convert from generic
1848 // to localized patterns or localized to generic.
1849 if (U_FAILURE(status
))
1852 translatedPattern
.remove();
1853 UBool inQuote
= FALSE
;
1854 for (int32_t i
= 0; i
< originalPattern
.length(); ++i
) {
1855 UChar c
= originalPattern
[i
];
1863 else if ((c
>= 0x0061 /*'a'*/ && c
<= 0x007A) /*'z'*/
1864 || (c
>= 0x0041 /*'A'*/ && c
<= 0x005A /*'Z'*/)) {
1865 int32_t ci
= from
.indexOf(c
);
1867 status
= U_INVALID_FORMAT_ERROR
;
1873 translatedPattern
+= c
;
1876 status
= U_INVALID_FORMAT_ERROR
;
1881 //----------------------------------------------------------------------
1884 SimpleDateFormat::toPattern(UnicodeString
& result
) const
1890 //----------------------------------------------------------------------
1893 SimpleDateFormat::toLocalizedPattern(UnicodeString
& result
,
1894 UErrorCode
& status
) const
1896 translatePattern(fPattern
, result
, DateFormatSymbols::getPatternUChars(), fSymbols
->fLocalPatternChars
, status
);
1900 //----------------------------------------------------------------------
1903 SimpleDateFormat::applyPattern(const UnicodeString
& pattern
)
1908 //----------------------------------------------------------------------
1911 SimpleDateFormat::applyLocalizedPattern(const UnicodeString
& pattern
,
1914 translatePattern(pattern
, fPattern
, fSymbols
->fLocalPatternChars
, DateFormatSymbols::getPatternUChars(), status
);
1917 //----------------------------------------------------------------------
1919 const DateFormatSymbols
*
1920 SimpleDateFormat::getDateFormatSymbols() const
1925 //----------------------------------------------------------------------
1928 SimpleDateFormat::adoptDateFormatSymbols(DateFormatSymbols
* newFormatSymbols
)
1931 fSymbols
= newFormatSymbols
;
1934 //----------------------------------------------------------------------
1936 SimpleDateFormat::setDateFormatSymbols(const DateFormatSymbols
& newFormatSymbols
)
1939 fSymbols
= new DateFormatSymbols(newFormatSymbols
);
1943 //----------------------------------------------------------------------
1946 void SimpleDateFormat::adoptCalendar(Calendar
* calendarToAdopt
)
1948 UErrorCode status
= U_ZERO_ERROR
;
1949 DateFormat::adoptCalendar(calendarToAdopt
);
1952 initializeSymbols(fLocale
, fCalendar
, status
); // we need new symbols
1953 initializeDefaultCentury(); // we need a new century (possibly)
1958 #endif /* #if !UCONFIG_NO_FORMATTING */