1 // © 2016 and later: Unicode, Inc. and others.
2 // License & terms of use: http://www.unicode.org/copyright.html
4 *******************************************************************************
5 * Copyright (C) 1997-2016, International Business Machines Corporation and *
6 * others. All Rights Reserved. *
7 *******************************************************************************
11 * Modification History:
13 * Date Name Description
14 * 02/19/97 aliu Converted from java.
15 * 03/31/97 aliu Modified extensively to work with 50 locales.
16 * 04/01/97 aliu Added support for centuries.
17 * 07/09/97 helena Made ParsePosition into a class.
18 * 07/21/98 stephen Added initializeDefaultCentury.
19 * Removed getZoneIndex (added in DateFormatSymbols)
20 * Removed subParseLong
22 * 02/22/99 stephen Removed character literals for EBCDIC safety
23 * 10/14/99 aliu Updated 2-digit year parsing so that only "00" thru
24 * "99" are recognized. {j28 4182066}
25 * 11/15/99 weiv Added support for week of year/day of week format
26 ********************************************************************************
29 #define ZID_KEY_MAX 128
31 #include "unicode/utypes.h"
33 #if !UCONFIG_NO_FORMATTING
34 #include "unicode/smpdtfmt.h"
35 #include "unicode/dtfmtsym.h"
36 #include "unicode/ures.h"
37 #include "unicode/msgfmt.h"
38 #include "unicode/calendar.h"
39 #include "unicode/gregocal.h"
40 #include "unicode/timezone.h"
41 #include "unicode/decimfmt.h"
42 #include "unicode/dcfmtsym.h"
43 #include "unicode/uchar.h"
44 #include "unicode/uniset.h"
45 #include "unicode/ustring.h"
46 #include "unicode/basictz.h"
47 #include "unicode/simpleformatter.h"
48 #include "unicode/simpletz.h"
49 #include "unicode/rbtz.h"
50 #include "unicode/tzfmt.h"
51 #include "unicode/ucasemap.h"
52 #include "unicode/utf16.h"
53 #include "unicode/vtzone.h"
54 #include "unicode/udisplaycontext.h"
55 #include "unicode/brkiter.h"
58 #include "patternprops.h"
67 #include "sharednumberformat.h"
68 #include "ucasemap_imp.h"
73 #include "dayperiodrules.h"
74 #include "tznames_impl.h" // ZONE_NAME_U16_MAX
76 #if defined( U_DEBUG_CALSVC ) || defined (U_DEBUG_CAL)
80 // *****************************************************************************
81 // class SimpleDateFormat
82 // *****************************************************************************
87 * Last-resort string to use for "GMT" when constructing time zone strings.
89 // For time zones that have no names, use strings GMT+minutes and
90 // GMT-minutes. For instance, in France the time zone is GMT+60.
91 // Also accepted are GMT+H:MM or GMT-H:MM.
92 // Currently not being used
93 //static const UChar gGmt[] = {0x0047, 0x004D, 0x0054, 0x0000}; // "GMT"
94 //static const UChar gGmtPlus[] = {0x0047, 0x004D, 0x0054, 0x002B, 0x0000}; // "GMT+"
95 //static const UChar gGmtMinus[] = {0x0047, 0x004D, 0x0054, 0x002D, 0x0000}; // "GMT-"
96 //static const UChar gDefGmtPat[] = {0x0047, 0x004D, 0x0054, 0x007B, 0x0030, 0x007D, 0x0000}; /* GMT{0} */
97 //static const UChar gDefGmtNegHmsPat[] = {0x002D, 0x0048, 0x0048, 0x003A, 0x006D, 0x006D, 0x003A, 0x0073, 0x0073, 0x0000}; /* -HH:mm:ss */
98 //static const UChar gDefGmtNegHmPat[] = {0x002D, 0x0048, 0x0048, 0x003A, 0x006D, 0x006D, 0x0000}; /* -HH:mm */
99 //static const UChar gDefGmtPosHmsPat[] = {0x002B, 0x0048, 0x0048, 0x003A, 0x006D, 0x006D, 0x003A, 0x0073, 0x0073, 0x0000}; /* +HH:mm:ss */
100 //static const UChar gDefGmtPosHmPat[] = {0x002B, 0x0048, 0x0048, 0x003A, 0x006D, 0x006D, 0x0000}; /* +HH:mm */
101 //static const UChar gUt[] = {0x0055, 0x0054, 0x0000}; // "UT"
102 //static const UChar gUtc[] = {0x0055, 0x0054, 0x0043, 0x0000}; // "UT"
104 typedef enum GmtPatSize
{
115 // Stuff needed for numbering system overrides
117 typedef enum OvrStrType
{
123 static const UDateFormatField kDateFields
[] = {
127 UDAT_DAY_OF_YEAR_FIELD
,
128 UDAT_DAY_OF_WEEK_IN_MONTH_FIELD
,
129 UDAT_WEEK_OF_YEAR_FIELD
,
130 UDAT_WEEK_OF_MONTH_FIELD
,
132 UDAT_EXTENDED_YEAR_FIELD
,
133 UDAT_JULIAN_DAY_FIELD
,
134 UDAT_STANDALONE_DAY_FIELD
,
135 UDAT_STANDALONE_MONTH_FIELD
,
137 UDAT_STANDALONE_QUARTER_FIELD
,
138 UDAT_YEAR_NAME_FIELD
,
139 UDAT_RELATED_YEAR_FIELD
};
140 static const int8_t kDateFieldsCount
= 16;
142 static const UDateFormatField kTimeFields
[] = {
143 UDAT_HOUR_OF_DAY1_FIELD
,
144 UDAT_HOUR_OF_DAY0_FIELD
,
147 UDAT_FRACTIONAL_SECOND_FIELD
,
150 UDAT_MILLISECONDS_IN_DAY_FIELD
,
151 UDAT_TIMEZONE_RFC_FIELD
,
152 UDAT_TIMEZONE_LOCALIZED_GMT_OFFSET_FIELD
};
153 static const int8_t kTimeFieldsCount
= 10;
156 // This is a pattern-of-last-resort used when we can't load a usable pattern out
158 static const UChar gDefaultPattern
[] =
160 0x79, 0x79, 0x79, 0x79, 0x4D, 0x4D, 0x64, 0x64, 0x20, 0x68, 0x68, 0x3A, 0x6D, 0x6D, 0x20, 0x61, 0
161 }; /* "yyyyMMdd hh:mm a" */
163 // This prefix is designed to NEVER MATCH real text, in order to
164 // suppress the parsing of negative numbers. Adjust as needed (if
165 // this becomes valid Unicode).
166 static const UChar SUPPRESS_NEGATIVE_PREFIX
[] = {0xAB00, 0};
169 * These are the tags we expect to see in normal resource bundle files associated
172 static const UChar QUOTE
= 0x27; // Single quote
175 * The field range check bias for each UDateFormatField.
176 * The bias is added to the minimum and maximum values
177 * before they are compared to the parsed number.
178 * For example, the calendar stores zero-based month numbers
179 * but the parsed month numbers start at 1, so the bias is 1.
181 * A value of -1 means that the value is not checked.
183 static const int32_t gFieldRangeBias
[] = {
184 -1, // 'G' - UDAT_ERA_FIELD
185 -1, // 'y' - UDAT_YEAR_FIELD
186 1, // 'M' - UDAT_MONTH_FIELD
187 0, // 'd' - UDAT_DATE_FIELD
188 -1, // 'k' - UDAT_HOUR_OF_DAY1_FIELD
189 -1, // 'H' - UDAT_HOUR_OF_DAY0_FIELD
190 0, // 'm' - UDAT_MINUTE_FIELD
191 0, // 's' - UDAT_SECOND_FIELD
192 -1, // 'S' - UDAT_FRACTIONAL_SECOND_FIELD (0-999?)
193 -1, // 'E' - UDAT_DAY_OF_WEEK_FIELD (1-7?)
194 -1, // 'D' - UDAT_DAY_OF_YEAR_FIELD (1 - 366?)
195 -1, // 'F' - UDAT_DAY_OF_WEEK_IN_MONTH_FIELD (1-5?)
196 -1, // 'w' - UDAT_WEEK_OF_YEAR_FIELD (1-52?)
197 -1, // 'W' - UDAT_WEEK_OF_MONTH_FIELD (1-5?)
198 -1, // 'a' - UDAT_AM_PM_FIELD
199 -1, // 'h' - UDAT_HOUR1_FIELD
200 -1, // 'K' - UDAT_HOUR0_FIELD
201 -1, // 'z' - UDAT_TIMEZONE_FIELD
202 -1, // 'Y' - UDAT_YEAR_WOY_FIELD
203 -1, // 'e' - UDAT_DOW_LOCAL_FIELD
204 -1, // 'u' - UDAT_EXTENDED_YEAR_FIELD
205 -1, // 'g' - UDAT_JULIAN_DAY_FIELD
206 -1, // 'A' - UDAT_MILLISECONDS_IN_DAY_FIELD
207 -1, // 'Z' - UDAT_TIMEZONE_RFC_FIELD
208 -1, // 'v' - UDAT_TIMEZONE_GENERIC_FIELD
209 0, // 'c' - UDAT_STANDALONE_DAY_FIELD
210 1, // 'L' - UDAT_STANDALONE_MONTH_FIELD
211 -1, // 'Q' - UDAT_QUARTER_FIELD (1-4?)
212 -1, // 'q' - UDAT_STANDALONE_QUARTER_FIELD
213 -1, // 'V' - UDAT_TIMEZONE_SPECIAL_FIELD
214 -1, // 'U' - UDAT_YEAR_NAME_FIELD
215 -1, // 'O' - UDAT_TIMEZONE_LOCALIZED_GMT_OFFSET_FIELD
216 -1, // 'X' - UDAT_TIMEZONE_ISO_FIELD
217 -1, // 'x' - UDAT_TIMEZONE_ISO_LOCAL_FIELD
218 -1, // 'r' - UDAT_RELATED_YEAR_FIELD
219 #if UDAT_HAS_PATTERN_CHAR_FOR_TIME_SEPARATOR
220 -1, // ':' - UDAT_TIME_SEPARATOR_FIELD
222 -1, // (no pattern character currently) - UDAT_TIME_SEPARATOR_FIELD
225 // A slightly looser range check for lenient parsing
226 static const int32_t gFieldRangeBiasLenient
[] = {
227 -1, // 'G' - UDAT_ERA_FIELD
228 -1, // 'y' - UDAT_YEAR_FIELD
229 8, // 'M' - UDAT_MONTH_FIELD (allow calendar max + 7, e.g. 19 for grego 1-based month)
230 18, // 'd' - UDAT_DATE_FIELD (allow calendar max + 18, e.g. 49 for grego; tests require at least 40 for grego)
231 -1, // 'k' - UDAT_HOUR_OF_DAY1_FIELD
232 -1, // 'H' - UDAT_HOUR_OF_DAY0_FIELD
233 40, // 'm' - UDAT_MINUTE_FIELD (allow calendar max + 40, e.g. 99)
234 40, // 's' - UDAT_SECOND_FIELD (allow calendar max + 40, e.g. 99)
235 -1, // 'S' - UDAT_FRACTIONAL_SECOND_FIELD (0-999?)
236 -1, // 'E' - UDAT_DAY_OF_WEEK_FIELD (1-7?)
237 -1, // 'D' - UDAT_DAY_OF_YEAR_FIELD (1 - 366?)
238 -1, // 'F' - UDAT_DAY_OF_WEEK_IN_MONTH_FIELD (1-5?)
239 -1, // 'w' - UDAT_WEEK_OF_YEAR_FIELD (1-52?)
240 -1, // 'W' - UDAT_WEEK_OF_MONTH_FIELD (1-5?)
241 -1, // 'a' - UDAT_AM_PM_FIELD
242 -1, // 'h' - UDAT_HOUR1_FIELD
243 -1, // 'K' - UDAT_HOUR0_FIELD
244 -1, // 'z' - UDAT_TIMEZONE_FIELD
245 -1, // 'Y' - UDAT_YEAR_WOY_FIELD
246 -1, // 'e' - UDAT_DOW_LOCAL_FIELD
247 -1, // 'u' - UDAT_EXTENDED_YEAR_FIELD
248 -1, // 'g' - UDAT_JULIAN_DAY_FIELD
249 -1, // 'A' - UDAT_MILLISECONDS_IN_DAY_FIELD
250 -1, // 'Z' - UDAT_TIMEZONE_RFC_FIELD
251 -1, // 'v' - UDAT_TIMEZONE_GENERIC_FIELD
252 18, // 'c' - UDAT_STANDALONE_DAY_FIELD (allow calendar max + 18, e.g. 49 for grego)
253 8, // 'L' - UDAT_STANDALONE_MONTH_FIELD (allow calendar max + 7, e.g. 19 for grego 1-based month)
254 -1, // 'Q' - UDAT_QUARTER_FIELD (1-4?)
255 -1, // 'q' - UDAT_STANDALONE_QUARTER_FIELD
256 -1, // 'V' - UDAT_TIMEZONE_SPECIAL_FIELD
257 -1, // 'U' - UDAT_YEAR_NAME_FIELD
258 -1, // 'O' - UDAT_TIMEZONE_LOCALIZED_GMT_OFFSET_FIELD
259 -1, // 'X' - UDAT_TIMEZONE_ISO_FIELD
260 -1, // 'x' - UDAT_TIMEZONE_ISO_LOCAL_FIELD
261 -1, // 'r' - UDAT_RELATED_YEAR_FIELD
262 #if UDAT_HAS_PATTERN_CHAR_FOR_TIME_SEPARATOR
263 -1, // ':' - UDAT_TIME_SEPARATOR_FIELD
265 -1, // (no pattern character currently) - UDAT_TIME_SEPARATOR_FIELD
269 // When calendar uses hebr numbering (i.e. he@calendar=hebrew),
270 // offset the years within the current millenium down to 1-999
271 static const int32_t HEBREW_CAL_CUR_MILLENIUM_START_YEAR
= 5000;
272 static const int32_t HEBREW_CAL_CUR_MILLENIUM_END_YEAR
= 6000;
274 static UMutex LOCK
= U_MUTEX_INITIALIZER
;
276 UOBJECT_DEFINE_RTTI_IMPLEMENTATION(SimpleDateFormat
)
278 SimpleDateFormat::NSOverride::~NSOverride() {
285 void SimpleDateFormat::NSOverride::free() {
286 NSOverride
*cur
= this;
288 NSOverride
*next
= cur
->next
;
294 // no matter what the locale's default number format looked like, we want
295 // to modify it so that it doesn't use thousands separators, doesn't always
296 // show the decimal point, and recognizes integers only when parsing
297 static void fixNumberFormatForDates(NumberFormat
&nf
) {
298 nf
.setGroupingUsed(FALSE
);
299 DecimalFormat
* decfmt
= dynamic_cast<DecimalFormat
*>(&nf
);
300 if (decfmt
!= NULL
) {
301 decfmt
->setDecimalSeparatorAlwaysShown(FALSE
);
303 nf
.setParseIntegerOnly(TRUE
);
304 nf
.setMinimumFractionDigits(0); // To prevent "Jan 1.00, 1997.00"
307 static const SharedNumberFormat
*createSharedNumberFormat(
308 NumberFormat
*nfToAdopt
) {
309 fixNumberFormatForDates(*nfToAdopt
);
310 const SharedNumberFormat
*result
= new SharedNumberFormat(nfToAdopt
);
311 if (result
== NULL
) {
317 static const SharedNumberFormat
*createSharedNumberFormat(
318 const Locale
&loc
, UErrorCode
&status
) {
319 NumberFormat
*nf
= NumberFormat::createInstance(loc
, status
);
320 if (U_FAILURE(status
)) {
323 const SharedNumberFormat
*result
= createSharedNumberFormat(nf
);
324 if (result
== NULL
) {
325 status
= U_MEMORY_ALLOCATION_ERROR
;
330 static const SharedNumberFormat
**allocSharedNumberFormatters() {
331 const SharedNumberFormat
**result
= (const SharedNumberFormat
**)
332 uprv_malloc(UDAT_FIELD_COUNT
* sizeof(const SharedNumberFormat
*));
333 if (result
== NULL
) {
336 for (int32_t i
= 0; i
< UDAT_FIELD_COUNT
; ++i
) {
342 static void freeSharedNumberFormatters(const SharedNumberFormat
** list
) {
343 for (int32_t i
= 0; i
< UDAT_FIELD_COUNT
; ++i
) {
344 SharedObject::clearPtr(list
[i
]);
349 const NumberFormat
*SimpleDateFormat::getNumberFormatByIndex(
350 UDateFormatField index
) const {
351 if (fSharedNumberFormatters
== NULL
||
352 fSharedNumberFormatters
[index
] == NULL
) {
353 return fNumberFormat
;
355 return &(**fSharedNumberFormatters
[index
]);
358 class SimpleDateFormatMutableNFNode
{
360 const NumberFormat
*key
;
362 SimpleDateFormatMutableNFNode()
363 : key(NULL
), value(NULL
) { }
364 ~SimpleDateFormatMutableNFNode() {
368 SimpleDateFormatMutableNFNode(const SimpleDateFormatMutableNFNode
&);
369 SimpleDateFormatMutableNFNode
&operator=(const SimpleDateFormatMutableNFNode
&);
372 // Single threaded cache of non const NumberFormats. Designed to be stack
373 // allocated and used for a single format call.
374 class SimpleDateFormatMutableNFs
: public UMemory
{
376 SimpleDateFormatMutableNFs() {
379 // Returns a non-const clone of nf which can be safely modified.
380 // Subsequent calls with same nf will return the same non-const clone.
381 // This object maintains ownership of all returned non-const
382 // NumberFormat objects. On memory allocation error returns NULL.
383 // Caller must check for NULL return value.
384 NumberFormat
*get(const NumberFormat
*nf
) {
389 while (nodes
[idx
].value
) {
390 if (nf
== nodes
[idx
].key
) {
391 return nodes
[idx
].value
;
395 U_ASSERT(idx
< UDAT_FIELD_COUNT
);
397 nodes
[idx
].value
= (NumberFormat
*) nf
->clone();
398 return nodes
[idx
].value
;
401 // +1 extra for sentinel. If each field had its own NumberFormat, this
402 // cache would have to allocate UDAT_FIELD_COUNT mutable versions worst
404 SimpleDateFormatMutableNFNode nodes
[UDAT_FIELD_COUNT
+ 1];
405 SimpleDateFormatMutableNFs(const SimpleDateFormatMutableNFs
&);
406 SimpleDateFormatMutableNFs
&operator=(const SimpleDateFormatMutableNFs
&);
409 //----------------------------------------------------------------------
411 SimpleDateFormat::~SimpleDateFormat()
414 if (fSharedNumberFormatters
) {
415 freeSharedNumberFormatters(fSharedNumberFormatters
);
417 if (fTimeZoneFormat
) {
418 delete fTimeZoneFormat
;
421 #if !UCONFIG_NO_BREAK_ITERATION
422 delete fCapitalizationBrkIter
;
426 //----------------------------------------------------------------------
428 SimpleDateFormat::SimpleDateFormat(UErrorCode
& status
)
429 : fLocale(Locale::getDefault()),
431 fTimeZoneFormat(NULL
),
432 fSharedNumberFormatters(NULL
),
433 fCapitalizationBrkIter(NULL
)
435 initializeBooleanAttributes();
436 construct(kShort
, (EStyle
) (kShort
+ kDateOffset
), fLocale
, status
);
437 initializeDefaultCentury();
440 //----------------------------------------------------------------------
442 SimpleDateFormat::SimpleDateFormat(const UnicodeString
& pattern
,
445 fLocale(Locale::getDefault()),
447 fTimeZoneFormat(NULL
),
448 fSharedNumberFormatters(NULL
),
449 fCapitalizationBrkIter(NULL
)
451 fDateOverride
.setToBogus();
452 fTimeOverride
.setToBogus();
453 initializeBooleanAttributes();
454 initializeCalendar(NULL
,fLocale
,status
);
455 fSymbols
= DateFormatSymbols::createForLocale(fLocale
, status
);
456 initialize(fLocale
, status
);
457 initializeDefaultCentury();
461 //----------------------------------------------------------------------
463 SimpleDateFormat::SimpleDateFormat(const UnicodeString
& pattern
,
464 const UnicodeString
& override
,
467 fLocale(Locale::getDefault()),
469 fTimeZoneFormat(NULL
),
470 fSharedNumberFormatters(NULL
),
471 fCapitalizationBrkIter(NULL
)
473 fDateOverride
.setTo(override
);
474 fTimeOverride
.setToBogus();
475 initializeBooleanAttributes();
476 initializeCalendar(NULL
,fLocale
,status
);
477 fSymbols
= DateFormatSymbols::createForLocale(fLocale
, status
);
478 initialize(fLocale
, status
);
479 initializeDefaultCentury();
481 processOverrideString(fLocale
,override
,kOvrStrBoth
,status
);
485 //----------------------------------------------------------------------
487 SimpleDateFormat::SimpleDateFormat(const UnicodeString
& pattern
,
488 const Locale
& locale
,
492 fTimeZoneFormat(NULL
),
493 fSharedNumberFormatters(NULL
),
494 fCapitalizationBrkIter(NULL
)
497 fDateOverride
.setToBogus();
498 fTimeOverride
.setToBogus();
499 initializeBooleanAttributes();
501 initializeCalendar(NULL
,fLocale
,status
);
502 fSymbols
= DateFormatSymbols::createForLocale(fLocale
, status
);
503 initialize(fLocale
, status
);
504 initializeDefaultCentury();
507 //----------------------------------------------------------------------
509 SimpleDateFormat::SimpleDateFormat(const UnicodeString
& pattern
,
510 const UnicodeString
& override
,
511 const Locale
& locale
,
515 fTimeZoneFormat(NULL
),
516 fSharedNumberFormatters(NULL
),
517 fCapitalizationBrkIter(NULL
)
520 fDateOverride
.setTo(override
);
521 fTimeOverride
.setToBogus();
522 initializeBooleanAttributes();
524 initializeCalendar(NULL
,fLocale
,status
);
525 fSymbols
= DateFormatSymbols::createForLocale(fLocale
, status
);
526 initialize(fLocale
, status
);
527 initializeDefaultCentury();
529 processOverrideString(locale
,override
,kOvrStrBoth
,status
);
533 //----------------------------------------------------------------------
535 SimpleDateFormat::SimpleDateFormat(const UnicodeString
& pattern
,
536 DateFormatSymbols
* symbolsToAdopt
,
539 fLocale(Locale::getDefault()),
540 fSymbols(symbolsToAdopt
),
541 fTimeZoneFormat(NULL
),
542 fSharedNumberFormatters(NULL
),
543 fCapitalizationBrkIter(NULL
)
546 fDateOverride
.setToBogus();
547 fTimeOverride
.setToBogus();
548 initializeBooleanAttributes();
550 initializeCalendar(NULL
,fLocale
,status
);
551 initialize(fLocale
, status
);
552 initializeDefaultCentury();
555 //----------------------------------------------------------------------
557 SimpleDateFormat::SimpleDateFormat(const UnicodeString
& pattern
,
558 const DateFormatSymbols
& symbols
,
561 fLocale(Locale::getDefault()),
562 fSymbols(new DateFormatSymbols(symbols
)),
563 fTimeZoneFormat(NULL
),
564 fSharedNumberFormatters(NULL
),
565 fCapitalizationBrkIter(NULL
)
568 fDateOverride
.setToBogus();
569 fTimeOverride
.setToBogus();
570 initializeBooleanAttributes();
572 initializeCalendar(NULL
, fLocale
, status
);
573 initialize(fLocale
, status
);
574 initializeDefaultCentury();
577 //----------------------------------------------------------------------
579 // Not for public consumption; used by DateFormat
580 SimpleDateFormat::SimpleDateFormat(EStyle timeStyle
,
582 const Locale
& locale
,
586 fTimeZoneFormat(NULL
),
587 fSharedNumberFormatters(NULL
),
588 fCapitalizationBrkIter(NULL
)
590 initializeBooleanAttributes();
591 construct(timeStyle
, dateStyle
, fLocale
, status
);
592 if(U_SUCCESS(status
)) {
593 initializeDefaultCentury();
597 //----------------------------------------------------------------------
600 * Not for public consumption; used by DateFormat. This constructor
601 * never fails. If the resource data is not available, it uses the
602 * the last resort symbols.
604 SimpleDateFormat::SimpleDateFormat(const Locale
& locale
,
606 : fPattern(gDefaultPattern
),
609 fTimeZoneFormat(NULL
),
610 fSharedNumberFormatters(NULL
),
611 fCapitalizationBrkIter(NULL
)
613 if (U_FAILURE(status
)) return;
614 initializeBooleanAttributes();
615 initializeCalendar(NULL
, fLocale
, status
);
616 fSymbols
= DateFormatSymbols::createForLocale(fLocale
, status
);
617 if (U_FAILURE(status
))
619 status
= U_ZERO_ERROR
;
621 // This constructor doesn't fail; it uses last resort data
622 fSymbols
= new DateFormatSymbols(status
);
625 status
= U_MEMORY_ALLOCATION_ERROR
;
630 fDateOverride
.setToBogus();
631 fTimeOverride
.setToBogus();
633 initialize(fLocale
, status
);
634 if(U_SUCCESS(status
)) {
635 initializeDefaultCentury();
639 //----------------------------------------------------------------------
641 SimpleDateFormat::SimpleDateFormat(const SimpleDateFormat
& other
)
643 fLocale(other
.fLocale
),
645 fTimeZoneFormat(NULL
),
646 fSharedNumberFormatters(NULL
),
647 fCapitalizationBrkIter(NULL
)
649 initializeBooleanAttributes();
653 //----------------------------------------------------------------------
655 SimpleDateFormat
& SimpleDateFormat::operator=(const SimpleDateFormat
& other
)
657 if (this == &other
) {
660 DateFormat::operator=(other
);
661 fDateOverride
= other
.fDateOverride
;
662 fTimeOverride
= other
.fTimeOverride
;
668 fSymbols
= new DateFormatSymbols(*other
.fSymbols
);
670 fDefaultCenturyStart
= other
.fDefaultCenturyStart
;
671 fDefaultCenturyStartYear
= other
.fDefaultCenturyStartYear
;
672 fHaveDefaultCentury
= other
.fHaveDefaultCentury
;
674 fPattern
= other
.fPattern
;
675 fHasMinute
= other
.fHasMinute
;
676 fHasSecond
= other
.fHasSecond
;
678 fLocale
= other
.fLocale
;
679 // TimeZoneFormat can now be set independently via setter.
680 // If it is NULL, it will be lazily initialized from locale
681 delete fTimeZoneFormat
;
682 fTimeZoneFormat
= NULL
;
683 if (other
.fTimeZoneFormat
) {
684 fTimeZoneFormat
= new TimeZoneFormat(*other
.fTimeZoneFormat
);
687 #if !UCONFIG_NO_BREAK_ITERATION
688 if (other
.fCapitalizationBrkIter
!= NULL
) {
689 fCapitalizationBrkIter
= (other
.fCapitalizationBrkIter
)->clone();
693 if (fSharedNumberFormatters
!= NULL
) {
694 freeSharedNumberFormatters(fSharedNumberFormatters
);
695 fSharedNumberFormatters
= NULL
;
697 if (other
.fSharedNumberFormatters
!= NULL
) {
698 fSharedNumberFormatters
= allocSharedNumberFormatters();
699 if (fSharedNumberFormatters
) {
700 for (int32_t i
= 0; i
< UDAT_FIELD_COUNT
; ++i
) {
701 SharedObject::copyPtr(
702 other
.fSharedNumberFormatters
[i
],
703 fSharedNumberFormatters
[i
]);
711 //----------------------------------------------------------------------
714 SimpleDateFormat::clone() const
716 return new SimpleDateFormat(*this);
719 //----------------------------------------------------------------------
722 SimpleDateFormat::operator==(const Format
& other
) const
724 if (DateFormat::operator==(other
)) {
725 // The DateFormat::operator== check for fCapitalizationContext equality above
726 // is sufficient to check equality of all derived context-related data.
727 // DateFormat::operator== guarantees following cast is safe
728 SimpleDateFormat
* that
= (SimpleDateFormat
*)&other
;
729 return (fPattern
== that
->fPattern
&&
730 fSymbols
!= NULL
&& // Check for pathological object
731 that
->fSymbols
!= NULL
&& // Check for pathological object
732 *fSymbols
== *that
->fSymbols
&&
733 fHaveDefaultCentury
== that
->fHaveDefaultCentury
&&
734 fDefaultCenturyStart
== that
->fDefaultCenturyStart
&&
735 // Check fTimeZoneFormat, it can be set independently via setter
736 ((fTimeZoneFormat
== NULL
&& that
->fTimeZoneFormat
== NULL
) ||
737 (fTimeZoneFormat
!= NULL
&& that
->fTimeZoneFormat
!= NULL
&& *fTimeZoneFormat
== *that
->fTimeZoneFormat
)) &&
738 // Check override strings (these also indicate any relevant
739 // differences in fNumberFormatters, fOverrideList)
740 fDateOverride
== that
->fDateOverride
&&
741 fTimeOverride
== that
->fTimeOverride
);
746 //----------------------------------------------------------------------
748 void SimpleDateFormat::construct(EStyle timeStyle
,
750 const Locale
& locale
,
753 // called by several constructors to load pattern data from the resources
754 if (U_FAILURE(status
)) return;
756 // We will need the calendar to know what type of symbols to load.
757 initializeCalendar(NULL
, locale
, status
);
758 if (U_FAILURE(status
)) return;
760 // Load date time patterns directly from resources.
761 const char* cType
= fCalendar
? fCalendar
->getType() : NULL
;
762 LocalUResourceBundlePointer
bundle(ures_open(NULL
, locale
.getBaseName(), &status
));
763 if (U_FAILURE(status
)) return;
765 UBool cTypeIsGregorian
= TRUE
;
766 LocalUResourceBundlePointer dateTimePatterns
;
767 if (cType
!= NULL
&& uprv_strcmp(cType
, "gregorian") != 0) {
768 CharString
resourcePath("calendar/", status
);
769 resourcePath
.append(cType
, status
).append("/DateTimePatterns", status
);
770 dateTimePatterns
.adoptInstead(
771 ures_getByKeyWithFallback(bundle
.getAlias(), resourcePath
.data(),
772 (UResourceBundle
*)NULL
, &status
));
773 cTypeIsGregorian
= FALSE
;
776 // Check for "gregorian" fallback.
777 if (cTypeIsGregorian
|| status
== U_MISSING_RESOURCE_ERROR
) {
778 status
= U_ZERO_ERROR
;
779 dateTimePatterns
.adoptInstead(
780 ures_getByKeyWithFallback(bundle
.getAlias(),
781 "calendar/gregorian/DateTimePatterns",
782 (UResourceBundle
*)NULL
, &status
));
784 if (U_FAILURE(status
)) return;
786 LocalUResourceBundlePointer currentBundle
;
788 if (ures_getSize(dateTimePatterns
.getAlias()) <= kDateTime
)
790 status
= U_INVALID_FORMAT_ERROR
;
794 setLocaleIDs(ures_getLocaleByType(dateTimePatterns
.getAlias(), ULOC_VALID_LOCALE
, &status
),
795 ures_getLocaleByType(dateTimePatterns
.getAlias(), ULOC_ACTUAL_LOCALE
, &status
));
797 // create a symbols object from the locale
798 fSymbols
= DateFormatSymbols::createForLocale(locale
, status
);
799 if (U_FAILURE(status
)) return;
802 status
= U_MEMORY_ALLOCATION_ERROR
;
806 const UChar
*resStr
,*ovrStr
;
807 int32_t resStrLen
,ovrStrLen
= 0;
808 fDateOverride
.setToBogus();
809 fTimeOverride
.setToBogus();
811 // if the pattern should include both date and time information, use the date/time
812 // pattern string as a guide to tell use how to glue together the appropriate date
813 // and time pattern strings.
814 if ((timeStyle
!= kNone
) && (dateStyle
!= kNone
))
816 currentBundle
.adoptInstead(
817 ures_getByIndex(dateTimePatterns
.getAlias(), (int32_t)timeStyle
, NULL
, &status
));
818 if (U_FAILURE(status
)) {
819 status
= U_INVALID_FORMAT_ERROR
;
822 switch (ures_getType(currentBundle
.getAlias())) {
824 resStr
= ures_getString(currentBundle
.getAlias(), &resStrLen
, &status
);
828 resStr
= ures_getStringByIndex(currentBundle
.getAlias(), 0, &resStrLen
, &status
);
829 ovrStr
= ures_getStringByIndex(currentBundle
.getAlias(), 1, &ovrStrLen
, &status
);
830 fTimeOverride
.setTo(TRUE
, ovrStr
, ovrStrLen
);
834 status
= U_INVALID_FORMAT_ERROR
;
839 UnicodeString
tempus1(TRUE
, resStr
, resStrLen
);
841 currentBundle
.adoptInstead(
842 ures_getByIndex(dateTimePatterns
.getAlias(), (int32_t)dateStyle
, NULL
, &status
));
843 if (U_FAILURE(status
)) {
844 status
= U_INVALID_FORMAT_ERROR
;
847 switch (ures_getType(currentBundle
.getAlias())) {
849 resStr
= ures_getString(currentBundle
.getAlias(), &resStrLen
, &status
);
853 resStr
= ures_getStringByIndex(currentBundle
.getAlias(), 0, &resStrLen
, &status
);
854 ovrStr
= ures_getStringByIndex(currentBundle
.getAlias(), 1, &ovrStrLen
, &status
);
855 fDateOverride
.setTo(TRUE
, ovrStr
, ovrStrLen
);
859 status
= U_INVALID_FORMAT_ERROR
;
864 UnicodeString
tempus2(TRUE
, resStr
, resStrLen
);
866 int32_t glueIndex
= kDateTime
;
867 int32_t patternsSize
= ures_getSize(dateTimePatterns
.getAlias());
868 if (patternsSize
>= (kDateTimeOffset
+ kShort
+ 1)) {
869 // Get proper date time format
870 glueIndex
= (int32_t)(kDateTimeOffset
+ (dateStyle
- kDateOffset
));
873 resStr
= ures_getStringByIndex(dateTimePatterns
.getAlias(), glueIndex
, &resStrLen
, &status
);
874 SimpleFormatter(UnicodeString(TRUE
, resStr
, resStrLen
), 2, 2, status
).
875 format(tempus1
, tempus2
, fPattern
, status
);
877 // if the pattern includes just time data or just date date, load the appropriate
878 // pattern string from the resources
879 // setTo() - see DateFormatSymbols::assignArray comments
880 else if (timeStyle
!= kNone
) {
881 currentBundle
.adoptInstead(
882 ures_getByIndex(dateTimePatterns
.getAlias(), (int32_t)timeStyle
, NULL
, &status
));
883 if (U_FAILURE(status
)) {
884 status
= U_INVALID_FORMAT_ERROR
;
887 switch (ures_getType(currentBundle
.getAlias())) {
889 resStr
= ures_getString(currentBundle
.getAlias(), &resStrLen
, &status
);
893 resStr
= ures_getStringByIndex(currentBundle
.getAlias(), 0, &resStrLen
, &status
);
894 ovrStr
= ures_getStringByIndex(currentBundle
.getAlias(), 1, &ovrStrLen
, &status
);
895 fDateOverride
.setTo(TRUE
, ovrStr
, ovrStrLen
);
899 status
= U_INVALID_FORMAT_ERROR
;
903 fPattern
.setTo(TRUE
, resStr
, resStrLen
);
905 else if (dateStyle
!= kNone
) {
906 currentBundle
.adoptInstead(
907 ures_getByIndex(dateTimePatterns
.getAlias(), (int32_t)dateStyle
, NULL
, &status
));
908 if (U_FAILURE(status
)) {
909 status
= U_INVALID_FORMAT_ERROR
;
912 switch (ures_getType(currentBundle
.getAlias())) {
914 resStr
= ures_getString(currentBundle
.getAlias(), &resStrLen
, &status
);
918 resStr
= ures_getStringByIndex(currentBundle
.getAlias(), 0, &resStrLen
, &status
);
919 ovrStr
= ures_getStringByIndex(currentBundle
.getAlias(), 1, &ovrStrLen
, &status
);
920 fDateOverride
.setTo(TRUE
, ovrStr
, ovrStrLen
);
924 status
= U_INVALID_FORMAT_ERROR
;
928 fPattern
.setTo(TRUE
, resStr
, resStrLen
);
931 // and if it includes _neither_, that's an error
933 status
= U_INVALID_FORMAT_ERROR
;
935 // finally, finish initializing by creating a Calendar and a NumberFormat
936 initialize(locale
, status
);
939 //----------------------------------------------------------------------
942 SimpleDateFormat::initializeCalendar(TimeZone
* adoptZone
, const Locale
& locale
, UErrorCode
& status
)
944 if(!U_FAILURE(status
)) {
945 fCalendar
= Calendar::createInstance(adoptZone
?adoptZone
:TimeZone::createDefault(), locale
, status
);
951 SimpleDateFormat::initialize(const Locale
& locale
,
954 if (U_FAILURE(status
)) return;
956 // If the locale has @[....]numbers=hanidays we want to *delete* that (so it
957 // it is not used for every field) and then set fDateOverride to "d=hanidays"
958 // (as with std formats for zh@calendar=chinese) to use hanidays for d field.
959 static const UChar hanidaysOverride
[] = {0x64,0x3D,0x68,0x61,0x6E,0x69,0x64,0x61,0x79,0x73,0}; // "d=hanidays"
960 char numbersValue
[ULOC_KEYWORDS_CAPACITY
];
961 UErrorCode numbersStatus
= U_ZERO_ERROR
;
962 Locale
localeNoHanidays(locale
);
963 int32_t numbersLen
= localeNoHanidays
.getKeywordValue("numbers", numbersValue
, ULOC_KEYWORDS_CAPACITY
, numbersStatus
);
964 if ( U_SUCCESS(numbersStatus
) && numbersLen
> 0 ) {
965 if ( uprv_strcmp(numbersValue
, "hanidays") == 0 ) {
966 localeNoHanidays
.setKeywordValue("numbers", NULL
, numbersStatus
);
967 fDateOverride
.setTo(hanidaysOverride
,-1);
971 // We don't need to check that the row count is >= 1, since all 2d arrays have at
973 fNumberFormat
= NumberFormat::createInstance(localeNoHanidays
, status
);
974 if (fNumberFormat
!= NULL
&& U_SUCCESS(status
))
976 fixNumberFormatForDates(*fNumberFormat
);
977 //fNumberFormat->setLenient(TRUE); // Java uses a custom DateNumberFormat to format/parse
979 initNumberFormatters(locale
,status
);
982 else if (U_SUCCESS(status
))
984 status
= U_MISSING_RESOURCE_ERROR
;
990 /* Initialize the fields we use to disambiguate ambiguous years. Separate
991 * so we can call it from readObject().
993 void SimpleDateFormat::initializeDefaultCentury()
996 fHaveDefaultCentury
= fCalendar
->haveDefaultCentury();
997 if(fHaveDefaultCentury
) {
998 fDefaultCenturyStart
= fCalendar
->defaultCenturyStart();
999 fDefaultCenturyStartYear
= fCalendar
->defaultCenturyStartYear();
1001 fDefaultCenturyStart
= DBL_MIN
;
1002 fDefaultCenturyStartYear
= -1;
1008 * Initialize the boolean attributes. Separate so we can call it from all constructors.
1010 void SimpleDateFormat::initializeBooleanAttributes()
1012 UErrorCode status
= U_ZERO_ERROR
;
1014 setBooleanAttribute(UDAT_PARSE_ALLOW_WHITESPACE
, true, status
);
1015 setBooleanAttribute(UDAT_PARSE_ALLOW_NUMERIC
, true, status
);
1016 setBooleanAttribute(UDAT_PARSE_PARTIAL_LITERAL_MATCH
, true, status
);
1017 setBooleanAttribute(UDAT_PARSE_MULTIPLE_PATTERNS_FOR_MATCH
, true, status
);
1020 /* Define one-century window into which to disambiguate dates using
1021 * two-digit years. Make public in JDK 1.2.
1023 void SimpleDateFormat::parseAmbiguousDatesAsAfter(UDate startDate
, UErrorCode
& status
)
1025 if(U_FAILURE(status
)) {
1029 status
= U_ILLEGAL_ARGUMENT_ERROR
;
1033 fCalendar
->setTime(startDate
, status
);
1034 if(U_SUCCESS(status
)) {
1035 fHaveDefaultCentury
= TRUE
;
1036 fDefaultCenturyStart
= startDate
;
1037 fDefaultCenturyStartYear
= fCalendar
->get(UCAL_YEAR
, status
);
1041 //----------------------------------------------------------------------
1044 SimpleDateFormat::format(Calendar
& cal
, UnicodeString
& appendTo
, FieldPosition
& pos
) const
1046 UErrorCode status
= U_ZERO_ERROR
;
1047 FieldPositionOnlyHandler
handler(pos
);
1048 return _format(cal
, appendTo
, handler
, status
);
1051 //----------------------------------------------------------------------
1054 SimpleDateFormat::format(Calendar
& cal
, UnicodeString
& appendTo
,
1055 FieldPositionIterator
* posIter
, UErrorCode
& status
) const
1057 FieldPositionIteratorHandler
handler(posIter
, status
);
1058 return _format(cal
, appendTo
, handler
, status
);
1061 //----------------------------------------------------------------------
1064 SimpleDateFormat::_format(Calendar
& cal
, UnicodeString
& appendTo
,
1065 FieldPositionHandler
& handler
, UErrorCode
& status
) const
1067 if ( U_FAILURE(status
) ) {
1070 Calendar
* workCal
= &cal
;
1071 Calendar
* calClone
= NULL
;
1072 if (&cal
!= fCalendar
&& uprv_strcmp(cal
.getType(), fCalendar
->getType()) != 0) {
1073 // Different calendar type
1074 // We use the time and time zone from the input calendar, but
1075 // do not use the input calendar for field calculation.
1076 calClone
= fCalendar
->clone();
1077 if (calClone
!= NULL
) {
1078 UDate t
= cal
.getTime(status
);
1079 calClone
->setTime(t
, status
);
1080 calClone
->setTimeZone(cal
.getTimeZone());
1083 status
= U_MEMORY_ALLOCATION_ERROR
;
1088 UBool inQuote
= FALSE
;
1091 int32_t fieldNum
= 0;
1092 UDisplayContext capitalizationContext
= getContext(UDISPCTX_TYPE_CAPITALIZATION
, status
);
1094 // Create temporary cache of mutable number format objects. This way
1095 // subFormat won't have to clone the const NumberFormat for each field.
1096 // if several fields share the same NumberFormat, which will almost
1097 // always be the case, this is a big save.
1098 SimpleDateFormatMutableNFs mutableNFs
;
1099 // loop through the pattern string character by character
1100 for (int32_t i
= 0; i
< fPattern
.length() && U_SUCCESS(status
); ++i
) {
1101 UChar ch
= fPattern
[i
];
1103 // Use subFormat() to format a repeated pattern character
1104 // when a different pattern or non-pattern character is seen
1105 if (ch
!= prevCh
&& count
> 0) {
1106 subFormat(appendTo
, prevCh
, count
, capitalizationContext
, fieldNum
++, handler
, *workCal
, mutableNFs
, status
);
1110 // Consecutive single quotes are a single quote literal,
1111 // either outside of quotes or between quotes
1112 if ((i
+1) < fPattern
.length() && fPattern
[i
+1] == QUOTE
) {
1113 appendTo
+= (UChar
)QUOTE
;
1116 inQuote
= ! inQuote
;
1119 else if (!inQuote
&& isSyntaxChar(ch
)) {
1120 // ch is a date-time pattern character to be interpreted
1121 // by subFormat(); count the number of times it is repeated
1126 // Append quoted characters and unquoted non-pattern characters
1131 // Format the last item in the pattern, if any
1133 subFormat(appendTo
, prevCh
, count
, capitalizationContext
, fieldNum
++, handler
, *workCal
, mutableNFs
, status
);
1136 if (calClone
!= NULL
) {
1143 //----------------------------------------------------------------------
1145 /* Map calendar field into calendar field level.
1146 * the larger the level, the smaller the field unit.
1147 * For example, UCAL_ERA level is 0, UCAL_YEAR level is 10,
1148 * UCAL_MONTH level is 20.
1149 * NOTE: if new fields adds in, the table needs to update.
1152 SimpleDateFormat::fgCalendarFieldToLevel
[] =
1156 /*dDEF*/ 30, 20, 30, 30,
1157 /*ahHm*/ 40, 50, 50, 60,
1164 int32_t SimpleDateFormat::getLevelFromChar(UChar ch
) {
1165 // Map date field LETTER into calendar field level.
1166 // the larger the level, the smaller the field unit.
1167 // NOTE: if new fields adds in, the table needs to update.
1168 static const int32_t mapCharToLevel
[] = {
1169 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
1171 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
1172 // ! " # $ % & ' ( ) * + , - . /
1173 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
1174 #if UDAT_HAS_PATTERN_CHAR_FOR_TIME_SEPARATOR
1175 // 0 1 2 3 4 5 6 7 8 9 : ; < = > ?
1176 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 0, -1, -1, -1, -1, -1,
1178 // 0 1 2 3 4 5 6 7 8 9 : ; < = > ?
1179 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
1181 // @ A B C D E F G H I J K L M N O
1182 -1, 40, -1, -1, 20, 30, 30, 0, 50, -1, -1, 50, 20, 20, -1, 0,
1183 // P Q R S T U V W X Y Z [ \ ] ^ _
1184 -1, 20, -1, 80, -1, 10, 0, 30, 0, 10, 0, -1, -1, -1, -1, -1,
1185 // ` a b c d e f g h i j k l m n o
1186 -1, 40, -1, 30, 30, 30, -1, 0, 50, -1, -1, 50, 0, 60, -1, -1,
1187 // p q r s t u v w x y z { | } ~
1188 -1, 20, 10, 70, -1, 10, 0, 20, 0, 10, 0, -1, -1, -1, -1, -1
1191 return ch
< UPRV_LENGTHOF(mapCharToLevel
) ? mapCharToLevel
[ch
] : -1;
1194 UBool
SimpleDateFormat::isSyntaxChar(UChar ch
) {
1195 static const UBool mapCharToIsSyntax
[] = {
1197 FALSE
, FALSE
, FALSE
, FALSE
, FALSE
, FALSE
, FALSE
, FALSE
,
1199 FALSE
, FALSE
, FALSE
, FALSE
, FALSE
, FALSE
, FALSE
, FALSE
,
1201 FALSE
, FALSE
, FALSE
, FALSE
, FALSE
, FALSE
, FALSE
, FALSE
,
1203 FALSE
, FALSE
, FALSE
, FALSE
, FALSE
, FALSE
, FALSE
, FALSE
,
1205 FALSE
, FALSE
, FALSE
, FALSE
, FALSE
, FALSE
, FALSE
, FALSE
,
1207 FALSE
, FALSE
, FALSE
, FALSE
, FALSE
, FALSE
, FALSE
, FALSE
,
1209 FALSE
, FALSE
, FALSE
, FALSE
, FALSE
, FALSE
, FALSE
, FALSE
,
1210 #if UDAT_HAS_PATTERN_CHAR_FOR_TIME_SEPARATOR
1212 FALSE
, FALSE
, TRUE
, FALSE
, FALSE
, FALSE
, FALSE
, FALSE
,
1215 FALSE
, FALSE
, FALSE
, FALSE
, FALSE
, FALSE
, FALSE
, FALSE
,
1218 FALSE
, TRUE
, TRUE
, TRUE
, TRUE
, TRUE
, TRUE
, TRUE
,
1220 TRUE
, TRUE
, TRUE
, TRUE
, TRUE
, TRUE
, TRUE
, TRUE
,
1222 TRUE
, TRUE
, TRUE
, TRUE
, TRUE
, TRUE
, TRUE
, TRUE
,
1224 TRUE
, TRUE
, TRUE
, FALSE
, FALSE
, FALSE
, FALSE
, FALSE
,
1226 FALSE
, TRUE
, TRUE
, TRUE
, TRUE
, TRUE
, TRUE
, TRUE
,
1228 TRUE
, TRUE
, TRUE
, TRUE
, TRUE
, TRUE
, TRUE
, TRUE
,
1230 TRUE
, TRUE
, TRUE
, TRUE
, TRUE
, TRUE
, TRUE
, TRUE
,
1232 TRUE
, TRUE
, TRUE
, FALSE
, FALSE
, FALSE
, FALSE
, FALSE
1235 return ch
< UPRV_LENGTHOF(mapCharToIsSyntax
) ? mapCharToIsSyntax
[ch
] : FALSE
;
1238 // Map index into pattern character string to Calendar field number.
1239 const UCalendarDateFields
1240 SimpleDateFormat::fgPatternIndexToCalendarField
[] =
1242 /*GyM*/ UCAL_ERA
, UCAL_YEAR
, UCAL_MONTH
,
1243 /*dkH*/ UCAL_DATE
, UCAL_HOUR_OF_DAY
, UCAL_HOUR_OF_DAY
,
1244 /*msS*/ UCAL_MINUTE
, UCAL_SECOND
, UCAL_MILLISECOND
,
1245 /*EDF*/ UCAL_DAY_OF_WEEK
, UCAL_DAY_OF_YEAR
, UCAL_DAY_OF_WEEK_IN_MONTH
,
1246 /*wWa*/ UCAL_WEEK_OF_YEAR
, UCAL_WEEK_OF_MONTH
, UCAL_AM_PM
,
1247 /*hKz*/ UCAL_HOUR
, UCAL_HOUR
, UCAL_ZONE_OFFSET
,
1248 /*Yeu*/ UCAL_YEAR_WOY
, UCAL_DOW_LOCAL
, UCAL_EXTENDED_YEAR
,
1249 /*gAZ*/ UCAL_JULIAN_DAY
, UCAL_MILLISECONDS_IN_DAY
, UCAL_ZONE_OFFSET
,
1250 /*v*/ UCAL_ZONE_OFFSET
,
1251 /*c*/ UCAL_DOW_LOCAL
,
1255 /*V*/ UCAL_ZONE_OFFSET
,
1257 /*O*/ UCAL_ZONE_OFFSET
,
1258 /*Xx*/ UCAL_ZONE_OFFSET
, UCAL_ZONE_OFFSET
,
1259 /*r*/ UCAL_EXTENDED_YEAR
,
1260 /*bB*/ UCAL_FIELD_COUNT
, UCAL_FIELD_COUNT
, // no mappings to calendar fields
1261 #if UDAT_HAS_PATTERN_CHAR_FOR_TIME_SEPARATOR
1262 /*:*/ UCAL_FIELD_COUNT
, /* => no useful mapping to any calendar field */
1264 /*no pattern char for UDAT_TIME_SEPARATOR_FIELD*/ UCAL_FIELD_COUNT
, /* => no useful mapping to any calendar field */
1268 // Map index into pattern character string to DateFormat field number
1269 const UDateFormatField
1270 SimpleDateFormat::fgPatternIndexToDateFormatField
[] = {
1271 /*GyM*/ UDAT_ERA_FIELD
, UDAT_YEAR_FIELD
, UDAT_MONTH_FIELD
,
1272 /*dkH*/ UDAT_DATE_FIELD
, UDAT_HOUR_OF_DAY1_FIELD
, UDAT_HOUR_OF_DAY0_FIELD
,
1273 /*msS*/ UDAT_MINUTE_FIELD
, UDAT_SECOND_FIELD
, UDAT_FRACTIONAL_SECOND_FIELD
,
1274 /*EDF*/ UDAT_DAY_OF_WEEK_FIELD
, UDAT_DAY_OF_YEAR_FIELD
, UDAT_DAY_OF_WEEK_IN_MONTH_FIELD
,
1275 /*wWa*/ UDAT_WEEK_OF_YEAR_FIELD
, UDAT_WEEK_OF_MONTH_FIELD
, UDAT_AM_PM_FIELD
,
1276 /*hKz*/ UDAT_HOUR1_FIELD
, UDAT_HOUR0_FIELD
, UDAT_TIMEZONE_FIELD
,
1277 /*Yeu*/ UDAT_YEAR_WOY_FIELD
, UDAT_DOW_LOCAL_FIELD
, UDAT_EXTENDED_YEAR_FIELD
,
1278 /*gAZ*/ UDAT_JULIAN_DAY_FIELD
, UDAT_MILLISECONDS_IN_DAY_FIELD
, UDAT_TIMEZONE_RFC_FIELD
,
1279 /*v*/ UDAT_TIMEZONE_GENERIC_FIELD
,
1280 /*c*/ UDAT_STANDALONE_DAY_FIELD
,
1281 /*L*/ UDAT_STANDALONE_MONTH_FIELD
,
1282 /*Q*/ UDAT_QUARTER_FIELD
,
1283 /*q*/ UDAT_STANDALONE_QUARTER_FIELD
,
1284 /*V*/ UDAT_TIMEZONE_SPECIAL_FIELD
,
1285 /*U*/ UDAT_YEAR_NAME_FIELD
,
1286 /*O*/ UDAT_TIMEZONE_LOCALIZED_GMT_OFFSET_FIELD
,
1287 /*Xx*/ UDAT_TIMEZONE_ISO_FIELD
, UDAT_TIMEZONE_ISO_LOCAL_FIELD
,
1288 /*r*/ UDAT_RELATED_YEAR_FIELD
,
1289 /*bB*/ UDAT_AM_PM_MIDNIGHT_NOON_FIELD
, UDAT_FLEXIBLE_DAY_PERIOD_FIELD
,
1290 #if UDAT_HAS_PATTERN_CHAR_FOR_TIME_SEPARATOR
1291 /*:*/ UDAT_TIME_SEPARATOR_FIELD
,
1293 /*no pattern char for UDAT_TIME_SEPARATOR_FIELD*/ UDAT_TIME_SEPARATOR_FIELD
,
1297 //----------------------------------------------------------------------
1300 * Append symbols[value] to dst. Make sure the array index is not out
1304 _appendSymbol(UnicodeString
& dst
,
1306 const UnicodeString
* symbols
,
1307 int32_t symbolsCount
) {
1308 U_ASSERT(0 <= value
&& value
< symbolsCount
);
1309 if (0 <= value
&& value
< symbolsCount
) {
1310 dst
+= symbols
[value
];
1315 _appendSymbolWithMonthPattern(UnicodeString
& dst
, int32_t value
, const UnicodeString
* symbols
, int32_t symbolsCount
,
1316 const UnicodeString
* monthPattern
, UErrorCode
& status
) {
1317 U_ASSERT(0 <= value
&& value
< symbolsCount
);
1318 if (0 <= value
&& value
< symbolsCount
) {
1319 if (monthPattern
== NULL
) {
1320 dst
+= symbols
[value
];
1322 SimpleFormatter(*monthPattern
, 1, 1, status
).format(symbols
[value
], dst
, status
);
1327 //----------------------------------------------------------------------
1329 SimpleDateFormat::initNumberFormatters(const Locale
&locale
,UErrorCode
&status
) {
1330 if (U_FAILURE(status
)) {
1333 if ( fDateOverride
.isBogus() && fTimeOverride
.isBogus() ) {
1337 if (fSharedNumberFormatters
== NULL
) {
1338 fSharedNumberFormatters
= allocSharedNumberFormatters();
1339 if (fSharedNumberFormatters
== NULL
) {
1340 status
= U_MEMORY_ALLOCATION_ERROR
;
1345 if (U_FAILURE(status
)) {
1349 processOverrideString(locale
,fDateOverride
,kOvrStrDate
,status
);
1350 processOverrideString(locale
,fTimeOverride
,kOvrStrTime
,status
);
1354 SimpleDateFormat::processOverrideString(const Locale
&locale
, const UnicodeString
&str
, int8_t type
, UErrorCode
&status
) {
1355 if (str
.isBogus() || U_FAILURE(status
)) {
1361 UnicodeString nsName
;
1362 UnicodeString ovrField
;
1363 UBool moreToProcess
= TRUE
;
1364 NSOverride
*overrideList
= NULL
;
1366 while (moreToProcess
) {
1367 int32_t delimiterPosition
= str
.indexOf((UChar
)ULOC_KEYWORD_ITEM_SEPARATOR_UNICODE
,start
);
1368 if (delimiterPosition
== -1) {
1369 moreToProcess
= FALSE
;
1370 len
= str
.length() - start
;
1372 len
= delimiterPosition
- start
;
1374 UnicodeString
currentString(str
,start
,len
);
1375 int32_t equalSignPosition
= currentString
.indexOf((UChar
)ULOC_KEYWORD_ASSIGN_UNICODE
,0);
1376 if (equalSignPosition
== -1) { // Simple override string such as "hebrew"
1377 nsName
.setTo(currentString
);
1378 ovrField
.setToBogus();
1379 } else { // Field specific override string such as "y=hebrew"
1380 nsName
.setTo(currentString
,equalSignPosition
+1);
1381 ovrField
.setTo(currentString
,0,1); // We just need the first character.
1384 int32_t nsNameHash
= nsName
.hashCode();
1385 // See if the numbering system is in the override list, if not, then add it.
1386 NSOverride
*cur
= overrideList
;
1387 const SharedNumberFormat
*snf
= NULL
;
1388 UBool found
= FALSE
;
1389 while ( cur
&& !found
) {
1390 if ( cur
->hash
== nsNameHash
) {
1398 LocalPointer
<NSOverride
> cur(new NSOverride
);
1399 if (!cur
.isNull()) {
1400 char kw
[ULOC_KEYWORD_AND_VALUES_CAPACITY
];
1401 uprv_strcpy(kw
,"numbers=");
1402 nsName
.extract(0,len
,kw
+8,ULOC_KEYWORD_AND_VALUES_CAPACITY
-8,US_INV
);
1404 Locale
ovrLoc(locale
.getLanguage(),locale
.getCountry(),locale
.getVariant(),kw
);
1405 cur
->hash
= nsNameHash
;
1406 cur
->next
= overrideList
;
1407 SharedObject::copyPtr(
1408 createSharedNumberFormat(ovrLoc
, status
), cur
->snf
);
1409 if (U_FAILURE(status
)) {
1411 overrideList
->free();
1416 overrideList
= cur
.orphan();
1418 status
= U_MEMORY_ALLOCATION_ERROR
;
1420 overrideList
->free();
1426 // Now that we have an appropriate number formatter, fill in the appropriate spaces in the
1427 // number formatters table.
1428 if (ovrField
.isBogus()) {
1432 for ( int8_t i
=0 ; i
<kDateFieldsCount
; i
++ ) {
1433 SharedObject::copyPtr(snf
, fSharedNumberFormatters
[kDateFields
[i
]]);
1435 if (type
==kOvrStrDate
) {
1440 case kOvrStrTime
: {
1441 for ( int8_t i
=0 ; i
<kTimeFieldsCount
; i
++ ) {
1442 SharedObject::copyPtr(snf
, fSharedNumberFormatters
[kTimeFields
[i
]]);
1448 // if the pattern character is unrecognized, signal an error and bail out
1449 UDateFormatField patternCharIndex
=
1450 DateFormatSymbols::getPatternCharIndex(ovrField
.charAt(0));
1451 if (patternCharIndex
== UDAT_FIELD_COUNT
) {
1452 status
= U_INVALID_FORMAT_ERROR
;
1454 overrideList
->free();
1458 SharedObject::copyPtr(snf
, fSharedNumberFormatters
[patternCharIndex
]);
1461 start
= delimiterPosition
+ 1;
1464 overrideList
->free();
1468 //---------------------------------------------------------------------
1470 SimpleDateFormat::subFormat(UnicodeString
&appendTo
,
1473 UDisplayContext capitalizationContext
,
1475 FieldPositionHandler
& handler
,
1477 SimpleDateFormatMutableNFs
&mutableNFs
,
1478 UErrorCode
& status
) const
1480 if (U_FAILURE(status
)) {
1484 // this function gets called by format() to produce the appropriate substitution
1485 // text for an individual pattern symbol (e.g., "HH" or "yyyy")
1487 UDateFormatField patternCharIndex
= DateFormatSymbols::getPatternCharIndex(ch
);
1488 const int32_t maxIntCount
= 10;
1489 int32_t beginOffset
= appendTo
.length();
1490 NumberFormat
*currentNumberFormat
;
1491 DateFormatSymbols::ECapitalizationContextUsageType capContextUsageType
= DateFormatSymbols::kCapContextUsageOther
;
1493 UBool isHebrewCalendar
= (uprv_strcmp(cal
.getType(),"hebrew") == 0);
1494 UBool isChineseCalendar
= (uprv_strcmp(cal
.getType(),"chinese") == 0 || uprv_strcmp(cal
.getType(),"dangi") == 0);
1496 // if the pattern character is unrecognized, signal an error and dump out
1497 if (patternCharIndex
== UDAT_FIELD_COUNT
)
1499 if (ch
!= 0x6C) { // pattern char 'l' (SMALL LETTER L) just gets ignored
1500 status
= U_INVALID_FORMAT_ERROR
;
1505 UCalendarDateFields field
= fgPatternIndexToCalendarField
[patternCharIndex
];
1507 // Don't get value unless it is useful
1508 if (field
< UCAL_FIELD_COUNT
) {
1509 value
= (patternCharIndex
!= UDAT_RELATED_YEAR_FIELD
)? cal
.get(field
, status
): cal
.getRelatedYear(status
);
1511 if (U_FAILURE(status
)) {
1515 currentNumberFormat
= mutableNFs
.get(getNumberFormatByIndex(patternCharIndex
));
1516 if (currentNumberFormat
== NULL
) {
1517 status
= U_MEMORY_ALLOCATION_ERROR
;
1520 UnicodeString
hebr("hebr", 4, US_INV
);
1522 switch (patternCharIndex
) {
1524 // for any "G" symbol, write out the appropriate era string
1525 // "GGGG" is wide era name, "GGGGG" is narrow era name, anything else is abbreviated name
1526 case UDAT_ERA_FIELD
:
1527 if (isChineseCalendar
) {
1528 zeroPaddingNumber(currentNumberFormat
,appendTo
, value
, 1, 9); // as in ICU4J
1531 _appendSymbol(appendTo
, value
, fSymbols
->fNarrowEras
, fSymbols
->fNarrowErasCount
);
1532 capContextUsageType
= DateFormatSymbols::kCapContextUsageEraNarrow
;
1533 } else if (count
== 4) {
1534 _appendSymbol(appendTo
, value
, fSymbols
->fEraNames
, fSymbols
->fEraNamesCount
);
1535 capContextUsageType
= DateFormatSymbols::kCapContextUsageEraWide
;
1537 _appendSymbol(appendTo
, value
, fSymbols
->fEras
, fSymbols
->fErasCount
);
1538 capContextUsageType
= DateFormatSymbols::kCapContextUsageEraAbbrev
;
1543 case UDAT_YEAR_NAME_FIELD
:
1544 if (fSymbols
->fShortYearNames
!= NULL
&& value
<= fSymbols
->fShortYearNamesCount
) {
1545 // the Calendar YEAR field runs 1 through 60 for cyclic years
1546 _appendSymbol(appendTo
, value
- 1, fSymbols
->fShortYearNames
, fSymbols
->fShortYearNamesCount
);
1549 // else fall through to numeric year handling, do not break here
1552 // OLD: for "yyyy", write out the whole year; for "yy", write out the last 2 digits
1554 //Year y yy yyy yyyy yyyyy
1555 //AD 1 1 01 001 0001 00001
1556 //AD 12 12 12 012 0012 00012
1557 //AD 123 123 23 123 0123 00123
1558 //AD 1234 1234 34 1234 1234 01234
1559 //AD 12345 12345 45 12345 12345 12345
1560 case UDAT_YEAR_FIELD
:
1561 case UDAT_YEAR_WOY_FIELD
:
1562 if (fDateOverride
.compare(hebr
)==0 && value
>HEBREW_CAL_CUR_MILLENIUM_START_YEAR
&& value
<HEBREW_CAL_CUR_MILLENIUM_END_YEAR
) {
1563 value
-=HEBREW_CAL_CUR_MILLENIUM_START_YEAR
;
1566 zeroPaddingNumber(currentNumberFormat
, appendTo
, value
, 2, 2);
1568 zeroPaddingNumber(currentNumberFormat
, appendTo
, value
, count
, maxIntCount
);
1571 // for "MMMM"/"LLLL", write out the whole month name, for "MMM"/"LLL", write out the month
1572 // abbreviation, for "M"/"L" or "MM"/"LL", write out the month as a number with the
1573 // appropriate number of digits
1574 // for "MMMMM"/"LLLLL", use the narrow form
1575 case UDAT_MONTH_FIELD
:
1576 case UDAT_STANDALONE_MONTH_FIELD
:
1577 if ( isHebrewCalendar
) {
1578 HebrewCalendar
*hc
= (HebrewCalendar
*)&cal
;
1579 if (hc
->isLeapYear(hc
->get(UCAL_YEAR
,status
)) && value
== 6 && count
>= 3 )
1580 value
= 13; // Show alternate form for Adar II in leap years in Hebrew calendar.
1581 if (!hc
->isLeapYear(hc
->get(UCAL_YEAR
,status
)) && value
>= 6 && count
< 3 )
1582 value
--; // Adjust the month number down 1 in Hebrew non-leap years, i.e. Adar is 6, not 7.
1585 int32_t isLeapMonth
= (fSymbols
->fLeapMonthPatterns
!= NULL
&& fSymbols
->fLeapMonthPatternsCount
>= DateFormatSymbols::kMonthPatternsCount
)?
1586 cal
.get(UCAL_IS_LEAP_MONTH
, status
): 0;
1587 // should consolidate the next section by using arrays of pointers & counts for the right symbols...
1589 if (patternCharIndex
== UDAT_MONTH_FIELD
) {
1590 _appendSymbolWithMonthPattern(appendTo
, value
, fSymbols
->fNarrowMonths
, fSymbols
->fNarrowMonthsCount
,
1591 (isLeapMonth
!=0)? &(fSymbols
->fLeapMonthPatterns
[DateFormatSymbols::kLeapMonthPatternFormatNarrow
]): NULL
, status
);
1593 _appendSymbolWithMonthPattern(appendTo
, value
, fSymbols
->fStandaloneNarrowMonths
, fSymbols
->fStandaloneNarrowMonthsCount
,
1594 (isLeapMonth
!=0)? &(fSymbols
->fLeapMonthPatterns
[DateFormatSymbols::kLeapMonthPatternStandaloneNarrow
]): NULL
, status
);
1596 capContextUsageType
= DateFormatSymbols::kCapContextUsageMonthNarrow
;
1597 } else if (count
== 4) {
1598 if (patternCharIndex
== UDAT_MONTH_FIELD
) {
1599 _appendSymbolWithMonthPattern(appendTo
, value
, fSymbols
->fMonths
, fSymbols
->fMonthsCount
,
1600 (isLeapMonth
!=0)? &(fSymbols
->fLeapMonthPatterns
[DateFormatSymbols::kLeapMonthPatternFormatWide
]): NULL
, status
);
1601 capContextUsageType
= DateFormatSymbols::kCapContextUsageMonthFormat
;
1603 _appendSymbolWithMonthPattern(appendTo
, value
, fSymbols
->fStandaloneMonths
, fSymbols
->fStandaloneMonthsCount
,
1604 (isLeapMonth
!=0)? &(fSymbols
->fLeapMonthPatterns
[DateFormatSymbols::kLeapMonthPatternStandaloneWide
]): NULL
, status
);
1605 capContextUsageType
= DateFormatSymbols::kCapContextUsageMonthStandalone
;
1607 } else if (count
== 3) {
1608 if (patternCharIndex
== UDAT_MONTH_FIELD
) {
1609 _appendSymbolWithMonthPattern(appendTo
, value
, fSymbols
->fShortMonths
, fSymbols
->fShortMonthsCount
,
1610 (isLeapMonth
!=0)? &(fSymbols
->fLeapMonthPatterns
[DateFormatSymbols::kLeapMonthPatternFormatAbbrev
]): NULL
, status
);
1611 capContextUsageType
= DateFormatSymbols::kCapContextUsageMonthFormat
;
1613 _appendSymbolWithMonthPattern(appendTo
, value
, fSymbols
->fStandaloneShortMonths
, fSymbols
->fStandaloneShortMonthsCount
,
1614 (isLeapMonth
!=0)? &(fSymbols
->fLeapMonthPatterns
[DateFormatSymbols::kLeapMonthPatternStandaloneAbbrev
]): NULL
, status
);
1615 capContextUsageType
= DateFormatSymbols::kCapContextUsageMonthStandalone
;
1618 UnicodeString monthNumber
;
1619 zeroPaddingNumber(currentNumberFormat
,monthNumber
, value
+ 1, count
, maxIntCount
);
1620 _appendSymbolWithMonthPattern(appendTo
, 0, &monthNumber
, 1,
1621 (isLeapMonth
!=0)? &(fSymbols
->fLeapMonthPatterns
[DateFormatSymbols::kLeapMonthPatternNumeric
]): NULL
, status
);
1626 // for "k" and "kk", write out the hour, adjusting midnight to appear as "24"
1627 case UDAT_HOUR_OF_DAY1_FIELD
:
1629 zeroPaddingNumber(currentNumberFormat
,appendTo
, cal
.getMaximum(UCAL_HOUR_OF_DAY
) + 1, count
, maxIntCount
);
1631 zeroPaddingNumber(currentNumberFormat
,appendTo
, value
, count
, maxIntCount
);
1634 case UDAT_FRACTIONAL_SECOND_FIELD
:
1635 // Fractional seconds left-justify
1637 currentNumberFormat
->setMinimumIntegerDigits((count
> 3) ? 3 : count
);
1638 currentNumberFormat
->setMaximumIntegerDigits(maxIntCount
);
1641 } else if (count
== 2) {
1644 FieldPosition
p(FieldPosition::DONT_CARE
);
1645 currentNumberFormat
->format(value
, appendTo
, p
);
1647 currentNumberFormat
->setMinimumIntegerDigits(count
- 3);
1648 currentNumberFormat
->format((int32_t)0, appendTo
, p
);
1653 // for "ee" or "e", use local numeric day-of-the-week
1654 // for "EEEEEE" or "eeeeee", write out the short day-of-the-week name
1655 // for "EEEEE" or "eeeee", write out the narrow day-of-the-week name
1656 // for "EEEE" or "eeee", write out the wide day-of-the-week name
1657 // for "EEE" or "EE" or "E" or "eee", write out the abbreviated day-of-the-week name
1658 case UDAT_DOW_LOCAL_FIELD
:
1660 zeroPaddingNumber(currentNumberFormat
,appendTo
, value
, count
, maxIntCount
);
1663 // fall through to EEEEE-EEE handling, but for that we don't want local day-of-week,
1664 // we want standard day-of-week, so first fix value to work for EEEEE-EEE.
1665 value
= cal
.get(UCAL_DAY_OF_WEEK
, status
);
1666 if (U_FAILURE(status
)) {
1669 // fall through, do not break here
1671 case UDAT_DAY_OF_WEEK_FIELD
:
1673 _appendSymbol(appendTo
, value
, fSymbols
->fNarrowWeekdays
,
1674 fSymbols
->fNarrowWeekdaysCount
);
1675 capContextUsageType
= DateFormatSymbols::kCapContextUsageDayNarrow
;
1676 } else if (count
== 4) {
1677 _appendSymbol(appendTo
, value
, fSymbols
->fWeekdays
,
1678 fSymbols
->fWeekdaysCount
);
1679 capContextUsageType
= DateFormatSymbols::kCapContextUsageDayFormat
;
1680 } else if (count
== 6) {
1681 _appendSymbol(appendTo
, value
, fSymbols
->fShorterWeekdays
,
1682 fSymbols
->fShorterWeekdaysCount
);
1683 capContextUsageType
= DateFormatSymbols::kCapContextUsageDayFormat
;
1685 _appendSymbol(appendTo
, value
, fSymbols
->fShortWeekdays
,
1686 fSymbols
->fShortWeekdaysCount
);
1687 capContextUsageType
= DateFormatSymbols::kCapContextUsageDayFormat
;
1691 // for "ccc", write out the abbreviated day-of-the-week name
1692 // for "cccc", write out the wide day-of-the-week name
1693 // for "ccccc", use the narrow day-of-the-week name
1694 // for "ccccc", use the short day-of-the-week name
1695 case UDAT_STANDALONE_DAY_FIELD
:
1697 zeroPaddingNumber(currentNumberFormat
,appendTo
, value
, 1, maxIntCount
);
1700 // fall through to alpha DOW handling, but for that we don't want local day-of-week,
1701 // we want standard day-of-week, so first fix value.
1702 value
= cal
.get(UCAL_DAY_OF_WEEK
, status
);
1703 if (U_FAILURE(status
)) {
1707 _appendSymbol(appendTo
, value
, fSymbols
->fStandaloneNarrowWeekdays
,
1708 fSymbols
->fStandaloneNarrowWeekdaysCount
);
1709 capContextUsageType
= DateFormatSymbols::kCapContextUsageDayNarrow
;
1710 } else if (count
== 4) {
1711 _appendSymbol(appendTo
, value
, fSymbols
->fStandaloneWeekdays
,
1712 fSymbols
->fStandaloneWeekdaysCount
);
1713 capContextUsageType
= DateFormatSymbols::kCapContextUsageDayStandalone
;
1714 } else if (count
== 6) {
1715 _appendSymbol(appendTo
, value
, fSymbols
->fStandaloneShorterWeekdays
,
1716 fSymbols
->fStandaloneShorterWeekdaysCount
);
1717 capContextUsageType
= DateFormatSymbols::kCapContextUsageDayStandalone
;
1718 } else { // count == 3
1719 _appendSymbol(appendTo
, value
, fSymbols
->fStandaloneShortWeekdays
,
1720 fSymbols
->fStandaloneShortWeekdaysCount
);
1721 capContextUsageType
= DateFormatSymbols::kCapContextUsageDayStandalone
;
1725 // for "a" symbol, write out the whole AM/PM string
1726 case UDAT_AM_PM_FIELD
:
1728 _appendSymbol(appendTo
, value
, fSymbols
->fAmPms
,
1729 fSymbols
->fAmPmsCount
);
1731 _appendSymbol(appendTo
, value
, fSymbols
->fNarrowAmPms
,
1732 fSymbols
->fNarrowAmPmsCount
);
1736 // if we see pattern character for UDAT_TIME_SEPARATOR_FIELD (none currently defined),
1737 // write out the time separator string. Leave support in for future definition.
1738 case UDAT_TIME_SEPARATOR_FIELD
:
1740 UnicodeString separator
;
1741 appendTo
+= fSymbols
->getTimeSeparatorString(separator
);
1745 // for "h" and "hh", write out the hour, adjusting noon and midnight to show up
1747 case UDAT_HOUR1_FIELD
:
1749 zeroPaddingNumber(currentNumberFormat
,appendTo
, cal
.getLeastMaximum(UCAL_HOUR
) + 1, count
, maxIntCount
);
1751 zeroPaddingNumber(currentNumberFormat
,appendTo
, value
, count
, maxIntCount
);
1754 case UDAT_TIMEZONE_FIELD
: // 'z'
1755 case UDAT_TIMEZONE_RFC_FIELD
: // 'Z'
1756 case UDAT_TIMEZONE_GENERIC_FIELD
: // 'v'
1757 case UDAT_TIMEZONE_SPECIAL_FIELD
: // 'V'
1758 case UDAT_TIMEZONE_LOCALIZED_GMT_OFFSET_FIELD
: // 'O'
1759 case UDAT_TIMEZONE_ISO_FIELD
: // 'X'
1760 case UDAT_TIMEZONE_ISO_LOCAL_FIELD
: // 'x'
1762 UChar zsbuf
[ZONE_NAME_U16_MAX
];
1763 UnicodeString
zoneString(zsbuf
, 0, UPRV_LENGTHOF(zsbuf
));
1764 const TimeZone
& tz
= cal
.getTimeZone();
1765 UDate date
= cal
.getTime(status
);
1766 if (U_SUCCESS(status
)) {
1767 if (patternCharIndex
== UDAT_TIMEZONE_FIELD
) {
1770 tzFormat()->format(UTZFMT_STYLE_SPECIFIC_SHORT
, tz
, date
, zoneString
);
1771 capContextUsageType
= DateFormatSymbols::kCapContextUsageMetazoneShort
;
1774 tzFormat()->format(UTZFMT_STYLE_SPECIFIC_LONG
, tz
, date
, zoneString
);
1775 capContextUsageType
= DateFormatSymbols::kCapContextUsageMetazoneLong
;
1778 else if (patternCharIndex
== UDAT_TIMEZONE_RFC_FIELD
) {
1781 tzFormat()->format(UTZFMT_STYLE_ISO_BASIC_LOCAL_FULL
, tz
, date
, zoneString
);
1782 } else if (count
== 5) {
1784 tzFormat()->format(UTZFMT_STYLE_ISO_EXTENDED_FULL
, tz
, date
, zoneString
);
1786 // "ZZ", "ZZZ", "ZZZZ"
1787 tzFormat()->format(UTZFMT_STYLE_LOCALIZED_GMT
, tz
, date
, zoneString
);
1790 else if (patternCharIndex
== UDAT_TIMEZONE_GENERIC_FIELD
) {
1793 tzFormat()->format(UTZFMT_STYLE_GENERIC_SHORT
, tz
, date
, zoneString
);
1794 capContextUsageType
= DateFormatSymbols::kCapContextUsageMetazoneShort
;
1795 } else if (count
== 4) {
1797 tzFormat()->format(UTZFMT_STYLE_GENERIC_LONG
, tz
, date
, zoneString
);
1798 capContextUsageType
= DateFormatSymbols::kCapContextUsageMetazoneLong
;
1801 else if (patternCharIndex
== UDAT_TIMEZONE_SPECIAL_FIELD
) {
1804 tzFormat()->format(UTZFMT_STYLE_ZONE_ID_SHORT
, tz
, date
, zoneString
);
1805 } else if (count
== 2) {
1807 tzFormat()->format(UTZFMT_STYLE_ZONE_ID
, tz
, date
, zoneString
);
1808 } else if (count
== 3) {
1810 tzFormat()->format(UTZFMT_STYLE_EXEMPLAR_LOCATION
, tz
, date
, zoneString
);
1811 } else if (count
== 4) {
1813 tzFormat()->format(UTZFMT_STYLE_GENERIC_LOCATION
, tz
, date
, zoneString
);
1814 capContextUsageType
= DateFormatSymbols::kCapContextUsageZoneLong
;
1817 else if (patternCharIndex
== UDAT_TIMEZONE_LOCALIZED_GMT_OFFSET_FIELD
) {
1820 tzFormat()->format(UTZFMT_STYLE_LOCALIZED_GMT_SHORT
, tz
, date
, zoneString
);
1821 } else if (count
== 4) {
1823 tzFormat()->format(UTZFMT_STYLE_LOCALIZED_GMT
, tz
, date
, zoneString
);
1826 else if (patternCharIndex
== UDAT_TIMEZONE_ISO_FIELD
) {
1829 tzFormat()->format(UTZFMT_STYLE_ISO_BASIC_SHORT
, tz
, date
, zoneString
);
1830 } else if (count
== 2) {
1832 tzFormat()->format(UTZFMT_STYLE_ISO_BASIC_FIXED
, tz
, date
, zoneString
);
1833 } else if (count
== 3) {
1835 tzFormat()->format(UTZFMT_STYLE_ISO_EXTENDED_FIXED
, tz
, date
, zoneString
);
1836 } else if (count
== 4) {
1838 tzFormat()->format(UTZFMT_STYLE_ISO_BASIC_FULL
, tz
, date
, zoneString
);
1839 } else if (count
== 5) {
1841 tzFormat()->format(UTZFMT_STYLE_ISO_EXTENDED_FULL
, tz
, date
, zoneString
);
1844 else if (patternCharIndex
== UDAT_TIMEZONE_ISO_LOCAL_FIELD
) {
1847 tzFormat()->format(UTZFMT_STYLE_ISO_BASIC_LOCAL_SHORT
, tz
, date
, zoneString
);
1848 } else if (count
== 2) {
1850 tzFormat()->format(UTZFMT_STYLE_ISO_BASIC_LOCAL_FIXED
, tz
, date
, zoneString
);
1851 } else if (count
== 3) {
1853 tzFormat()->format(UTZFMT_STYLE_ISO_EXTENDED_LOCAL_FIXED
, tz
, date
, zoneString
);
1854 } else if (count
== 4) {
1856 tzFormat()->format(UTZFMT_STYLE_ISO_BASIC_LOCAL_FULL
, tz
, date
, zoneString
);
1857 } else if (count
== 5) {
1859 tzFormat()->format(UTZFMT_STYLE_ISO_EXTENDED_LOCAL_FULL
, tz
, date
, zoneString
);
1866 appendTo
+= zoneString
;
1870 case UDAT_QUARTER_FIELD
:
1872 _appendSymbol(appendTo
, value
/3, fSymbols
->fQuarters
,
1873 fSymbols
->fQuartersCount
);
1874 else if (count
== 3)
1875 _appendSymbol(appendTo
, value
/3, fSymbols
->fShortQuarters
,
1876 fSymbols
->fShortQuartersCount
);
1878 zeroPaddingNumber(currentNumberFormat
,appendTo
, (value
/3) + 1, count
, maxIntCount
);
1881 case UDAT_STANDALONE_QUARTER_FIELD
:
1883 _appendSymbol(appendTo
, value
/3, fSymbols
->fStandaloneQuarters
,
1884 fSymbols
->fStandaloneQuartersCount
);
1885 else if (count
== 3)
1886 _appendSymbol(appendTo
, value
/3, fSymbols
->fStandaloneShortQuarters
,
1887 fSymbols
->fStandaloneShortQuartersCount
);
1889 zeroPaddingNumber(currentNumberFormat
,appendTo
, (value
/3) + 1, count
, maxIntCount
);
1892 case UDAT_AM_PM_MIDNIGHT_NOON_FIELD
:
1894 const UnicodeString
*toAppend
= NULL
;
1895 int32_t hour
= cal
.get(UCAL_HOUR_OF_DAY
, status
);
1897 // Note: "midnight" can be ambiguous as to whether it refers to beginning of day or end of day.
1898 // For ICU 57 output of "midnight" is temporarily suppressed.
1900 // For "midnight" and "noon":
1901 // Time, as displayed, must be exactly noon or midnight.
1902 // This means minutes and seconds, if present, must be zero.
1903 if ((/*hour == 0 ||*/ hour
== 12) &&
1904 (!fHasMinute
|| cal
.get(UCAL_MINUTE
, status
) == 0) &&
1905 (!fHasSecond
|| cal
.get(UCAL_SECOND
, status
) == 0)) {
1906 // Stealing am/pm value to use as our array index.
1907 // It works out: am/midnight are both 0, pm/noon are both 1,
1908 // 12 am is 12 midnight, and 12 pm is 12 noon.
1909 int32_t value
= cal
.get(UCAL_AM_PM
, status
);
1912 toAppend
= &fSymbols
->fAbbreviatedDayPeriods
[value
];
1913 } else if (count
== 4 || count
> 5) {
1914 toAppend
= &fSymbols
->fWideDayPeriods
[value
];
1915 } else { // count == 5
1916 toAppend
= &fSymbols
->fNarrowDayPeriods
[value
];
1920 // toAppend is NULL if time isn't exactly midnight or noon (as displayed).
1921 // toAppend is bogus if time is midnight or noon, but no localized string exists.
1922 // In either case, fall back to am/pm.
1923 if (toAppend
== NULL
|| toAppend
->isBogus()) {
1924 // Reformat with identical arguments except ch, now changed to 'a'.
1925 subFormat(appendTo
, 0x61, count
, capitalizationContext
, fieldNum
,
1926 handler
, cal
, mutableNFs
, status
);
1928 appendTo
+= *toAppend
;
1934 case UDAT_FLEXIBLE_DAY_PERIOD_FIELD
:
1936 // TODO: Maybe fetch the DayperiodRules during initialization (instead of at the first
1937 // loading of an instance) if a relevant pattern character (b or B) is used.
1938 const DayPeriodRules
*ruleSet
= DayPeriodRules::getInstance(this->getSmpFmtLocale(), status
);
1939 if (U_FAILURE(status
)) {
1940 // Data doesn't conform to spec, therefore loading failed.
1943 if (ruleSet
== NULL
) {
1944 // Data doesn't exist for the locale we're looking for.
1945 // Falling back to am/pm.
1946 subFormat(appendTo
, 0x61, count
, capitalizationContext
, fieldNum
,
1947 handler
, cal
, mutableNFs
, status
);
1951 // Get current display time.
1952 int32_t hour
= cal
.get(UCAL_HOUR_OF_DAY
, status
);
1955 minute
= cal
.get(UCAL_MINUTE
, status
);
1959 second
= cal
.get(UCAL_SECOND
, status
);
1962 // Determine day period.
1963 DayPeriodRules::DayPeriod periodType
;
1964 if (hour
== 0 && minute
== 0 && second
== 0 && ruleSet
->hasMidnight()) {
1965 periodType
= DayPeriodRules::DAYPERIOD_MIDNIGHT
;
1966 } else if (hour
== 12 && minute
== 0 && second
== 0 && ruleSet
->hasNoon()) {
1967 periodType
= DayPeriodRules::DAYPERIOD_NOON
;
1969 periodType
= ruleSet
->getDayPeriodForHour(hour
);
1972 // Rule set exists, therefore periodType can't be UNKNOWN.
1973 // Get localized string.
1974 U_ASSERT(periodType
!= DayPeriodRules::DAYPERIOD_UNKNOWN
);
1975 UnicodeString
*toAppend
= NULL
;
1978 // Note: "midnight" can be ambiguous as to whether it refers to beginning of day or end of day.
1979 // For ICU 57 output of "midnight" is temporarily suppressed.
1981 if (periodType
!= DayPeriodRules::DAYPERIOD_AM
&&
1982 periodType
!= DayPeriodRules::DAYPERIOD_PM
&&
1983 periodType
!= DayPeriodRules::DAYPERIOD_MIDNIGHT
) {
1984 index
= (int32_t)periodType
;
1986 toAppend
= &fSymbols
->fAbbreviatedDayPeriods
[index
]; // i.e. short
1987 } else if (count
== 4 || count
> 5) {
1988 toAppend
= &fSymbols
->fWideDayPeriods
[index
];
1989 } else { // count == 5
1990 toAppend
= &fSymbols
->fNarrowDayPeriods
[index
];
1994 // Fallback schedule:
1995 // Midnight/Noon -> General Periods -> AM/PM.
1997 // Midnight/Noon -> General Periods.
1998 if ((toAppend
== NULL
|| toAppend
->isBogus()) &&
1999 (periodType
== DayPeriodRules::DAYPERIOD_MIDNIGHT
||
2000 periodType
== DayPeriodRules::DAYPERIOD_NOON
)) {
2001 periodType
= ruleSet
->getDayPeriodForHour(hour
);
2002 index
= (int32_t)periodType
;
2005 toAppend
= &fSymbols
->fAbbreviatedDayPeriods
[index
]; // i.e. short
2006 } else if (count
== 4 || count
> 5) {
2007 toAppend
= &fSymbols
->fWideDayPeriods
[index
];
2008 } else { // count == 5
2009 toAppend
= &fSymbols
->fNarrowDayPeriods
[index
];
2013 // General Periods -> AM/PM.
2014 if (periodType
== DayPeriodRules::DAYPERIOD_AM
||
2015 periodType
== DayPeriodRules::DAYPERIOD_PM
||
2016 toAppend
->isBogus()) {
2017 subFormat(appendTo
, 0x61, count
, capitalizationContext
, fieldNum
,
2018 handler
, cal
, mutableNFs
, status
);
2021 appendTo
+= *toAppend
;
2027 // all of the other pattern symbols can be formatted as simple numbers with
2028 // appropriate zero padding
2030 zeroPaddingNumber(currentNumberFormat
,appendTo
, value
, count
, maxIntCount
);
2033 #if !UCONFIG_NO_BREAK_ITERATION
2034 // if first field, check to see whether we need to and are able to titlecase it
2035 if (fieldNum
== 0 && u_islower(appendTo
.char32At(beginOffset
)) && fCapitalizationBrkIter
!= NULL
) {
2036 UBool titlecase
= FALSE
;
2037 switch (capitalizationContext
) {
2038 case UDISPCTX_CAPITALIZATION_FOR_BEGINNING_OF_SENTENCE
:
2041 case UDISPCTX_CAPITALIZATION_FOR_UI_LIST_OR_MENU
:
2042 titlecase
= fSymbols
->fCapitalization
[capContextUsageType
][0];
2044 case UDISPCTX_CAPITALIZATION_FOR_STANDALONE
:
2045 titlecase
= fSymbols
->fCapitalization
[capContextUsageType
][1];
2048 // titlecase = FALSE;
2052 UnicodeString
firstField(appendTo
, beginOffset
);
2053 firstField
.toTitle(fCapitalizationBrkIter
, fLocale
, U_TITLECASE_NO_LOWERCASE
| U_TITLECASE_NO_BREAK_ADJUSTMENT
);
2054 appendTo
.replaceBetween(beginOffset
, appendTo
.length(), firstField
);
2059 handler
.addAttribute(fgPatternIndexToDateFormatField
[patternCharIndex
], beginOffset
, appendTo
.length());
2062 //----------------------------------------------------------------------
2064 void SimpleDateFormat::adoptNumberFormat(NumberFormat
*formatToAdopt
) {
2065 fixNumberFormatForDates(*formatToAdopt
);
2066 delete fNumberFormat
;
2067 fNumberFormat
= formatToAdopt
;
2069 // We successfully set the default number format. Now delete the overrides
2071 if (fSharedNumberFormatters
) {
2072 freeSharedNumberFormatters(fSharedNumberFormatters
);
2073 fSharedNumberFormatters
= NULL
;
2077 void SimpleDateFormat::adoptNumberFormat(const UnicodeString
& fields
, NumberFormat
*formatToAdopt
, UErrorCode
&status
){
2078 fixNumberFormatForDates(*formatToAdopt
);
2079 LocalPointer
<NumberFormat
> fmt(formatToAdopt
);
2080 if (U_FAILURE(status
)) {
2084 // We must ensure fSharedNumberFormatters is allocated.
2085 if (fSharedNumberFormatters
== NULL
) {
2086 fSharedNumberFormatters
= allocSharedNumberFormatters();
2087 if (fSharedNumberFormatters
== NULL
) {
2088 status
= U_MEMORY_ALLOCATION_ERROR
;
2092 const SharedNumberFormat
*newFormat
= createSharedNumberFormat(fmt
.orphan());
2093 if (newFormat
== NULL
) {
2094 status
= U_MEMORY_ALLOCATION_ERROR
;
2097 for (int i
=0; i
<fields
.length(); i
++) {
2098 UChar field
= fields
.charAt(i
);
2099 // if the pattern character is unrecognized, signal an error and bail out
2100 UDateFormatField patternCharIndex
= DateFormatSymbols::getPatternCharIndex(field
);
2101 if (patternCharIndex
== UDAT_FIELD_COUNT
) {
2102 status
= U_INVALID_FORMAT_ERROR
;
2103 newFormat
->deleteIfZeroRefCount();
2107 // Set the number formatter in the table
2108 SharedObject::copyPtr(
2109 newFormat
, fSharedNumberFormatters
[patternCharIndex
]);
2111 newFormat
->deleteIfZeroRefCount();
2114 const NumberFormat
*
2115 SimpleDateFormat::getNumberFormatForField(UChar field
) const {
2116 UDateFormatField index
= DateFormatSymbols::getPatternCharIndex(field
);
2117 if (index
== UDAT_FIELD_COUNT
) {
2120 return getNumberFormatByIndex(index
);
2123 //----------------------------------------------------------------------
2125 SimpleDateFormat::zeroPaddingNumber(
2126 NumberFormat
*currentNumberFormat
,
2127 UnicodeString
&appendTo
,
2128 int32_t value
, int32_t minDigits
, int32_t maxDigits
) const
2130 if (currentNumberFormat
!=NULL
) {
2131 FieldPosition
pos(FieldPosition::DONT_CARE
);
2133 currentNumberFormat
->setMinimumIntegerDigits(minDigits
);
2134 currentNumberFormat
->setMaximumIntegerDigits(maxDigits
);
2135 currentNumberFormat
->format(value
, appendTo
, pos
); // 3rd arg is there to speed up processing
2139 //----------------------------------------------------------------------
2142 * Return true if the given format character, occuring count
2143 * times, represents a numeric field.
2145 UBool
SimpleDateFormat::isNumeric(UChar formatChar
, int32_t count
) {
2146 return DateFormatSymbols::isNumericPatternChar(formatChar
, count
);
2150 SimpleDateFormat::isAtNumericField(const UnicodeString
&pattern
, int32_t patternOffset
) {
2151 if (patternOffset
>= pattern
.length()) {
2155 UChar ch
= pattern
.charAt(patternOffset
);
2156 UDateFormatField f
= DateFormatSymbols::getPatternCharIndex(ch
);
2157 if (f
== UDAT_FIELD_COUNT
) {
2161 int32_t i
= patternOffset
;
2162 while (pattern
.charAt(++i
) == ch
) {}
2163 return DateFormatSymbols::isNumericField(f
, i
- patternOffset
);
2167 SimpleDateFormat::isAfterNonNumericField(const UnicodeString
&pattern
, int32_t patternOffset
) {
2168 if (patternOffset
<= 0) {
2169 // not after any field
2172 UChar ch
= pattern
.charAt(--patternOffset
);
2173 UDateFormatField f
= DateFormatSymbols::getPatternCharIndex(ch
);
2174 if (f
== UDAT_FIELD_COUNT
) {
2175 // not after any field
2178 int32_t i
= patternOffset
;
2179 while (pattern
.charAt(--i
) == ch
) {}
2180 return !DateFormatSymbols::isNumericField(f
, patternOffset
- i
);
2184 SimpleDateFormat::parse(const UnicodeString
& text
, Calendar
& cal
, ParsePosition
& parsePos
) const
2186 UErrorCode status
= U_ZERO_ERROR
;
2187 int32_t pos
= parsePos
.getIndex();
2188 if(parsePos
.getIndex() < 0) {
2189 parsePos
.setErrorIndex(0);
2192 int32_t start
= pos
;
2194 // Hold the day period until everything else is parsed, because we need
2195 // the hour to interpret time correctly.
2196 int32_t dayPeriodInt
= -1;
2198 UBool ambiguousYear
[] = { FALSE
};
2199 int32_t saveHebrewMonth
= -1;
2201 UTimeZoneFormatTimeType tzTimeType
= UTZFMT_TIME_TYPE_UNKNOWN
;
2202 SimpleDateFormatMutableNFs mutableNFs
;
2204 // For parsing abutting numeric fields. 'abutPat' is the
2205 // offset into 'pattern' of the first of 2 or more abutting
2206 // numeric fields. 'abutStart' is the offset into 'text'
2207 // where parsing the fields begins. 'abutPass' starts off as 0
2208 // and increments each time we try to parse the fields.
2209 int32_t abutPat
= -1; // If >=0, we are in a run of abutting numeric fields
2210 int32_t abutStart
= 0;
2211 int32_t abutPass
= 0;
2212 UBool inQuote
= FALSE
;
2214 MessageFormat
* numericLeapMonthFormatter
= NULL
;
2216 Calendar
* calClone
= NULL
;
2217 Calendar
*workCal
= &cal
;
2218 if (&cal
!= fCalendar
&& uprv_strcmp(cal
.getType(), fCalendar
->getType()) != 0) {
2219 // Different calendar type
2220 // We use the time/zone from the input calendar, but
2221 // do not use the input calendar for field calculation.
2222 calClone
= fCalendar
->clone();
2223 if (calClone
!= NULL
) {
2224 calClone
->setTime(cal
.getTime(status
),status
);
2225 if (U_FAILURE(status
)) {
2228 calClone
->setTimeZone(cal
.getTimeZone());
2231 status
= U_MEMORY_ALLOCATION_ERROR
;
2236 if (fSymbols
->fLeapMonthPatterns
!= NULL
&& fSymbols
->fLeapMonthPatternsCount
>= DateFormatSymbols::kMonthPatternsCount
) {
2237 numericLeapMonthFormatter
= new MessageFormat(fSymbols
->fLeapMonthPatterns
[DateFormatSymbols::kLeapMonthPatternNumeric
], fLocale
, status
);
2238 if (numericLeapMonthFormatter
== NULL
) {
2239 status
= U_MEMORY_ALLOCATION_ERROR
;
2241 } else if (U_FAILURE(status
)) {
2242 goto ExitParse
; // this will delete numericLeapMonthFormatter
2246 for (int32_t i
=0; i
<fPattern
.length(); ++i
) {
2247 UChar ch
= fPattern
.charAt(i
);
2249 // Handle alphabetic field characters.
2250 if (!inQuote
&& isSyntaxChar(ch
)) {
2251 int32_t fieldPat
= i
;
2253 // Count the length of this field specifier
2255 while ((i
+1)<fPattern
.length() &&
2256 fPattern
.charAt(i
+1) == ch
) {
2261 if (isNumeric(ch
, count
)) {
2263 // Determine if there is an abutting numeric field.
2264 // Record the start of a set of abutting numeric fields.
2265 if (isAtNumericField(fPattern
, i
+ 1)) {
2272 abutPat
= -1; // End of any abutting fields
2275 // Handle fields within a run of abutting numeric fields. Take
2276 // the pattern "HHmmss" as an example. We will try to parse
2277 // 2/2/2 characters of the input text, then if that fails,
2278 // 1/2/2. We only adjust the width of the leftmost field; the
2279 // others remain fixed. This allows "123456" => 12:34:56, but
2280 // "12345" => 1:23:45. Likewise, for the pattern "yyyyMMdd" we
2281 // try 4/2/2, 3/2/2, 2/2/2, and finally 1/2/2.
2283 // If we are at the start of a run of abutting fields, then
2284 // shorten this field in each pass. If we can't shorten
2285 // this field any more, then the parse of this set of
2286 // abutting numeric fields has failed.
2287 if (fieldPat
== abutPat
) {
2288 count
-= abutPass
++;
2290 status
= U_PARSE_ERROR
;
2295 pos
= subParse(text
, pos
, ch
, count
,
2296 TRUE
, FALSE
, ambiguousYear
, saveHebrewMonth
, *workCal
, i
, numericLeapMonthFormatter
, &tzTimeType
, mutableNFs
);
2298 // If the parse fails anywhere in the run, back up to the
2299 // start of the run and retry.
2307 // Handle non-numeric fields and non-abutting numeric
2309 else if (ch
!= 0x6C) { // pattern char 'l' (SMALL LETTER L) just gets ignored
2310 int32_t s
= subParse(text
, pos
, ch
, count
,
2311 FALSE
, TRUE
, ambiguousYear
, saveHebrewMonth
, *workCal
, i
, numericLeapMonthFormatter
, &tzTimeType
, mutableNFs
, &dayPeriodInt
);
2314 // era not present, in special cases allow this to continue
2315 // from the position where the era was expected
2318 if (i
+1 < fPattern
.length()) {
2319 // move to next pattern character
2320 UChar ch
= fPattern
.charAt(i
+1);
2322 // check for whitespace
2323 if (PatternProps::isWhiteSpace(ch
)) {
2325 // Advance over run in pattern
2326 while ((i
+1)<fPattern
.length() &&
2327 PatternProps::isWhiteSpace(fPattern
.charAt(i
+1))) {
2334 status
= U_PARSE_ERROR
;
2341 // Handle literal pattern characters. These are any
2342 // quoted characters and non-alphabetic unquoted
2346 abutPat
= -1; // End of any abutting fields
2348 if (! matchLiterals(fPattern
, i
, text
, pos
, getBooleanAttribute(UDAT_PARSE_ALLOW_WHITESPACE
, status
), getBooleanAttribute(UDAT_PARSE_PARTIAL_LITERAL_MATCH
, status
), isLenient())) {
2349 status
= U_PARSE_ERROR
;
2355 // Special hack for trailing "." after non-numeric field.
2356 if (text
.charAt(pos
) == 0x2e && getBooleanAttribute(UDAT_PARSE_ALLOW_WHITESPACE
, status
)) {
2357 // only do if the last field is not numeric
2358 if (isAfterNonNumericField(fPattern
, fPattern
.length())) {
2359 pos
++; // skip the extra "."
2363 // If dayPeriod is set, use it in conjunction with hour-of-day to determine am/pm.
2364 if (dayPeriodInt
>= 0) {
2365 DayPeriodRules::DayPeriod dayPeriod
= (DayPeriodRules::DayPeriod
)dayPeriodInt
;
2366 const DayPeriodRules
*ruleSet
= DayPeriodRules::getInstance(this->getSmpFmtLocale(), status
);
2368 if (!cal
.isSet(UCAL_HOUR
) && !cal
.isSet(UCAL_HOUR_OF_DAY
)) {
2369 // If hour is not set, set time to the midpoint of current day period, overwriting
2370 // minutes if it's set.
2371 double midPoint
= ruleSet
->getMidPointForDayPeriod(dayPeriod
, status
);
2373 // If we can't get midPoint we do nothing.
2374 if (U_SUCCESS(status
)) {
2375 // Truncate midPoint toward zero to get the hour.
2376 // Any leftover means it was a half-hour.
2377 int32_t midPointHour
= (int32_t) midPoint
;
2378 int32_t midPointMinute
= (midPoint
- midPointHour
) > 0 ? 30 : 0;
2380 // No need to set am/pm because hour-of-day is set last therefore takes precedence.
2381 cal
.set(UCAL_HOUR_OF_DAY
, midPointHour
);
2382 cal
.set(UCAL_MINUTE
, midPointMinute
);
2387 if (cal
.isSet(UCAL_HOUR_OF_DAY
)) { // Hour is parsed in 24-hour format.
2388 hourOfDay
= cal
.get(UCAL_HOUR_OF_DAY
, status
);
2389 } else { // Hour is parsed in 12-hour format.
2390 hourOfDay
= cal
.get(UCAL_HOUR
, status
);
2391 // cal.get() turns 12 to 0 for 12-hour time; change 0 to 12
2392 // so 0 unambiguously means a 24-hour time from above.
2393 if (hourOfDay
== 0) { hourOfDay
= 12; }
2395 U_ASSERT(0 <= hourOfDay
&& hourOfDay
<= 23);
2398 // If hour-of-day is 0 or 13 thru 23 then input time in unambiguously in 24-hour format.
2399 if (hourOfDay
== 0 || (13 <= hourOfDay
&& hourOfDay
<= 23)) {
2400 // Make hour-of-day take precedence over (hour + am/pm) by setting it again.
2401 cal
.set(UCAL_HOUR_OF_DAY
, hourOfDay
);
2403 // We have a 12-hour time and need to choose between am and pm.
2404 // Behave as if dayPeriod spanned 6 hours each way from its center point.
2405 // This will parse correctly for consistent time + period (e.g. 10 at night) as
2406 // well as provide a reasonable recovery for inconsistent time + period (e.g.
2407 // 9 in the afternoon).
2409 // Assume current time is in the AM.
2410 // - Change 12 back to 0 for easier handling of 12am.
2411 // - Append minutes as fractional hours because e.g. 8:15 and 8:45 could be parsed
2412 // into different half-days if center of dayPeriod is at 14:30.
2413 // - cal.get(MINUTE) will return 0 if MINUTE is unset, which works.
2414 if (hourOfDay
== 12) { hourOfDay
= 0; }
2415 double currentHour
= hourOfDay
+ (cal
.get(UCAL_MINUTE
, status
)) / 60.0;
2416 double midPointHour
= ruleSet
->getMidPointForDayPeriod(dayPeriod
, status
);
2418 if (U_SUCCESS(status
)) {
2419 double hoursAheadMidPoint
= currentHour
- midPointHour
;
2421 // Assume current time is in the AM.
2422 if (-6 <= hoursAheadMidPoint
&& hoursAheadMidPoint
< 6) {
2423 // Assumption holds; set time as such.
2424 cal
.set(UCAL_AM_PM
, 0);
2426 cal
.set(UCAL_AM_PM
, 1);
2433 // At this point the fields of Calendar have been set. Calendar
2434 // will fill in default values for missing fields when the time
2437 parsePos
.setIndex(pos
);
2439 // This part is a problem: When we call parsedDate.after, we compute the time.
2440 // Take the date April 3 2004 at 2:30 am. When this is first set up, the year
2441 // will be wrong if we're parsing a 2-digit year pattern. It will be 1904.
2442 // April 3 1904 is a Sunday (unlike 2004) so it is the DST onset day. 2:30 am
2443 // is therefore an "impossible" time, since the time goes from 1:59 to 3:00 am
2444 // on that day. It is therefore parsed out to fields as 3:30 am. Then we
2445 // add 100 years, and get April 3 2004 at 3:30 am. Note that April 3 2004 is
2446 // a Saturday, so it can have a 2:30 am -- and it should. [LIU]
2448 UDate parsedDate = calendar.getTime();
2449 if( ambiguousYear[0] && !parsedDate.after(fDefaultCenturyStart) ) {
2450 calendar.add(Calendar.YEAR, 100);
2451 parsedDate = calendar.getTime();
2454 // Because of the above condition, save off the fields in case we need to readjust.
2455 // The procedure we use here is not particularly efficient, but there is no other
2456 // way to do this given the API restrictions present in Calendar. We minimize
2457 // inefficiency by only performing this computation when it might apply, that is,
2458 // when the two-digit year is equal to the start year, and thus might fall at the
2459 // front or the back of the default century. This only works because we adjust
2460 // the year correctly to start with in other cases -- see subParse().
2461 if (ambiguousYear
[0] || tzTimeType
!= UTZFMT_TIME_TYPE_UNKNOWN
) // If this is true then the two-digit year == the default start year
2463 // We need a copy of the fields, and we need to avoid triggering a call to
2464 // complete(), which will recalculate the fields. Since we can't access
2465 // the fields[] array in Calendar, we clone the entire object. This will
2466 // stop working if Calendar.clone() is ever rewritten to call complete().
2468 if (ambiguousYear
[0]) {
2470 // Check for failed cloning.
2472 status
= U_MEMORY_ALLOCATION_ERROR
;
2475 UDate parsedDate
= copy
->getTime(status
);
2476 // {sfb} check internalGetDefaultCenturyStart
2477 if (fHaveDefaultCentury
&& (parsedDate
< fDefaultCenturyStart
)) {
2478 // We can't use add here because that does a complete() first.
2479 cal
.set(UCAL_YEAR
, fDefaultCenturyStartYear
+ 100);
2484 if (tzTimeType
!= UTZFMT_TIME_TYPE_UNKNOWN
) {
2486 // Check for failed cloning.
2488 status
= U_MEMORY_ALLOCATION_ERROR
;
2491 const TimeZone
& tz
= cal
.getTimeZone();
2492 BasicTimeZone
*btz
= NULL
;
2494 if (dynamic_cast<const OlsonTimeZone
*>(&tz
) != NULL
2495 || dynamic_cast<const SimpleTimeZone
*>(&tz
) != NULL
2496 || dynamic_cast<const RuleBasedTimeZone
*>(&tz
) != NULL
2497 || dynamic_cast<const VTimeZone
*>(&tz
) != NULL
) {
2498 btz
= (BasicTimeZone
*)&tz
;
2502 copy
->set(UCAL_ZONE_OFFSET
, 0);
2503 copy
->set(UCAL_DST_OFFSET
, 0);
2504 UDate localMillis
= copy
->getTime(status
);
2506 // Make sure parsed time zone type (Standard or Daylight)
2507 // matches the rule used by the parsed time zone.
2510 if (tzTimeType
== UTZFMT_TIME_TYPE_STANDARD
) {
2511 btz
->getOffsetFromLocal(localMillis
,
2512 BasicTimeZone::kStandard
, BasicTimeZone::kStandard
, raw
, dst
, status
);
2514 btz
->getOffsetFromLocal(localMillis
,
2515 BasicTimeZone::kDaylight
, BasicTimeZone::kDaylight
, raw
, dst
, status
);
2518 // No good way to resolve ambiguous time at transition,
2519 // but following code work in most case.
2520 tz
.getOffset(localMillis
, TRUE
, raw
, dst
, status
);
2523 // Now, compare the results with parsed type, either standard or daylight saving time
2524 int32_t resolvedSavings
= dst
;
2525 if (tzTimeType
== UTZFMT_TIME_TYPE_STANDARD
) {
2527 // Override DST_OFFSET = 0 in the result calendar
2528 resolvedSavings
= 0;
2530 } else { // tztype == TZTYPE_DST
2533 UDate time
= localMillis
+ raw
;
2534 // We use the nearest daylight saving time rule.
2535 TimeZoneTransition beforeTrs
, afterTrs
;
2536 UDate beforeT
= time
, afterT
= time
;
2537 int32_t beforeSav
= 0, afterSav
= 0;
2538 UBool beforeTrsAvail
, afterTrsAvail
;
2540 // Search for DST rule before or on the time
2542 beforeTrsAvail
= btz
->getPreviousTransition(beforeT
, TRUE
, beforeTrs
);
2543 if (!beforeTrsAvail
) {
2546 beforeT
= beforeTrs
.getTime() - 1;
2547 beforeSav
= beforeTrs
.getFrom()->getDSTSavings();
2548 if (beforeSav
!= 0) {
2553 // Search for DST rule after the time
2555 afterTrsAvail
= btz
->getNextTransition(afterT
, FALSE
, afterTrs
);
2556 if (!afterTrsAvail
) {
2559 afterT
= afterTrs
.getTime();
2560 afterSav
= afterTrs
.getTo()->getDSTSavings();
2561 if (afterSav
!= 0) {
2566 if (beforeTrsAvail
&& afterTrsAvail
) {
2567 if (time
- beforeT
> afterT
- time
) {
2568 resolvedSavings
= afterSav
;
2570 resolvedSavings
= beforeSav
;
2572 } else if (beforeTrsAvail
&& beforeSav
!= 0) {
2573 resolvedSavings
= beforeSav
;
2574 } else if (afterTrsAvail
&& afterSav
!= 0) {
2575 resolvedSavings
= afterSav
;
2577 resolvedSavings
= btz
->getDSTSavings();
2580 resolvedSavings
= tz
.getDSTSavings();
2582 if (resolvedSavings
== 0) {
2584 resolvedSavings
= U_MILLIS_PER_HOUR
;
2588 cal
.set(UCAL_ZONE_OFFSET
, raw
);
2589 cal
.set(UCAL_DST_OFFSET
, resolvedSavings
);
2594 // Set the parsed result if local calendar is used
2595 // instead of the input calendar
2596 if (U_SUCCESS(status
) && workCal
!= &cal
) {
2597 cal
.setTimeZone(workCal
->getTimeZone());
2598 cal
.setTime(workCal
->getTime(status
), status
);
2601 if (numericLeapMonthFormatter
!= NULL
) {
2602 delete numericLeapMonthFormatter
;
2604 if (calClone
!= NULL
) {
2608 // If any Calendar calls failed, we pretend that we
2609 // couldn't parse the string, when in reality this isn't quite accurate--
2610 // we did parse it; the Calendar calls just failed.
2611 if (U_FAILURE(status
)) {
2612 parsePos
.setErrorIndex(pos
);
2613 parsePos
.setIndex(start
);
2617 //----------------------------------------------------------------------
2620 matchStringWithOptionalDot(const UnicodeString
&text
,
2622 const UnicodeString
&data
);
2624 int32_t SimpleDateFormat::matchQuarterString(const UnicodeString
& text
,
2626 UCalendarDateFields field
,
2627 const UnicodeString
* data
,
2629 Calendar
& cal
) const
2632 int32_t count
= dataCount
;
2634 // There may be multiple strings in the data[] array which begin with
2635 // the same prefix (e.g., Cerven and Cervenec (June and July) in Czech).
2636 // We keep track of the longest match, and return that. Note that this
2637 // unfortunately requires us to test all array elements.
2638 int32_t bestMatchLength
= 0, bestMatch
= -1;
2639 UnicodeString bestMatchName
;
2641 for (; i
< count
; ++i
) {
2642 int32_t matchLength
= 0;
2643 if ((matchLength
= matchStringWithOptionalDot(text
, start
, data
[i
])) > bestMatchLength
) {
2644 bestMatchLength
= matchLength
;
2649 if (bestMatch
>= 0) {
2650 cal
.set(field
, bestMatch
* 3);
2651 return start
+ bestMatchLength
;
2657 int32_t SimpleDateFormat::matchDayPeriodStrings(const UnicodeString
& text
, int32_t start
,
2658 const UnicodeString
* data
, int32_t dataCount
,
2659 int32_t &dayPeriod
) const
2662 int32_t bestMatchLength
= 0, bestMatch
= -1;
2664 for (int32_t i
= 0; i
< dataCount
; ++i
) {
2665 int32_t matchLength
= 0;
2666 if ((matchLength
= matchStringWithOptionalDot(text
, start
, data
[i
])) > bestMatchLength
) {
2667 bestMatchLength
= matchLength
;
2672 if (bestMatch
>= 0) {
2673 dayPeriod
= bestMatch
;
2674 return start
+ bestMatchLength
;
2680 //----------------------------------------------------------------------
2681 #define IS_BIDI_MARK(c) (c==0x200E || c==0x200F || c==0x061C)
2683 UBool
SimpleDateFormat::matchLiterals(const UnicodeString
&pattern
,
2684 int32_t &patternOffset
,
2685 const UnicodeString
&text
,
2686 int32_t &textOffset
,
2687 UBool whitespaceLenient
,
2688 UBool partialMatchLenient
,
2691 UBool inQuote
= FALSE
;
2692 UnicodeString literal
;
2693 int32_t i
= patternOffset
;
2695 // scan pattern looking for contiguous literal characters
2696 for ( ; i
< pattern
.length(); i
+= 1) {
2697 UChar ch
= pattern
.charAt(i
);
2699 if (!inQuote
&& isSyntaxChar(ch
)) {
2704 // Match a quote literal ('') inside OR outside of quotes
2705 if ((i
+ 1) < pattern
.length() && pattern
.charAt(i
+ 1) == QUOTE
) {
2713 if (!IS_BIDI_MARK(ch
)) {
2718 // at this point, literal contains the pattern literal text (without bidi marks)
2719 // and i is the index of the next non-literal pattern character.
2721 int32_t t
= textOffset
;
2723 if (whitespaceLenient
) {
2724 // trim leading, trailing whitespace from the pattern literal
2727 // ignore any leading whitespace (or bidi marks) in the text
2728 while (t
< text
.length()) {
2729 UChar ch
= text
.charAt(t
);
2730 if (!u_isWhitespace(ch
) && !IS_BIDI_MARK(ch
)) {
2737 // Get ignorables, move up here
2738 const UnicodeSet
*ignorables
= NULL
;
2739 UDateFormatField patternCharIndex
= DateFormatSymbols::getPatternCharIndex(pattern
.charAt(i
));
2740 if (patternCharIndex
!= UDAT_FIELD_COUNT
) {
2741 ignorables
= SimpleDateFormatStaticSets::getIgnorables(patternCharIndex
);
2744 for (p
= 0; p
< literal
.length() && t
< text
.length();) {
2745 UBool needWhitespace
= FALSE
;
2747 // Skip any whitespace at current position in pattern,
2748 // but remember whether we found whitespace in the pattern
2749 // (we already deleted any bidi marks in the pattern).
2750 while (p
< literal
.length() && PatternProps::isWhiteSpace(literal
.charAt(p
))) {
2751 needWhitespace
= TRUE
;
2755 // If the pattern has whitespace at this point, skip it in text as well
2756 // (if the text does not have any, that may be an error for strict parsing)
2757 if (needWhitespace
) {
2758 UBool whitespaceInText
= FALSE
;
2760 // Skip any whitespace (or bidi marks) at current position in text,
2761 // but remember whether we found whitespace in the text at this point.
2762 while (t
< text
.length()) {
2763 UChar tch
= text
.charAt(t
);
2764 if (u_isUWhiteSpace(tch
) || PatternProps::isWhiteSpace(tch
)) {
2765 whitespaceInText
= TRUE
;
2766 } else if (!IS_BIDI_MARK(tch
)) {
2773 // TODO: should we require internal spaces
2774 // in lenient mode? (There won't be any
2775 // leading or trailing spaces)
2776 if (!whitespaceLenient
&& !whitespaceInText
) {
2777 // didn't find matching whitespace:
2778 // an error in strict mode
2782 // In strict mode, this run of whitespace
2783 // may have been at the end.
2784 if (p
>= literal
.length()) {
2788 // Still need to skip any bidi marks in the text
2789 while (t
< text
.length() && IS_BIDI_MARK(text
.charAt(t
))) {
2793 if (t
>= text
.length() || literal
.charAt(p
) != text
.charAt(t
)) {
2794 // Ran out of text, or found a non-matching character:
2795 // OK in lenient mode, an error in strict mode.
2796 if (whitespaceLenient
) {
2797 if (t
== textOffset
&& text
.charAt(t
) == 0x2e &&
2798 isAfterNonNumericField(pattern
, patternOffset
)) {
2799 // Lenient mode and the literal input text begins with a "." and
2800 // we are after a non-numeric field: We skip the "."
2802 continue; // Do not update p.
2804 // if it is actual whitespace and we're whitespace lenient it's OK
2806 UChar wsc
= text
.charAt(t
);
2807 if(PatternProps::isWhiteSpace(wsc
)) {
2808 // Lenient mode and it's just whitespace we skip it
2810 continue; // Do not update p.
2813 // hack around oldleniency being a bit of a catch-all bucket and we're just adding support specifically for paritial matches
2814 // This fix is for http://bugs.icu-project.org/trac/ticket/10855 and adds "&& oldLeniency"
2815 //if(partialMatchLenient && oldLeniency) {
2816 // However this causes problems for Apple, see <rdar://problem/20692829> regressions in Chinese date parsing
2817 // We don't want to go back to just "if(partialMatchLenient)" as in ICU 53, that is too lenient for strict mode.
2818 // So if the pattern character is in the separator set, we allow the text character to be in that set or be an alpha char.
2819 if( partialMatchLenient
&& ( oldLeniency
||
2820 ( ignorables
!= NULL
&& ignorables
->contains(literal
.charAt(p
)) && (ignorables
->contains(text
.charAt(t
)) || u_isalpha(text
.charAt(t
))) ) )
2831 // At this point if we're in strict mode we have a complete match.
2832 // If we're in lenient mode we may have a partial match, or no
2835 // no match. Pretend it matched a run of whitespace
2836 // and ignorables in the text.
2838 for (t
= textOffset
; t
< text
.length(); t
+= 1) {
2839 UChar ch
= text
.charAt(t
);
2841 if (!IS_BIDI_MARK(ch
) && (ignorables
== NULL
|| !ignorables
->contains(ch
))) {
2847 // if we get here, we've got a complete match.
2848 patternOffset
= i
- 1;
2854 //----------------------------------------------------------------------
2856 int32_t SimpleDateFormat::matchString(const UnicodeString
& text
,
2858 UCalendarDateFields field
,
2859 const UnicodeString
* data
,
2861 const UnicodeString
* monthPattern
,
2862 Calendar
& cal
) const
2865 int32_t count
= dataCount
;
2867 if (field
== UCAL_DAY_OF_WEEK
) i
= 1;
2869 // There may be multiple strings in the data[] array which begin with
2870 // the same prefix (e.g., Cerven and Cervenec (June and July) in Czech).
2871 // We keep track of the longest match, and return that. Note that this
2872 // unfortunately requires us to test all array elements.
2873 int32_t bestMatchLength
= 0, bestMatch
= -1;
2874 UnicodeString bestMatchName
;
2875 int32_t isLeapMonth
= 0;
2877 for (; i
< count
; ++i
) {
2878 int32_t matchLen
= 0;
2879 if ((matchLen
= matchStringWithOptionalDot(text
, start
, data
[i
])) > bestMatchLength
) {
2881 bestMatchLength
= matchLen
;
2884 if (monthPattern
!= NULL
) {
2885 UErrorCode status
= U_ZERO_ERROR
;
2886 UnicodeString leapMonthName
;
2887 SimpleFormatter(*monthPattern
, 1, 1, status
).format(data
[i
], leapMonthName
, status
);
2888 if (U_SUCCESS(status
)) {
2889 if ((matchLen
= matchStringWithOptionalDot(text
, start
, leapMonthName
)) > bestMatchLength
) {
2891 bestMatchLength
= matchLen
;
2898 if (bestMatch
>= 0) {
2899 if (field
< UCAL_FIELD_COUNT
) {
2900 // Adjustment for Hebrew Calendar month Adar II
2901 if (!strcmp(cal
.getType(),"hebrew") && field
==UCAL_MONTH
&& bestMatch
==13) {
2904 if (field
== UCAL_YEAR
) {
2905 bestMatch
++; // only get here for cyclic year names, which match 1-based years 1-60
2907 cal
.set(field
, bestMatch
);
2909 if (monthPattern
!= NULL
) {
2910 cal
.set(UCAL_IS_LEAP_MONTH
, isLeapMonth
);
2914 return start
+ bestMatchLength
;
2921 matchStringWithOptionalDot(const UnicodeString
&text
,
2923 const UnicodeString
&data
) {
2924 UErrorCode sts
= U_ZERO_ERROR
;
2925 int32_t matchLenText
= 0;
2926 int32_t matchLenData
= 0;
2928 u_caseInsensitivePrefixMatch(text
.getBuffer() + index
, text
.length() - index
,
2929 data
.getBuffer(), data
.length(),
2930 0 /* default case option */,
2931 &matchLenText
, &matchLenData
,
2933 U_ASSERT (U_SUCCESS(sts
));
2935 if (matchLenData
== data
.length() /* normal match */
2936 || (data
.charAt(data
.length() - 1) == 0x2e
2937 && matchLenData
== data
.length() - 1 /* match without trailing dot */)) {
2938 return matchLenText
;
2944 //----------------------------------------------------------------------
2947 SimpleDateFormat::set2DigitYearStart(UDate d
, UErrorCode
& status
)
2949 parseAmbiguousDatesAsAfter(d
, status
);
2953 * Private member function that converts the parsed date strings into
2954 * timeFields. Returns -start (for ParsePosition) if failed.
2956 int32_t SimpleDateFormat::subParse(const UnicodeString
& text
, int32_t& start
, UChar ch
, int32_t count
,
2957 UBool obeyCount
, UBool allowNegative
, UBool ambiguousYear
[], int32_t& saveHebrewMonth
, Calendar
& cal
,
2958 int32_t patLoc
, MessageFormat
* numericLeapMonthFormatter
, UTimeZoneFormatTimeType
*tzTimeType
, SimpleDateFormatMutableNFs
&mutableNFs
,
2959 int32_t *dayPeriod
) const
2965 UErrorCode status
= U_ZERO_ERROR
;
2966 ParsePosition
pos(0);
2967 UDateFormatField patternCharIndex
= DateFormatSymbols::getPatternCharIndex(ch
);
2968 NumberFormat
*currentNumberFormat
;
2970 int32_t tzParseOptions
= (isLenient())? UTZFMT_PARSE_OPTION_ALL_STYLES
: UTZFMT_PARSE_OPTION_NONE
;
2971 UBool gotNumber
= FALSE
;
2973 #if defined (U_DEBUG_CAL)
2974 //fprintf(stderr, "%s:%d - [%c] st=%d \n", __FILE__, __LINE__, (char) ch, start);
2977 if (patternCharIndex
== UDAT_FIELD_COUNT
) {
2981 currentNumberFormat
= mutableNFs
.get(getNumberFormatByIndex(patternCharIndex
));
2982 if (currentNumberFormat
== NULL
) {
2985 UCalendarDateFields field
= fgPatternIndexToCalendarField
[patternCharIndex
]; // UCAL_FIELD_COUNT if irrelevant
2986 UnicodeString
hebr("hebr", 4, US_INV
);
2988 if (numericLeapMonthFormatter
!= NULL
) {
2989 numericLeapMonthFormatter
->setFormats((const Format
**)¤tNumberFormat
, 1);
2991 UBool isChineseCalendar
= (uprv_strcmp(cal
.getType(),"chinese") == 0 || uprv_strcmp(cal
.getType(),"dangi") == 0);
2993 // If there are any spaces here, skip over them. If we hit the end
2994 // of the string, then fail.
2996 if (start
>= text
.length()) {
2999 UChar32 c
= text
.char32At(start
);
3000 if (!u_isUWhiteSpace(c
) /*||*/ && !PatternProps::isWhiteSpace(c
)) {
3003 start
+= U16_LENGTH(c
);
3005 pos
.setIndex(start
);
3007 // We handle a few special cases here where we need to parse
3008 // a number value. We handle further, more generic cases below. We need
3009 // to handle some of them here because some fields require extra processing on
3010 // the parsed value.
3011 if (patternCharIndex
== UDAT_HOUR_OF_DAY1_FIELD
|| // k
3012 patternCharIndex
== UDAT_HOUR_OF_DAY0_FIELD
|| // H
3013 patternCharIndex
== UDAT_HOUR1_FIELD
|| // h
3014 patternCharIndex
== UDAT_HOUR0_FIELD
|| // K
3015 (patternCharIndex
== UDAT_DOW_LOCAL_FIELD
&& count
<= 2) || // e
3016 (patternCharIndex
== UDAT_STANDALONE_DAY_FIELD
&& count
<= 2) || // c
3017 (patternCharIndex
== UDAT_MONTH_FIELD
&& count
<= 2) || // M
3018 (patternCharIndex
== UDAT_STANDALONE_MONTH_FIELD
&& count
<= 2) || // L
3019 (patternCharIndex
== UDAT_QUARTER_FIELD
&& count
<= 2) || // Q
3020 (patternCharIndex
== UDAT_STANDALONE_QUARTER_FIELD
&& count
<= 2) || // q
3021 patternCharIndex
== UDAT_YEAR_FIELD
|| // y
3022 patternCharIndex
== UDAT_YEAR_WOY_FIELD
|| // Y
3023 patternCharIndex
== UDAT_YEAR_NAME_FIELD
|| // U (falls back to numeric)
3024 (patternCharIndex
== UDAT_ERA_FIELD
&& isChineseCalendar
) || // G
3025 patternCharIndex
== UDAT_FRACTIONAL_SECOND_FIELD
) // S
3027 int32_t parseStart
= pos
.getIndex();
3028 // It would be good to unify this with the obeyCount logic below,
3029 // but that's going to be difficult.
3030 const UnicodeString
* src
;
3032 UBool parsedNumericLeapMonth
= FALSE
;
3033 if (numericLeapMonthFormatter
!= NULL
&& (patternCharIndex
== UDAT_MONTH_FIELD
|| patternCharIndex
== UDAT_STANDALONE_MONTH_FIELD
)) {
3035 Formattable
* args
= numericLeapMonthFormatter
->parse(text
, pos
, argCount
);
3036 if (args
!= NULL
&& argCount
== 1 && pos
.getIndex() > parseStart
&& args
[0].isNumeric()) {
3037 parsedNumericLeapMonth
= TRUE
;
3038 number
.setLong(args
[0].getLong());
3039 cal
.set(UCAL_IS_LEAP_MONTH
, 1);
3042 pos
.setIndex(parseStart
);
3043 cal
.set(UCAL_IS_LEAP_MONTH
, 0);
3047 if (!parsedNumericLeapMonth
) {
3049 if ((start
+count
) > text
.length()) {
3053 text
.extractBetween(0, start
+ count
, temp
);
3059 parseInt(*src
, number
, pos
, allowNegative
,currentNumberFormat
);
3062 int32_t txtLoc
= pos
.getIndex();
3064 if (txtLoc
> parseStart
) {
3065 value
= number
.getLong();
3068 // suffix processing
3070 txtLoc
= checkIntSuffix(text
, txtLoc
, patLoc
+1, TRUE
);
3071 if (txtLoc
!= pos
.getIndex()) {
3076 txtLoc
= checkIntSuffix(text
, txtLoc
, patLoc
+1, FALSE
);
3079 // Check the range of the value
3080 if (!getBooleanAttribute(UDAT_PARSE_ALLOW_WHITESPACE
, status
)) {
3081 int32_t bias
= gFieldRangeBias
[patternCharIndex
];
3082 if (bias
>= 0 && (value
> cal
.getMaximum(field
) + bias
|| value
< cal
.getMinimum(field
) + bias
)) {
3086 int32_t bias
= gFieldRangeBiasLenient
[patternCharIndex
];
3087 if (bias
>= 0 && (value
> cal
.getMaximum(field
) + bias
)) {
3092 pos
.setIndex(txtLoc
);
3096 // Make sure that we got a number if
3097 // we want one, and didn't get one
3098 // if we don't want one.
3099 switch (patternCharIndex
) {
3100 case UDAT_HOUR_OF_DAY1_FIELD
:
3101 case UDAT_HOUR_OF_DAY0_FIELD
:
3102 case UDAT_HOUR1_FIELD
:
3103 case UDAT_HOUR0_FIELD
:
3104 // special range check for hours:
3105 if (value
< 0 || value
> 24) {
3109 // fall through to gotNumber check
3111 case UDAT_YEAR_FIELD
:
3112 case UDAT_YEAR_WOY_FIELD
:
3113 case UDAT_FRACTIONAL_SECOND_FIELD
:
3114 // these must be a number
3122 // we check the rest of the fields below.
3126 switch (patternCharIndex
) {
3127 case UDAT_ERA_FIELD
:
3128 if (isChineseCalendar
) {
3132 cal
.set(UCAL_ERA
, value
);
3133 return pos
.getIndex();
3136 ps
= matchString(text
, start
, UCAL_ERA
, fSymbols
->fNarrowEras
, fSymbols
->fNarrowErasCount
, NULL
, cal
);
3137 } else if (count
== 4) {
3138 ps
= matchString(text
, start
, UCAL_ERA
, fSymbols
->fEraNames
, fSymbols
->fEraNamesCount
, NULL
, cal
);
3140 ps
= matchString(text
, start
, UCAL_ERA
, fSymbols
->fEras
, fSymbols
->fErasCount
, NULL
, cal
);
3143 // check return position, if it equals -start, then matchString error
3144 // special case the return code so we don't necessarily fail out until we
3145 // verify no year information also
3151 case UDAT_YEAR_FIELD
:
3152 // If there are 3 or more YEAR pattern characters, this indicates
3153 // that the year value is to be treated literally, without any
3154 // two-digit year adjustments (e.g., from "01" to 2001). Otherwise
3155 // we made adjustments to place the 2-digit year in the proper
3156 // century, for parsed strings from "00" to "99". Any other string
3157 // is treated literally: "2250", "-1", "1", "002".
3158 if (fDateOverride
.compare(hebr
)==0 && value
< 1000) {
3159 value
+= HEBREW_CAL_CUR_MILLENIUM_START_YEAR
;
3160 } else if (text
.moveIndex32(start
, 2) == pos
.getIndex() && !isChineseCalendar
3161 && u_isdigit(text
.char32At(start
))
3162 && u_isdigit(text
.char32At(text
.moveIndex32(start
, 1))))
3164 // only adjust year for patterns less than 3.
3166 // Assume for example that the defaultCenturyStart is 6/18/1903.
3167 // This means that two-digit years will be forced into the range
3168 // 6/18/1903 to 6/17/2003. As a result, years 00, 01, and 02
3169 // correspond to 2000, 2001, and 2002. Years 04, 05, etc. correspond
3170 // to 1904, 1905, etc. If the year is 03, then it is 2003 if the
3171 // other fields specify a date before 6/18, or 1903 if they specify a
3172 // date afterwards. As a result, 03 is an ambiguous year. All other
3173 // two-digit years are unambiguous.
3174 if(fHaveDefaultCentury
) { // check if this formatter even has a pivot year
3175 int32_t ambiguousTwoDigitYear
= fDefaultCenturyStartYear
% 100;
3176 ambiguousYear
[0] = (value
== ambiguousTwoDigitYear
);
3177 value
+= (fDefaultCenturyStartYear
/100)*100 +
3178 (value
< ambiguousTwoDigitYear
? 100 : 0);
3182 cal
.set(UCAL_YEAR
, value
);
3184 // Delayed checking for adjustment of Hebrew month numbers in non-leap years.
3185 if (saveHebrewMonth
>= 0) {
3186 HebrewCalendar
*hc
= (HebrewCalendar
*)&cal
;
3187 if (!hc
->isLeapYear(value
) && saveHebrewMonth
>= 6) {
3188 cal
.set(UCAL_MONTH
,saveHebrewMonth
);
3190 cal
.set(UCAL_MONTH
,saveHebrewMonth
-1);
3192 saveHebrewMonth
= -1;
3194 return pos
.getIndex();
3196 case UDAT_YEAR_WOY_FIELD
:
3197 // Comment is the same as for UDAT_Year_FIELDs - look above
3198 if (fDateOverride
.compare(hebr
)==0 && value
< 1000) {
3199 value
+= HEBREW_CAL_CUR_MILLENIUM_START_YEAR
;
3200 } else if (text
.moveIndex32(start
, 2) == pos
.getIndex()
3201 && u_isdigit(text
.char32At(start
))
3202 && u_isdigit(text
.char32At(text
.moveIndex32(start
, 1)))
3203 && fHaveDefaultCentury
)
3205 int32_t ambiguousTwoDigitYear
= fDefaultCenturyStartYear
% 100;
3206 ambiguousYear
[0] = (value
== ambiguousTwoDigitYear
);
3207 value
+= (fDefaultCenturyStartYear
/100)*100 +
3208 (value
< ambiguousTwoDigitYear
? 100 : 0);
3210 cal
.set(UCAL_YEAR_WOY
, value
);
3211 return pos
.getIndex();
3213 case UDAT_YEAR_NAME_FIELD
:
3214 if (fSymbols
->fShortYearNames
!= NULL
) {
3215 int32_t newStart
= matchString(text
, start
, UCAL_YEAR
, fSymbols
->fShortYearNames
, fSymbols
->fShortYearNamesCount
, NULL
, cal
);
3220 if (gotNumber
&& (getBooleanAttribute(UDAT_PARSE_ALLOW_NUMERIC
,status
) || value
> fSymbols
->fShortYearNamesCount
)) {
3221 cal
.set(UCAL_YEAR
, value
);
3222 return pos
.getIndex();
3226 case UDAT_MONTH_FIELD
:
3227 case UDAT_STANDALONE_MONTH_FIELD
:
3228 if (gotNumber
) // i.e., M or MM.
3230 // When parsing month numbers from the Hebrew Calendar, we might need to adjust the month depending on whether
3231 // 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
3232 // the year is parsed.
3233 if (!strcmp(cal
.getType(),"hebrew")) {
3234 HebrewCalendar
*hc
= (HebrewCalendar
*)&cal
;
3235 if (cal
.isSet(UCAL_YEAR
)) {
3236 UErrorCode status
= U_ZERO_ERROR
;
3237 if (!hc
->isLeapYear(hc
->get(UCAL_YEAR
,status
)) && value
>= 6) {
3238 cal
.set(UCAL_MONTH
, value
);
3240 cal
.set(UCAL_MONTH
, value
- 1);
3243 saveHebrewMonth
= value
;
3246 // Don't want to parse the month if it is a string
3247 // while pattern uses numeric style: M/MM, L/LL
3248 // [We computed 'value' above.]
3249 cal
.set(UCAL_MONTH
, value
- 1);
3251 return pos
.getIndex();
3253 // count >= 3 // i.e., MMM/MMMM, LLL/LLLL
3254 // Want to be able to parse both short and long forms.
3255 // Try count == 4 first:
3256 UnicodeString
* wideMonthPat
= NULL
;
3257 UnicodeString
* shortMonthPat
= NULL
;
3258 if (fSymbols
->fLeapMonthPatterns
!= NULL
&& fSymbols
->fLeapMonthPatternsCount
>= DateFormatSymbols::kMonthPatternsCount
) {
3259 if (patternCharIndex
==UDAT_MONTH_FIELD
) {
3260 wideMonthPat
= &fSymbols
->fLeapMonthPatterns
[DateFormatSymbols::kLeapMonthPatternFormatWide
];
3261 shortMonthPat
= &fSymbols
->fLeapMonthPatterns
[DateFormatSymbols::kLeapMonthPatternFormatAbbrev
];
3263 wideMonthPat
= &fSymbols
->fLeapMonthPatterns
[DateFormatSymbols::kLeapMonthPatternStandaloneWide
];
3264 shortMonthPat
= &fSymbols
->fLeapMonthPatterns
[DateFormatSymbols::kLeapMonthPatternStandaloneAbbrev
];
3267 int32_t newStart
= 0;
3268 if (patternCharIndex
==UDAT_MONTH_FIELD
) {
3269 if(getBooleanAttribute(UDAT_PARSE_MULTIPLE_PATTERNS_FOR_MATCH
, status
) || count
== 4) {
3270 newStart
= matchString(text
, start
, UCAL_MONTH
, fSymbols
->fMonths
, fSymbols
->fMonthsCount
, wideMonthPat
, cal
); // try MMMM
3275 if(getBooleanAttribute(UDAT_PARSE_MULTIPLE_PATTERNS_FOR_MATCH
, status
) || count
== 3) {
3276 newStart
= matchString(text
, start
, UCAL_MONTH
, fSymbols
->fShortMonths
, fSymbols
->fShortMonthsCount
, shortMonthPat
, cal
); // try MMM
3279 if(getBooleanAttribute(UDAT_PARSE_MULTIPLE_PATTERNS_FOR_MATCH
, status
) || count
== 4) {
3280 newStart
= matchString(text
, start
, UCAL_MONTH
, fSymbols
->fStandaloneMonths
, fSymbols
->fStandaloneMonthsCount
, wideMonthPat
, cal
); // try LLLL
3285 if(getBooleanAttribute(UDAT_PARSE_MULTIPLE_PATTERNS_FOR_MATCH
, status
) || count
== 3) {
3286 newStart
= matchString(text
, start
, UCAL_MONTH
, fSymbols
->fStandaloneShortMonths
, fSymbols
->fStandaloneShortMonthsCount
, shortMonthPat
, cal
); // try LLL
3289 if (newStart
> 0 || !getBooleanAttribute(UDAT_PARSE_ALLOW_NUMERIC
, status
)) // currently we do not try to parse MMMMM/LLLLL: #8860
3291 // else we allowing parsing as number, below
3295 case UDAT_HOUR_OF_DAY1_FIELD
:
3296 // [We computed 'value' above.]
3297 if (value
== cal
.getMaximum(UCAL_HOUR_OF_DAY
) + 1)
3300 // fall through to set field
3302 case UDAT_HOUR_OF_DAY0_FIELD
:
3303 cal
.set(UCAL_HOUR_OF_DAY
, value
);
3304 return pos
.getIndex();
3306 case UDAT_FRACTIONAL_SECOND_FIELD
:
3307 // Fractional seconds left-justify
3308 i
= countDigits(text
, start
, pos
.getIndex());
3322 cal
.set(UCAL_MILLISECOND
, value
);
3323 return pos
.getIndex();
3325 case UDAT_DOW_LOCAL_FIELD
:
3326 if (gotNumber
) // i.e., e or ee
3328 // [We computed 'value' above.]
3329 cal
.set(UCAL_DOW_LOCAL
, value
);
3330 return pos
.getIndex();
3332 // else for eee-eeeee fall through to handling of EEE-EEEEE
3333 // fall through, do not break here
3335 case UDAT_DAY_OF_WEEK_FIELD
:
3337 // Want to be able to parse both short and long forms.
3338 // Try count == 4 (EEEE) wide first:
3339 int32_t newStart
= 0;
3340 if(getBooleanAttribute(UDAT_PARSE_MULTIPLE_PATTERNS_FOR_MATCH
, status
) || count
== 4) {
3341 if ((newStart
= matchString(text
, start
, UCAL_DAY_OF_WEEK
,
3342 fSymbols
->fWeekdays
, fSymbols
->fWeekdaysCount
, NULL
, cal
)) > 0)
3345 // EEEE wide failed, now try EEE abbreviated
3346 if(getBooleanAttribute(UDAT_PARSE_MULTIPLE_PATTERNS_FOR_MATCH
, status
) || count
== 3) {
3347 if ((newStart
= matchString(text
, start
, UCAL_DAY_OF_WEEK
,
3348 fSymbols
->fShortWeekdays
, fSymbols
->fShortWeekdaysCount
, NULL
, cal
)) > 0)
3351 // EEE abbreviated failed, now try EEEEEE short
3352 if(getBooleanAttribute(UDAT_PARSE_MULTIPLE_PATTERNS_FOR_MATCH
, status
) || count
== 6) {
3353 if ((newStart
= matchString(text
, start
, UCAL_DAY_OF_WEEK
,
3354 fSymbols
->fShorterWeekdays
, fSymbols
->fShorterWeekdaysCount
, NULL
, cal
)) > 0)
3357 // EEEEEE short failed, now try EEEEE narrow
3358 if(getBooleanAttribute(UDAT_PARSE_MULTIPLE_PATTERNS_FOR_MATCH
, status
) || count
== 5) {
3359 if ((newStart
= matchString(text
, start
, UCAL_DAY_OF_WEEK
,
3360 fSymbols
->fNarrowWeekdays
, fSymbols
->fNarrowWeekdaysCount
, NULL
, cal
)) > 0)
3363 if (!getBooleanAttribute(UDAT_PARSE_ALLOW_NUMERIC
, status
) || patternCharIndex
== UDAT_DAY_OF_WEEK_FIELD
)
3365 // else we allowing parsing as number, below
3369 case UDAT_STANDALONE_DAY_FIELD
:
3371 if (gotNumber
) // c or cc
3373 // [We computed 'value' above.]
3374 cal
.set(UCAL_DOW_LOCAL
, value
);
3375 return pos
.getIndex();
3377 // Want to be able to parse both short and long forms.
3378 // Try count == 4 (cccc) first:
3379 int32_t newStart
= 0;
3380 if(getBooleanAttribute(UDAT_PARSE_MULTIPLE_PATTERNS_FOR_MATCH
, status
) || count
== 4) {
3381 if ((newStart
= matchString(text
, start
, UCAL_DAY_OF_WEEK
,
3382 fSymbols
->fStandaloneWeekdays
, fSymbols
->fStandaloneWeekdaysCount
, NULL
, cal
)) > 0)
3385 if(getBooleanAttribute(UDAT_PARSE_MULTIPLE_PATTERNS_FOR_MATCH
, status
) || count
== 3) {
3386 if ((newStart
= matchString(text
, start
, UCAL_DAY_OF_WEEK
,
3387 fSymbols
->fStandaloneShortWeekdays
, fSymbols
->fStandaloneShortWeekdaysCount
, NULL
, cal
)) > 0)
3390 if(getBooleanAttribute(UDAT_PARSE_MULTIPLE_PATTERNS_FOR_MATCH
, status
) || count
== 6) {
3391 if ((newStart
= matchString(text
, start
, UCAL_DAY_OF_WEEK
,
3392 fSymbols
->fStandaloneShorterWeekdays
, fSymbols
->fStandaloneShorterWeekdaysCount
, NULL
, cal
)) > 0)
3395 if (!getBooleanAttribute(UDAT_PARSE_ALLOW_NUMERIC
, status
))
3397 // else we allowing parsing as number, below
3401 case UDAT_AM_PM_FIELD
:
3403 // optionally try both wide/abbrev and narrow forms
3404 int32_t newStart
= 0;
3406 if( getBooleanAttribute(UDAT_PARSE_MULTIPLE_PATTERNS_FOR_MATCH
, status
) || count
< 5 ) {
3407 if ((newStart
= matchString(text
, start
, UCAL_AM_PM
, fSymbols
->fAmPms
, fSymbols
->fAmPmsCount
, NULL
, cal
)) > 0) {
3412 if( getBooleanAttribute(UDAT_PARSE_MULTIPLE_PATTERNS_FOR_MATCH
, status
) || count
>= 5 ) {
3413 if ((newStart
= matchString(text
, start
, UCAL_AM_PM
, fSymbols
->fNarrowAmPms
, fSymbols
->fNarrowAmPmsCount
, NULL
, cal
)) > 0) {
3417 // no matches for given options
3421 case UDAT_HOUR1_FIELD
:
3422 // [We computed 'value' above.]
3423 if (value
== cal
.getLeastMaximum(UCAL_HOUR
)+1)
3426 // fall through to set field
3428 case UDAT_HOUR0_FIELD
:
3429 cal
.set(UCAL_HOUR
, value
);
3430 return pos
.getIndex();
3432 case UDAT_QUARTER_FIELD
:
3433 if (gotNumber
) // i.e., Q or QQ.
3435 // Don't want to parse the month if it is a string
3436 // while pattern uses numeric style: Q or QQ.
3437 // [We computed 'value' above.]
3438 cal
.set(UCAL_MONTH
, (value
- 1) * 3);
3439 return pos
.getIndex();
3441 // count >= 3 // i.e., QQQ or QQQQ
3442 // Want to be able to parse both short and long forms.
3443 // Try count == 4 first:
3444 int32_t newStart
= 0;
3446 if(getBooleanAttribute(UDAT_PARSE_MULTIPLE_PATTERNS_FOR_MATCH
, status
) || count
== 4) {
3447 if ((newStart
= matchQuarterString(text
, start
, UCAL_MONTH
,
3448 fSymbols
->fQuarters
, fSymbols
->fQuartersCount
, cal
)) > 0)
3451 if(getBooleanAttribute(UDAT_PARSE_MULTIPLE_PATTERNS_FOR_MATCH
, status
) || count
== 3) {
3452 if ((newStart
= matchQuarterString(text
, start
, UCAL_MONTH
,
3453 fSymbols
->fShortQuarters
, fSymbols
->fShortQuartersCount
, cal
)) > 0)
3456 if (!getBooleanAttribute(UDAT_PARSE_ALLOW_NUMERIC
, status
))
3458 // else we allowing parsing as number, below
3459 if(!getBooleanAttribute(UDAT_PARSE_MULTIPLE_PATTERNS_FOR_MATCH
, status
))
3464 case UDAT_STANDALONE_QUARTER_FIELD
:
3465 if (gotNumber
) // i.e., q or qq.
3467 // Don't want to parse the month if it is a string
3468 // while pattern uses numeric style: q or q.
3469 // [We computed 'value' above.]
3470 cal
.set(UCAL_MONTH
, (value
- 1) * 3);
3471 return pos
.getIndex();
3473 // count >= 3 // i.e., qqq or qqqq
3474 // Want to be able to parse both short and long forms.
3475 // Try count == 4 first:
3476 int32_t newStart
= 0;
3478 if(getBooleanAttribute(UDAT_PARSE_MULTIPLE_PATTERNS_FOR_MATCH
, status
) || count
== 4) {
3479 if ((newStart
= matchQuarterString(text
, start
, UCAL_MONTH
,
3480 fSymbols
->fStandaloneQuarters
, fSymbols
->fStandaloneQuartersCount
, cal
)) > 0)
3483 if(getBooleanAttribute(UDAT_PARSE_MULTIPLE_PATTERNS_FOR_MATCH
, status
) || count
== 3) {
3484 if ((newStart
= matchQuarterString(text
, start
, UCAL_MONTH
,
3485 fSymbols
->fStandaloneShortQuarters
, fSymbols
->fStandaloneShortQuartersCount
, cal
)) > 0)
3488 if (!getBooleanAttribute(UDAT_PARSE_ALLOW_NUMERIC
, status
))
3490 // else we allowing parsing as number, below
3491 if(!getBooleanAttribute(UDAT_PARSE_MULTIPLE_PATTERNS_FOR_MATCH
, status
))
3496 case UDAT_TIMEZONE_FIELD
: // 'z'
3498 UTimeZoneFormatStyle style
= (count
< 4) ? UTZFMT_STYLE_SPECIFIC_SHORT
: UTZFMT_STYLE_SPECIFIC_LONG
;
3499 TimeZone
*tz
= tzFormat()->parse(style
, text
, pos
, tzParseOptions
, tzTimeType
);
3501 cal
.adoptTimeZone(tz
);
3502 return pos
.getIndex();
3506 case UDAT_TIMEZONE_RFC_FIELD
: // 'Z'
3508 UTimeZoneFormatStyle style
= (count
< 4) ?
3509 UTZFMT_STYLE_ISO_BASIC_LOCAL_FULL
: ((count
== 5) ? UTZFMT_STYLE_ISO_EXTENDED_FULL
: UTZFMT_STYLE_LOCALIZED_GMT
);
3510 TimeZone
*tz
= tzFormat()->parse(style
, text
, pos
, tzTimeType
);
3512 cal
.adoptTimeZone(tz
);
3513 return pos
.getIndex();
3517 case UDAT_TIMEZONE_GENERIC_FIELD
: // 'v'
3519 UTimeZoneFormatStyle style
= (count
< 4) ? UTZFMT_STYLE_GENERIC_SHORT
: UTZFMT_STYLE_GENERIC_LONG
;
3520 TimeZone
*tz
= tzFormat()->parse(style
, text
, pos
, tzParseOptions
, tzTimeType
);
3522 cal
.adoptTimeZone(tz
);
3523 return pos
.getIndex();
3527 case UDAT_TIMEZONE_SPECIAL_FIELD
: // 'V'
3529 UTimeZoneFormatStyle style
;
3532 style
= UTZFMT_STYLE_ZONE_ID_SHORT
;
3535 style
= UTZFMT_STYLE_ZONE_ID
;
3538 style
= UTZFMT_STYLE_EXEMPLAR_LOCATION
;
3541 style
= UTZFMT_STYLE_GENERIC_LOCATION
;
3544 TimeZone
*tz
= tzFormat()->parse(style
, text
, pos
, tzTimeType
);
3546 cal
.adoptTimeZone(tz
);
3547 return pos
.getIndex();
3551 case UDAT_TIMEZONE_LOCALIZED_GMT_OFFSET_FIELD
: // 'O'
3553 UTimeZoneFormatStyle style
= (count
< 4) ? UTZFMT_STYLE_LOCALIZED_GMT_SHORT
: UTZFMT_STYLE_LOCALIZED_GMT
;
3554 TimeZone
*tz
= tzFormat()->parse(style
, text
, pos
, tzTimeType
);
3556 cal
.adoptTimeZone(tz
);
3557 return pos
.getIndex();
3561 case UDAT_TIMEZONE_ISO_FIELD
: // 'X'
3563 UTimeZoneFormatStyle style
;
3566 style
= UTZFMT_STYLE_ISO_BASIC_SHORT
;
3569 style
= UTZFMT_STYLE_ISO_BASIC_FIXED
;
3572 style
= UTZFMT_STYLE_ISO_EXTENDED_FIXED
;
3575 style
= UTZFMT_STYLE_ISO_BASIC_FULL
;
3578 style
= UTZFMT_STYLE_ISO_EXTENDED_FULL
;
3581 TimeZone
*tz
= tzFormat()->parse(style
, text
, pos
, tzTimeType
);
3583 cal
.adoptTimeZone(tz
);
3584 return pos
.getIndex();
3588 case UDAT_TIMEZONE_ISO_LOCAL_FIELD
: // 'x'
3590 UTimeZoneFormatStyle style
;
3593 style
= UTZFMT_STYLE_ISO_BASIC_LOCAL_SHORT
;
3596 style
= UTZFMT_STYLE_ISO_BASIC_LOCAL_FIXED
;
3599 style
= UTZFMT_STYLE_ISO_EXTENDED_LOCAL_FIXED
;
3602 style
= UTZFMT_STYLE_ISO_BASIC_LOCAL_FULL
;
3605 style
= UTZFMT_STYLE_ISO_EXTENDED_LOCAL_FULL
;
3608 TimeZone
*tz
= tzFormat()->parse(style
, text
, pos
, tzTimeType
);
3610 cal
.adoptTimeZone(tz
);
3611 return pos
.getIndex();
3615 // currently no pattern character is defined for UDAT_TIME_SEPARATOR_FIELD
3616 // so we should not get here. Leave support in for future definition.
3617 case UDAT_TIME_SEPARATOR_FIELD
:
3619 static const UChar def_sep
= DateFormatSymbols::DEFAULT_TIME_SEPARATOR
;
3620 static const UChar alt_sep
= DateFormatSymbols::ALTERNATE_TIME_SEPARATOR
;
3622 // Try matching a time separator.
3624 UnicodeString data
[3];
3625 fSymbols
->getTimeSeparatorString(data
[0]);
3627 // Add the default, if different from the locale.
3628 if (data
[0].compare(&def_sep
, 1) != 0) {
3629 data
[count
++].setTo(def_sep
);
3632 // If lenient, add also the alternate, if different from the locale.
3633 if (isLenient() && data
[0].compare(&alt_sep
, 1) != 0) {
3634 data
[count
++].setTo(alt_sep
);
3637 return matchString(text
, start
, UCAL_FIELD_COUNT
/* => nothing to set */, data
, count
, NULL
, cal
);
3640 case UDAT_AM_PM_MIDNIGHT_NOON_FIELD
:
3642 U_ASSERT(dayPeriod
!= NULL
);
3643 int32_t ampmStart
= subParse(text
, start
, 0x61, count
,
3644 obeyCount
, allowNegative
, ambiguousYear
, saveHebrewMonth
, cal
,
3645 patLoc
, numericLeapMonthFormatter
, tzTimeType
, mutableNFs
);
3647 if (ampmStart
> 0) {
3650 int32_t newStart
= 0;
3652 // Only match the first two strings from the day period strings array.
3653 if (getBooleanAttribute(UDAT_PARSE_MULTIPLE_PATTERNS_FOR_MATCH
, status
) || count
== 3) {
3654 if ((newStart
= matchDayPeriodStrings(text
, start
, fSymbols
->fAbbreviatedDayPeriods
,
3655 2, *dayPeriod
)) > 0) {
3659 if (getBooleanAttribute(UDAT_PARSE_MULTIPLE_PATTERNS_FOR_MATCH
, status
) || count
== 5) {
3660 if ((newStart
= matchDayPeriodStrings(text
, start
, fSymbols
->fNarrowDayPeriods
,
3661 2, *dayPeriod
)) > 0) {
3665 // count == 4, but allow other counts
3666 if (getBooleanAttribute(UDAT_PARSE_MULTIPLE_PATTERNS_FOR_MATCH
, status
)) {
3667 if ((newStart
= matchDayPeriodStrings(text
, start
, fSymbols
->fWideDayPeriods
,
3668 2, *dayPeriod
)) > 0) {
3677 case UDAT_FLEXIBLE_DAY_PERIOD_FIELD
:
3679 U_ASSERT(dayPeriod
!= NULL
);
3680 int32_t newStart
= 0;
3682 if (getBooleanAttribute(UDAT_PARSE_MULTIPLE_PATTERNS_FOR_MATCH
, status
) || count
== 3) {
3683 if ((newStart
= matchDayPeriodStrings(text
, start
, fSymbols
->fAbbreviatedDayPeriods
,
3684 fSymbols
->fAbbreviatedDayPeriodsCount
, *dayPeriod
)) > 0) {
3688 if (getBooleanAttribute(UDAT_PARSE_MULTIPLE_PATTERNS_FOR_MATCH
, status
) || count
== 5) {
3689 if ((newStart
= matchDayPeriodStrings(text
, start
, fSymbols
->fNarrowDayPeriods
,
3690 fSymbols
->fNarrowDayPeriodsCount
, *dayPeriod
)) > 0) {
3694 if (getBooleanAttribute(UDAT_PARSE_MULTIPLE_PATTERNS_FOR_MATCH
, status
) || count
== 4) {
3695 if ((newStart
= matchDayPeriodStrings(text
, start
, fSymbols
->fWideDayPeriods
,
3696 fSymbols
->fWideDayPeriodsCount
, *dayPeriod
)) > 0) {
3705 // Handle "generic" fields
3706 // this is now handled below, outside the switch block
3709 // Handle "generic" fields:
3710 // switch default case now handled here (outside switch block) to allow
3711 // parsing of some string fields as digits for lenient case
3713 int32_t parseStart
= pos
.getIndex();
3714 const UnicodeString
* src
;
3716 if ((start
+count
) > text
.length()) {
3719 text
.extractBetween(0, start
+ count
, temp
);
3724 parseInt(*src
, number
, pos
, allowNegative
,currentNumberFormat
);
3725 if (pos
.getIndex() != parseStart
) {
3726 int32_t value
= number
.getLong();
3728 // Don't need suffix processing here (as in number processing at the beginning of the function);
3729 // the new fields being handled as numeric values (month, weekdays, quarters) should not have suffixes.
3731 // Check the range of the value
3732 if (!getBooleanAttribute(UDAT_PARSE_ALLOW_NUMERIC
, status
)) {
3733 int32_t bias
= gFieldRangeBias
[patternCharIndex
];
3734 if (bias
>= 0 && (value
> cal
.getMaximum(field
) + bias
|| value
< cal
.getMinimum(field
) + bias
)) {
3738 int32_t bias
= gFieldRangeBiasLenient
[patternCharIndex
];
3739 if (bias
>= 0 && (value
> cal
.getMaximum(field
) + bias
)) {
3744 // For the following, need to repeat some of the "if (gotNumber)" code above:
3745 // UDAT_[STANDALONE_]MONTH_FIELD, UDAT_DOW_LOCAL_FIELD, UDAT_STANDALONE_DAY_FIELD,
3746 // UDAT_[STANDALONE_]QUARTER_FIELD
3747 switch (patternCharIndex
) {
3748 case UDAT_MONTH_FIELD
:
3749 // See notes under UDAT_MONTH_FIELD case above
3750 if (!strcmp(cal
.getType(),"hebrew")) {
3751 HebrewCalendar
*hc
= (HebrewCalendar
*)&cal
;
3752 if (cal
.isSet(UCAL_YEAR
)) {
3753 UErrorCode status
= U_ZERO_ERROR
;
3754 if (!hc
->isLeapYear(hc
->get(UCAL_YEAR
,status
)) && value
>= 6) {
3755 cal
.set(UCAL_MONTH
, value
);
3757 cal
.set(UCAL_MONTH
, value
- 1);
3760 saveHebrewMonth
= value
;
3763 cal
.set(UCAL_MONTH
, value
- 1);
3766 case UDAT_STANDALONE_MONTH_FIELD
:
3767 cal
.set(UCAL_MONTH
, value
- 1);
3769 case UDAT_DOW_LOCAL_FIELD
:
3770 case UDAT_STANDALONE_DAY_FIELD
:
3771 cal
.set(UCAL_DOW_LOCAL
, value
);
3773 case UDAT_QUARTER_FIELD
:
3774 case UDAT_STANDALONE_QUARTER_FIELD
:
3775 cal
.set(UCAL_MONTH
, (value
- 1) * 3);
3777 case UDAT_RELATED_YEAR_FIELD
:
3778 cal
.setRelatedYear(value
);
3781 cal
.set(field
, value
);
3784 return pos
.getIndex();
3790 * Parse an integer using fNumberFormat. This method is semantically
3791 * const, but actually may modify fNumberFormat.
3793 void SimpleDateFormat::parseInt(const UnicodeString
& text
,
3794 Formattable
& number
,
3796 UBool allowNegative
,
3797 NumberFormat
*fmt
) const {
3798 parseInt(text
, number
, -1, pos
, allowNegative
,fmt
);
3802 * Parse an integer using fNumberFormat up to maxDigits.
3804 void SimpleDateFormat::parseInt(const UnicodeString
& text
,
3805 Formattable
& number
,
3808 UBool allowNegative
,
3809 NumberFormat
*fmt
) const {
3810 UnicodeString oldPrefix
;
3811 DecimalFormat
* df
= NULL
;
3812 if (!allowNegative
&& (df
= dynamic_cast<DecimalFormat
*>(fmt
)) != NULL
) {
3813 df
->getNegativePrefix(oldPrefix
);
3814 df
->setNegativePrefix(UnicodeString(TRUE
, SUPPRESS_NEGATIVE_PREFIX
, -1));
3816 int32_t oldPos
= pos
.getIndex();
3817 fmt
->parse(text
, number
, pos
);
3819 df
->setNegativePrefix(oldPrefix
);
3822 if (maxDigits
> 0) {
3823 // adjust the result to fit into
3824 // the maxDigits and move the position back
3825 int32_t nDigits
= pos
.getIndex() - oldPos
;
3826 if (nDigits
> maxDigits
) {
3827 int32_t val
= number
.getLong();
3828 nDigits
-= maxDigits
;
3829 while (nDigits
> 0) {
3833 pos
.setIndex(oldPos
+ maxDigits
);
3834 number
.setLong(val
);
3839 int32_t SimpleDateFormat::countDigits(const UnicodeString
& text
, int32_t start
, int32_t end
) const {
3840 int32_t numDigits
= 0;
3841 int32_t idx
= start
;
3843 UChar32 cp
= text
.char32At(idx
);
3844 if (u_isdigit(cp
)) {
3847 idx
+= U16_LENGTH(cp
);
3852 //----------------------------------------------------------------------
3854 void SimpleDateFormat::translatePattern(const UnicodeString
& originalPattern
,
3855 UnicodeString
& translatedPattern
,
3856 const UnicodeString
& from
,
3857 const UnicodeString
& to
,
3860 // run through the pattern and convert any pattern symbols from the version
3861 // in "from" to the corresponding character in "to". This code takes
3862 // quoted strings into account (it doesn't try to translate them), and it signals
3863 // an error if a particular "pattern character" doesn't appear in "from".
3864 // Depending on the values of "from" and "to" this can convert from generic
3865 // to localized patterns or localized to generic.
3866 if (U_FAILURE(status
)) {
3870 translatedPattern
.remove();
3871 UBool inQuote
= FALSE
;
3872 for (int32_t i
= 0; i
< originalPattern
.length(); ++i
) {
3873 UChar c
= originalPattern
[i
];
3881 } else if (isSyntaxChar(c
)) {
3882 int32_t ci
= from
.indexOf(c
);
3884 status
= U_INVALID_FORMAT_ERROR
;
3890 translatedPattern
+= c
;
3893 status
= U_INVALID_FORMAT_ERROR
;
3898 //----------------------------------------------------------------------
3901 SimpleDateFormat::toPattern(UnicodeString
& result
) const
3907 //----------------------------------------------------------------------
3910 SimpleDateFormat::toLocalizedPattern(UnicodeString
& result
,
3911 UErrorCode
& status
) const
3913 translatePattern(fPattern
, result
,
3914 UnicodeString(DateFormatSymbols::getPatternUChars()),
3915 fSymbols
->fLocalPatternChars
, status
);
3919 //----------------------------------------------------------------------
3922 SimpleDateFormat::applyPattern(const UnicodeString
& pattern
)
3928 //----------------------------------------------------------------------
3931 SimpleDateFormat::applyLocalizedPattern(const UnicodeString
& pattern
,
3934 translatePattern(pattern
, fPattern
,
3935 fSymbols
->fLocalPatternChars
,
3936 UnicodeString(DateFormatSymbols::getPatternUChars()), status
);
3939 //----------------------------------------------------------------------
3941 const DateFormatSymbols
*
3942 SimpleDateFormat::getDateFormatSymbols() const
3947 //----------------------------------------------------------------------
3950 SimpleDateFormat::adoptDateFormatSymbols(DateFormatSymbols
* newFormatSymbols
)
3953 fSymbols
= newFormatSymbols
;
3956 //----------------------------------------------------------------------
3958 SimpleDateFormat::setDateFormatSymbols(const DateFormatSymbols
& newFormatSymbols
)
3961 fSymbols
= new DateFormatSymbols(newFormatSymbols
);
3964 //----------------------------------------------------------------------
3965 const TimeZoneFormat
*
3966 SimpleDateFormat::getTimeZoneFormat(void) const {
3967 return (const TimeZoneFormat
*)tzFormat();
3970 //----------------------------------------------------------------------
3972 SimpleDateFormat::adoptTimeZoneFormat(TimeZoneFormat
* timeZoneFormatToAdopt
)
3974 delete fTimeZoneFormat
;
3975 fTimeZoneFormat
= timeZoneFormatToAdopt
;
3978 //----------------------------------------------------------------------
3980 SimpleDateFormat::setTimeZoneFormat(const TimeZoneFormat
& newTimeZoneFormat
)
3982 delete fTimeZoneFormat
;
3983 fTimeZoneFormat
= new TimeZoneFormat(newTimeZoneFormat
);
3986 //----------------------------------------------------------------------
3989 void SimpleDateFormat::adoptCalendar(Calendar
* calendarToAdopt
)
3991 UErrorCode status
= U_ZERO_ERROR
;
3992 Locale
calLocale(fLocale
);
3993 DateFormatSymbols
*newSymbols
= fSymbols
;
3994 if (!newSymbols
|| fCalendar
->getType() != calendarToAdopt
->getType()) {
3995 calLocale
.setKeywordValue("calendar", calendarToAdopt
->getType(), status
);
3996 newSymbols
= DateFormatSymbols::createForLocale(calLocale
, status
);
3997 if (U_FAILURE(status
)) {
4001 DateFormat::adoptCalendar(calendarToAdopt
);
4002 if (fSymbols
!= newSymbols
) {
4004 fSymbols
= newSymbols
;
4006 initializeDefaultCentury(); // we need a new century (possibly)
4010 //----------------------------------------------------------------------
4013 // override the DateFormat implementation in order to
4014 // lazily initialize fCapitalizationBrkIter
4016 SimpleDateFormat::setContext(UDisplayContext value
, UErrorCode
& status
)
4018 DateFormat::setContext(value
, status
);
4019 #if !UCONFIG_NO_BREAK_ITERATION
4020 if (U_SUCCESS(status
)) {
4021 if ( fCapitalizationBrkIter
== NULL
&& (value
==UDISPCTX_CAPITALIZATION_FOR_BEGINNING_OF_SENTENCE
||
4022 value
==UDISPCTX_CAPITALIZATION_FOR_UI_LIST_OR_MENU
|| value
==UDISPCTX_CAPITALIZATION_FOR_STANDALONE
) ) {
4023 UErrorCode status
= U_ZERO_ERROR
;
4024 fCapitalizationBrkIter
= BreakIterator::createSentenceInstance(fLocale
, status
);
4025 if (U_FAILURE(status
)) {
4026 delete fCapitalizationBrkIter
;
4027 fCapitalizationBrkIter
= NULL
;
4035 //----------------------------------------------------------------------
4039 SimpleDateFormat::isFieldUnitIgnored(UCalendarDateFields field
) const {
4040 return isFieldUnitIgnored(fPattern
, field
);
4045 SimpleDateFormat::isFieldUnitIgnored(const UnicodeString
& pattern
,
4046 UCalendarDateFields field
) {
4047 int32_t fieldLevel
= fgCalendarFieldToLevel
[field
];
4050 UBool inQuote
= FALSE
;
4054 for (int32_t i
= 0; i
< pattern
.length(); ++i
) {
4056 if (ch
!= prevCh
&& count
> 0) {
4057 level
= getLevelFromChar(prevCh
);
4058 // the larger the level, the smaller the field unit.
4059 if (fieldLevel
<= level
) {
4065 if ((i
+1) < pattern
.length() && pattern
[i
+1] == QUOTE
) {
4068 inQuote
= ! inQuote
;
4071 else if (!inQuote
&& isSyntaxChar(ch
)) {
4078 level
= getLevelFromChar(prevCh
);
4079 if (fieldLevel
<= level
) {
4086 //----------------------------------------------------------------------
4089 SimpleDateFormat::getSmpFmtLocale(void) const {
4093 //----------------------------------------------------------------------
4096 SimpleDateFormat::checkIntSuffix(const UnicodeString
& text
, int32_t start
,
4097 int32_t patLoc
, UBool isNegative
) const {
4100 int32_t patternMatch
;
4101 int32_t textPreMatch
;
4102 int32_t textPostMatch
;
4104 // check that we are still in range
4105 if ( (start
> text
.length()) ||
4108 (patLoc
> fPattern
.length())) {
4109 // out of range, don't advance location in text
4114 DecimalFormat
* decfmt
= dynamic_cast<DecimalFormat
*>(fNumberFormat
);
4115 if (decfmt
!= NULL
) {
4117 suf
= decfmt
->getNegativeSuffix(suf
);
4120 suf
= decfmt
->getPositiveSuffix(suf
);
4125 if (suf
.length() <= 0) {
4129 // check suffix will be encountered in the pattern
4130 patternMatch
= compareSimpleAffix(suf
,fPattern
,patLoc
);
4132 // check if a suffix will be encountered in the text
4133 textPreMatch
= compareSimpleAffix(suf
,text
,start
);
4135 // check if a suffix was encountered in the text
4136 textPostMatch
= compareSimpleAffix(suf
,text
,start
-suf
.length());
4138 // check for suffix match
4139 if ((textPreMatch
>= 0) && (patternMatch
>= 0) && (textPreMatch
== patternMatch
)) {
4142 else if ((textPostMatch
>= 0) && (patternMatch
>= 0) && (textPostMatch
== patternMatch
)) {
4143 return start
- suf
.length();
4146 // should not get here
4150 //----------------------------------------------------------------------
4153 SimpleDateFormat::compareSimpleAffix(const UnicodeString
& affix
,
4154 const UnicodeString
& input
,
4155 int32_t pos
) const {
4156 int32_t start
= pos
;
4157 for (int32_t i
=0; i
<affix
.length(); ) {
4158 UChar32 c
= affix
.char32At(i
);
4159 int32_t len
= U16_LENGTH(c
);
4160 if (PatternProps::isWhiteSpace(c
)) {
4161 // We may have a pattern like: \u200F \u0020
4162 // and input text like: \u200F \u0020
4163 // Note that U+200F and U+0020 are Pattern_White_Space but only
4164 // U+0020 is UWhiteSpace. So we have to first do a direct
4165 // match of the run of Pattern_White_Space in the pattern,
4166 // then match any extra characters.
4167 UBool literalMatch
= FALSE
;
4168 while (pos
< input
.length() &&
4169 input
.char32At(pos
) == c
) {
4170 literalMatch
= TRUE
;
4173 if (i
== affix
.length()) {
4176 c
= affix
.char32At(i
);
4177 len
= U16_LENGTH(c
);
4178 if (!PatternProps::isWhiteSpace(c
)) {
4183 // Advance over run in pattern
4184 i
= skipPatternWhiteSpace(affix
, i
);
4186 // Advance over run in input text
4187 // Must see at least one white space char in input,
4188 // unless we've already matched some characters literally.
4190 pos
= skipUWhiteSpace(input
, pos
);
4191 if (pos
== s
&& !literalMatch
) {
4195 // If we skip UWhiteSpace in the input text, we need to skip it in the pattern.
4196 // Otherwise, the previous lines may have skipped over text (such as U+00A0) that
4197 // is also in the affix.
4198 i
= skipUWhiteSpace(affix
, i
);
4200 if (pos
< input
.length() &&
4201 input
.char32At(pos
) == c
) {
4212 //----------------------------------------------------------------------
4215 SimpleDateFormat::skipPatternWhiteSpace(const UnicodeString
& text
, int32_t pos
) const {
4216 const UChar
* s
= text
.getBuffer();
4217 return (int32_t)(PatternProps::skipWhiteSpace(s
+ pos
, text
.length() - pos
) - s
);
4220 //----------------------------------------------------------------------
4223 SimpleDateFormat::skipUWhiteSpace(const UnicodeString
& text
, int32_t pos
) const {
4224 while (pos
< text
.length()) {
4225 UChar32 c
= text
.char32At(pos
);
4226 if (!u_isUWhiteSpace(c
)) {
4229 pos
+= U16_LENGTH(c
);
4234 //----------------------------------------------------------------------
4236 // Lazy TimeZoneFormat instantiation, semantically const.
4238 SimpleDateFormat::tzFormat() const {
4239 if (fTimeZoneFormat
== NULL
) {
4242 if (fTimeZoneFormat
== NULL
) {
4243 UErrorCode status
= U_ZERO_ERROR
;
4244 TimeZoneFormat
*tzfmt
= TimeZoneFormat::createInstance(fLocale
, status
);
4245 if (U_FAILURE(status
)) {
4249 const_cast<SimpleDateFormat
*>(this)->fTimeZoneFormat
= tzfmt
;
4254 return fTimeZoneFormat
;
4257 void SimpleDateFormat::parsePattern() {
4261 int len
= fPattern
.length();
4262 UBool inQuote
= FALSE
;
4263 for (int32_t i
= 0; i
< len
; ++i
) {
4264 UChar ch
= fPattern
[i
];
4269 if (ch
== 0x6D) { // 0x6D == 'm'
4272 if (ch
== 0x73) { // 0x73 == 's'
4281 #endif /* #if !UCONFIG_NO_FORMATTING */