3 *******************************************************************************
4 * Copyright (C) 1996-2009, International Business Machines Corporation and *
5 * others. All Rights Reserved. *
6 *******************************************************************************
9 package com.ibm.icu.text;
11 import java.io.IOException;
12 import java.io.ObjectInputStream;
13 import java.io.ObjectOutputStream;
14 import java.lang.ref.WeakReference;
15 import java.lang.Character;
16 import java.text.FieldPosition;
17 import java.text.ParsePosition;
18 import java.util.ArrayList;
19 import java.util.Date;
20 import java.util.HashMap;
21 import java.util.List;
22 import java.util.Locale;
23 import java.util.MissingResourceException;
24 //#if defined(FOUNDATION10) || defined(J2SE13)
26 import java.text.AttributedCharacterIterator;
27 import java.text.AttributedString;
28 import java.text.Format;
29 import java.util.LinkedList;
32 import com.ibm.icu.impl.CalendarData;
33 import com.ibm.icu.impl.DateNumberFormat;
34 import com.ibm.icu.impl.ICUCache;
35 import com.ibm.icu.impl.SimpleCache;
36 import com.ibm.icu.impl.UCharacterProperty;
37 import com.ibm.icu.impl.ZoneMeta;
38 import com.ibm.icu.impl.ZoneStringFormat.ZoneStringInfo;
39 import com.ibm.icu.lang.UCharacter;
40 import com.ibm.icu.util.BasicTimeZone;
41 import com.ibm.icu.util.Calendar;
42 import com.ibm.icu.util.GregorianCalendar;
43 import com.ibm.icu.util.TimeZone;
44 import com.ibm.icu.util.TimeZoneTransition;
45 import com.ibm.icu.util.ULocale;
49 * <code>SimpleDateFormat</code> is a concrete class for formatting and
50 * parsing dates in a locale-sensitive manner. It allows for formatting
51 * (date -> text), parsing (text -> date), and normalization.
54 * <code>SimpleDateFormat</code> allows you to start by choosing
55 * any user-defined patterns for date-time formatting. However, you
56 * are encouraged to create a date-time formatter with either
57 * <code>getTimeInstance</code>, <code>getDateInstance</code>, or
58 * <code>getDateTimeInstance</code> in <code>DateFormat</code>. Each
59 * of these class methods can return a date/time formatter initialized
60 * with a default format pattern. You may modify the format pattern
61 * using the <code>applyPattern</code> methods as desired.
62 * For more information on using these methods, see
66 * <strong>Time Format Syntax:</strong>
68 * To specify the time format use a <em>time pattern</em> string.
69 * In this pattern, all ASCII letters are reserved as pattern letters,
70 * which are defined as the following:
73 * Symbol Meaning Presentation Example
74 * ------ ------- ------------ -------
75 * G era designator (Text) AD
76 * y† year (Number) 1996
77 * Y* year (week of year) (Number) 1997
78 * u* extended year (Number) 4601
79 * M month in year (Text & Number) July & 07
80 * d day in month (Number) 10
81 * h hour in am/pm (1~12) (Number) 12
82 * H hour in day (0~23) (Number) 0
83 * m minute in hour (Number) 30
84 * s second in minute (Number) 55
85 * S fractional second (Number) 978
86 * E day of week (Text) Tuesday
87 * e* day of week (local 1~7) (Text & Number) Tuesday & 2
88 * D day in year (Number) 189
89 * F day of week in month (Number) 2 (2nd Wed in July)
90 * w week in year (Number) 27
91 * W week in month (Number) 2
92 * a am/pm marker (Text) PM
93 * k hour in day (1~24) (Number) 24
94 * K hour in am/pm (0~11) (Number) 0
95 * z time zone (Text) Pacific Standard Time
96 * Z time zone (RFC 822) (Number) -0800
97 * v time zone (generic) (Text) Pacific Time
98 * V time zone (location) (Text) United States (Los Angeles)
99 * g* Julian day (Number) 2451334
100 * A* milliseconds in day (Number) 69540000
101 * Q* quarter in year (Text & Number) Q1 & 01
102 * c* stand alone day of week (Text & Number) Tuesday & 2
103 * L* stand alone month (Text & Number) July & 07
104 * q* stand alone quarter (Text & Number) Q1 & 01
105 * ' escape for text (Delimiter) 'Date='
106 * '' single quote (Literal) 'o''clock'
109 * <tt><b>*</b></tt> These items are not supported by Java's SimpleDateFormat.<br>
110 * <tt><b>†</b></tt> ICU interprets a single 'y' differently than Java.</p>
112 * The count of pattern letters determine the format.
114 * <strong>(Text)</strong>: 4 or more pattern letters--use full form,
115 * < 4--use short or abbreviated form if one exists.
117 * <strong>(Number)</strong>: the minimum number of digits. Shorter
118 * numbers are zero-padded to this amount. Year is handled specially;
119 * that is, if the count of 'y' is 2, the Year will be truncated to 2 digits.
120 * (e.g., if "yyyy" produces "1997", "yy" produces "97".)
121 * Unlike other fields, fractional seconds are padded on the right with zero.
123 * <strong>(Text & Number)</strong>: 3 or over, use text, otherwise use number.
125 * Any characters in the pattern that are not in the ranges of ['a'..'z']
126 * and ['A'..'Z'] will be treated as quoted text. For instance, characters
127 * like ':', '.', ' ', '#' and '@' will appear in the resulting time text
128 * even they are not embraced within single quotes.
130 * A pattern containing any invalid pattern letter will result in a thrown
131 * exception during formatting or parsing.
134 * <strong>Examples Using the US Locale:</strong>
137 * Format Pattern Result
138 * -------------- -------
139 * "yyyy.MM.dd G 'at' HH:mm:ss vvvv" ->> 1996.07.10 AD at 15:08:56 Pacific Time
140 * "EEE, MMM d, ''yy" ->> Wed, July 10, '96
141 * "h:mm a" ->> 12:08 PM
142 * "hh 'o''clock' a, zzzz" ->> 12 o'clock PM, Pacific Daylight Time
143 * "K:mm a, vvv" ->> 0:00 PM, PT
144 * "yyyyy.MMMMM.dd GGG hh:mm aaa" ->> 01996.July.10 AD 12:08 PM
147 * <strong>Code Sample:</strong>
150 * SimpleTimeZone pdt = new SimpleTimeZone(-8 * 60 * 60 * 1000, "PST");
151 * pdt.setStartRule(Calendar.APRIL, 1, Calendar.SUNDAY, 2*60*60*1000);
152 * pdt.setEndRule(Calendar.OCTOBER, -1, Calendar.SUNDAY, 2*60*60*1000);
154 * // Format the current time.
155 * SimpleDateFormat formatter
156 * = new SimpleDateFormat ("yyyy.MM.dd G 'at' hh:mm:ss a zzz");
157 * Date currentTime_1 = new Date();
158 * String dateString = formatter.format(currentTime_1);
160 * // Parse the previous string back into a Date.
161 * ParsePosition pos = new ParsePosition(0);
162 * Date currentTime_2 = formatter.parse(dateString, pos);
165 * In the example, the time value <code>currentTime_2</code> obtained from
166 * parsing will be equal to <code>currentTime_1</code>. However, they may not be
167 * equal if the am/pm marker 'a' is left out from the format pattern while
168 * the "hour in am/pm" pattern symbol is used. This information loss can
169 * happen when formatting the time in PM.
172 * When parsing a date string using the abbreviated year pattern ("yy"),
173 * SimpleDateFormat must interpret the abbreviated year
174 * relative to some century. It does this by adjusting dates to be
175 * within 80 years before and 20 years after the time the SimpleDateFormat
176 * instance is created. For example, using a pattern of "MM/dd/yy" and a
177 * SimpleDateFormat instance created on Jan 1, 1997, the string
178 * "01/11/12" would be interpreted as Jan 11, 2012 while the string "05/04/64"
179 * would be interpreted as May 4, 1964.
180 * During parsing, only strings consisting of exactly two digits, as defined by
181 * {@link com.ibm.icu.lang.UCharacter#isDigit(int)}, will be parsed into the default
183 * Any other numeric string, such as a one digit string, a three or more digit
184 * string, or a two digit string that isn't all digits (for example, "-1"), is
185 * interpreted literally. So "01/02/3" or "01/02/003" are parsed, using the
186 * same pattern, as Jan 2, 3 AD. Likewise, "01/02/-3" is parsed as Jan 2, 4 BC.
189 * If the year pattern does not have exactly two 'y' characters, the year is
190 * interpreted literally, regardless of the number of digits. So using the
191 * pattern "MM/dd/yyyy", "01/11/12" parses to Jan 11, 12 A.D.
194 * When numeric fields abut one another directly, with no intervening delimiter
195 * characters, they constitute a run of abutting numeric fields. Such runs are
196 * parsed specially. For example, the format "HHmmss" parses the input text
197 * "123456" to 12:34:56, parses the input text "12345" to 1:23:45, and fails to
198 * parse "1234". In other words, the leftmost field of the run is flexible,
199 * while the others keep a fixed width. If the parse fails anywhere in the run,
200 * then the leftmost field is shortened by one character, and the entire run is
201 * parsed again. This is repeated until either the parse succeeds or the
202 * leftmost field is one character in length. If the parse still fails at that
203 * point, the parse of the run fails.
206 * For time zones that have no names, use strings GMT+hours:minutes or
210 * The calendar defines what is the first day of the week, the first week
211 * of the year, whether hours are zero based or not (0 vs 12 or 24), and the
212 * time zone. There is one common decimal format to handle all the numbers;
213 * the digit count is handled programmatically according to the pattern.
215 * <h4>Synchronization</h4>
217 * Date formats are not synchronized. It is recommended to create separate
218 * format instances for each thread. If multiple threads access a format
219 * concurrently, it must be synchronized externally.
221 * @see com.ibm.icu.util.Calendar
222 * @see com.ibm.icu.util.GregorianCalendar
223 * @see com.ibm.icu.util.TimeZone
225 * @see DateFormatSymbols
227 * @author Mark Davis, Chen-Lieh Huang, Alan Liu
230 public class SimpleDateFormat extends DateFormat {
232 // the official serial version ID which says cryptically
233 // which version we're compatible with
234 private static final long serialVersionUID = 4774881970558875024L;
236 // the internal serial version which says which version was written
237 // - 0 (default) for version up to JDK 1.1.3
238 // - 1 for version from JDK 1.1.4, which includes a new field
239 static final int currentSerialVersion = 1;
243 * From calendar field to its level.
244 * Used to order calendar field.
245 * For example, calendar fields can be defined in the following order:
246 * year > month > date > am-pm > hour > minute
247 * YEAR --> 10, MONTH -->20, DATE --> 30;
248 * AM_PM -->40, HOUR --> 50, MINUTE -->60
250 private static final int[] CALENDAR_FIELD_TO_LEVEL =
254 /*dDEF*/ 30, 20, 30, 30,
255 /*ahHm*/ 40, 50, 50, 60,
265 * From calendar field letter to its level.
266 * Used to order calendar field.
267 * For example, calendar fields can be defined in the following order:
268 * year > month > date > am-pm > hour > minute
269 * 'y' --> 10, 'M' -->20, 'd' --> 30; 'a' -->40, 'h' --> 50, 'm' -->60
271 private static final int[] PATTERN_CHAR_TO_LEVEL =
273 // A B C D E F G H I J K L M N O
274 -1, 40, -1, -1, 20, 30, 30, 0, 50, -1, -1, 50, 20, 20, -1, -1,
275 // P Q R S T U V W X Y Z
276 -1, 20, -1, 80, -1, -1, 0, 30, -1, 10, 0, -1, -1, -1, -1, -1,
277 // a b c d e f g h i j k l m n o
278 -1, 40, -1, 30, 30, 30, -1, 0, 50, -1, -1, 50, -1, 60, -1, -1,
279 // p q r s t u v w x y z
280 -1, 20, -1, 70, -1, 10, 0, 20, -1, 10, 0, -1, -1, -1, -1, -1
285 * The version of the serialized data on the stream. Possible values:
287 * <li><b>0</b> or not present on stream: JDK 1.1.3. This version
288 * has no <code>defaultCenturyStart</code> on stream.
289 * <li><b>1</b> JDK 1.1.4 or later. This version adds
290 * <code>defaultCenturyStart</code>.
292 * When streaming out this class, the most recent format
293 * and the highest allowable <code>serialVersionOnStream</code>
297 private int serialVersionOnStream = currentSerialVersion;
300 * The pattern string of this formatter. This is always a non-localized
301 * pattern. May not be null. See class documentation for details.
304 private String pattern;
307 * The override string of this formatter. Used to override the
308 * numbering system for one or more fields.
311 private String override;
314 * The hash map used for number format overrides.
317 private HashMap numberFormatters;
320 * The hash map used for number format overrides.
323 private HashMap overrideMap;
326 * The symbols used by this formatter for week names, month names,
327 * etc. May not be null.
329 * @see DateFormatSymbols
331 private DateFormatSymbols formatData;
333 private transient ULocale locale;
336 * We map dates with two-digit years into the century starting at
337 * <code>defaultCenturyStart</code>, which may be any date. May
342 private Date defaultCenturyStart;
344 private transient int defaultCenturyStartYear;
346 // defaultCenturyBase is set when an instance is created
347 // and may be used for calculating defaultCenturyStart when needed.
348 private transient long defaultCenturyBase;
350 // We need to preserve time zone type when parsing specific
351 // time zone text (xxx Standard Time vs xxx Daylight Time)
352 private static final int TZTYPE_UNK = 0, TZTYPE_STD = 1, TZTYPE_DST = 2;
353 private transient int tztype = TZTYPE_UNK;
355 private static final int millisPerHour = 60 * 60 * 1000;
356 private static final int millisPerMinute = 60 * 1000;
357 private static final int millisPerSecond = 1000;
359 // This prefix is designed to NEVER MATCH real text, in order to
360 // suppress the parsing of negative numbers. Adjust as needed (if
361 // this becomes valid Unicode).
362 private static final String SUPPRESS_NEGATIVE_PREFIX = "\uAB00";
365 * If true, this object supports fast formatting using the
366 * subFormat variant that takes a StringBuffer.
368 private transient boolean useFastFormat;
371 * Construct a SimpleDateFormat using the default pattern for the default
372 * locale. <b>Note:</b> Not all locales support SimpleDateFormat; for full
373 * generality, use the factory methods in the DateFormat class.
378 public SimpleDateFormat() {
379 this(getDefaultPattern(), null, null, null, null, true, null);
383 * Construct a SimpleDateFormat using the given pattern in the default
384 * locale. <b>Note:</b> Not all locales support SimpleDateFormat; for full
385 * generality, use the factory methods in the DateFormat class.
388 public SimpleDateFormat(String pattern)
390 this(pattern, null, null, null, null, true, null);
394 * Construct a SimpleDateFormat using the given pattern and locale.
395 * <b>Note:</b> Not all locales support SimpleDateFormat; for full
396 * generality, use the factory methods in the DateFormat class.
399 public SimpleDateFormat(String pattern, Locale loc)
401 this(pattern, null, null, null, ULocale.forLocale(loc), true, null);
405 * Construct a SimpleDateFormat using the given pattern and locale.
406 * <b>Note:</b> Not all locales support SimpleDateFormat; for full
407 * generality, use the factory methods in the DateFormat class.
410 public SimpleDateFormat(String pattern, ULocale loc)
412 this(pattern, null, null, null, loc, true, null);
416 * Construct a SimpleDateFormat using the given pattern , override and locale.
418 * @provisional This API might change or be removed in a future release.
420 public SimpleDateFormat(String pattern, String override, ULocale loc)
422 this(pattern, null, null, null, loc, false,override);
426 * Construct a SimpleDateFormat using the given pattern and
427 * locale-specific symbol data.
428 * Warning: uses default locale for digits!
431 public SimpleDateFormat(String pattern, DateFormatSymbols formatData)
433 this(pattern, (DateFormatSymbols)formatData.clone(), null, null, null, true, null);
438 * @deprecated This API is ICU internal only.
440 public SimpleDateFormat(String pattern, DateFormatSymbols formatData, ULocale loc)
442 this(pattern, (DateFormatSymbols)formatData.clone(), null, null, loc, true,null);
446 * Package-private constructor that allows a subclass to specify
447 * whether it supports fast formatting.
449 * TODO make this API public.
451 SimpleDateFormat(String pattern, DateFormatSymbols formatData, Calendar calendar, ULocale locale,
452 boolean useFastFormat, String override) {
453 this(pattern, (DateFormatSymbols)formatData.clone(), (Calendar)calendar.clone(), null, locale, useFastFormat,override);
456 SimpleDateFormat(String pattern, DateFormatSymbols formatData, Calendar calendar, ULocale locale,
457 boolean useFastFormat) {
458 this(pattern, (DateFormatSymbols)formatData.clone(), (Calendar)calendar.clone(), null, locale, useFastFormat,null);
462 * The constructor called from all other SimpleDateFormat constructors
464 private SimpleDateFormat(String pattern, DateFormatSymbols formatData, Calendar calendar,
465 NumberFormat numberFormat, ULocale locale, boolean useFastFormat,String override) {
466 this.pattern = pattern;
467 this.formatData = formatData;
468 this.calendar = calendar;
469 this.numberFormat = numberFormat;
470 this.locale = locale; // time zone formatting
471 this.useFastFormat = useFastFormat;
472 this.override = override;
477 * Create an instance of SimpleDateForamt for the given format configuration
478 * @param formatConfig the format configuration
479 * @return A SimpleDateFormat instance
481 * @deprecated This API is ICU internal only.
483 public static SimpleDateFormat getInstance(Calendar.FormatConfiguration formatConfig) {
485 String ostr = formatConfig.getOverrideString();
486 boolean useFast = ( ostr != null && ostr.length() > 0 );
488 return new SimpleDateFormat(formatConfig.getPatternString(),
489 formatConfig.getDateFormatSymbols(),
490 formatConfig.getCalendar(),
492 formatConfig.getLocale(),
494 formatConfig.getOverrideString());
500 private void initialize() {
501 if (locale == null) {
502 locale = ULocale.getDefault();
504 if (formatData == null) {
505 formatData = new DateFormatSymbols(locale);
507 if (calendar == null) {
508 calendar = Calendar.getInstance(locale);
510 if (numberFormat == null) {
511 NumberingSystem ns = NumberingSystem.getInstance(locale);
512 if ( ns.isAlgorithmic() ) {
513 numberFormat = NumberFormat.getInstance(locale);
515 char digit0 = ns.getDescription().charAt(0);
516 // Use a NumberFormat optimized for date formatting
517 numberFormat = new DateNumberFormat(locale, digit0);
520 // Note: deferring calendar calculation until when we really need it.
521 // Instead, we just record time of construction for backward compatibility.
522 defaultCenturyBase = System.currentTimeMillis();
524 setLocale(calendar.getLocale(ULocale.VALID_LOCALE ), calendar.getLocale(ULocale.ACTUAL_LOCALE));
525 initLocalZeroPaddingNumberFormat();
527 if (override != null) {
528 initNumberFormatters(locale);
533 // privates for the default pattern
534 private static ULocale cachedDefaultLocale = null;
535 private static String cachedDefaultPattern = null;
536 private static final String FALLBACKPATTERN = "yy/MM/dd HH:mm";
539 * Returns the default date and time pattern (SHORT) for the default locale.
540 * This method is only used by the default SimpleDateFormat constructor.
542 private static synchronized String getDefaultPattern() {
543 ULocale defaultLocale = ULocale.getDefault();
544 if (!defaultLocale.equals(cachedDefaultLocale)) {
545 cachedDefaultLocale = defaultLocale;
546 Calendar cal = Calendar.getInstance(cachedDefaultLocale);
548 CalendarData calData = new CalendarData(cachedDefaultLocale, cal.getType());
549 String[] dateTimePatterns = calData.getDateTimePatterns();
551 if (dateTimePatterns.length >= 13)
553 glueIndex += (SHORT + 1);
555 cachedDefaultPattern = MessageFormat.format(dateTimePatterns[glueIndex],
556 new Object[] {dateTimePatterns[SHORT], dateTimePatterns[SHORT + 4]});
557 } catch (MissingResourceException e) {
558 cachedDefaultPattern = FALLBACKPATTERN;
561 return cachedDefaultPattern;
564 /* Define one-century window into which to disambiguate dates using
567 private void parseAmbiguousDatesAsAfter(Date startDate) {
568 defaultCenturyStart = startDate;
569 calendar.setTime(startDate);
570 defaultCenturyStartYear = calendar.get(Calendar.YEAR);
573 /* Initialize defaultCenturyStart and defaultCenturyStartYear by base time.
574 * The default start time is 80 years before the creation time of this object.
576 private void initializeDefaultCenturyStart(long baseTime) {
577 defaultCenturyBase = baseTime;
578 // clone to avoid messing up date stored in calendar object
579 // when this method is called while parsing
580 Calendar tmpCal = (Calendar)calendar.clone();
581 tmpCal.setTimeInMillis(baseTime);
582 tmpCal.add(Calendar.YEAR, -80);
583 defaultCenturyStart = tmpCal.getTime();
584 defaultCenturyStartYear = tmpCal.get(Calendar.YEAR);
587 /* Gets the default century start date for this object */
588 private Date getDefaultCenturyStart() {
589 if (defaultCenturyStart == null) {
590 // not yet initialized
591 initializeDefaultCenturyStart(defaultCenturyBase);
593 return defaultCenturyStart;
596 /* Gets the default century start year for this object */
597 private int getDefaultCenturyStartYear() {
598 if (defaultCenturyStart == null) {
599 // not yet initialized
600 initializeDefaultCenturyStart(defaultCenturyBase);
602 return defaultCenturyStartYear;
606 * Sets the 100-year period 2-digit years will be interpreted as being in
607 * to begin on the date the user specifies.
608 * @param startDate During parsing, two digit years will be placed in the range
609 * <code>startDate</code> to <code>startDate + 100 years</code>.
612 public void set2DigitYearStart(Date startDate) {
613 parseAmbiguousDatesAsAfter(startDate);
617 * Returns the beginning date of the 100-year period 2-digit years are interpreted
619 * @return the start of the 100-year period into which two digit years are
623 public Date get2DigitYearStart() {
624 return getDefaultCenturyStart();
628 * Overrides DateFormat.
629 * <p>Formats a date or time, which is the standard millis
630 * since January 1, 1970, 00:00:00 GMT.
631 * <p>Example: using the US locale:
632 * "yyyy.MM.dd G 'at' HH:mm:ss zzz" ->> 1996.07.10 AD at 15:08:56 PDT
633 * @param cal the calendar whose date-time value is to be formatted into a date-time string
634 * @param toAppendTo where the new date-time text is to be appended
635 * @param pos the formatting position. On input: an alignment field,
636 * if desired. On output: the offsets of the alignment field.
637 * @return the formatted date-time string.
641 public StringBuffer format(Calendar cal, StringBuffer toAppendTo,
643 TimeZone backupTZ = null;
644 if (cal != calendar && !cal.getType().equals(calendar.getType())) {
645 // Different calendar type
646 // We use the time and time zone from the input calendar, but
647 // do not use the input calendar for field calculation.
648 calendar.setTimeInMillis(cal.getTimeInMillis());
649 backupTZ = calendar.getTimeZone();
650 calendar.setTimeZone(cal.getTimeZone());
653 StringBuffer result = format(cal, toAppendTo, pos, null);
654 if (backupTZ != null) {
655 // Restore the original time zone
656 calendar.setTimeZone(backupTZ);
661 // The actual method to format date. If List attributes is not null,
662 // then attribute information will be recorded.
663 private StringBuffer format(Calendar cal, StringBuffer toAppendTo,
664 FieldPosition pos, List attributes) {
666 pos.setBeginIndex(0);
669 // Careful: For best performance, minimize the number of calls
670 // to StringBuffer.append() by consolidating appends when
673 Object[] items = getPatternItems();
674 for (int i = 0; i < items.length; i++) {
675 if (items[i] instanceof String) {
676 toAppendTo.append((String)items[i]);
678 PatternItem item = (PatternItem)items[i];
679 //#if defined(FOUNDATION10) || defined(J2SE13)
682 if (attributes != null) {
683 // Save the current length
684 start = toAppendTo.length();
688 subFormat(toAppendTo, item.type, item.length, toAppendTo.length(), pos, cal);
690 toAppendTo.append(subFormat(item.type, item.length, toAppendTo.length(), pos, formatData, cal));
692 //#if defined(FOUNDATION10) || defined(J2SE13)
694 if (attributes != null) {
695 // Check the sub format length
696 int end = toAppendTo.length();
697 if (end - start > 0) {
698 // Append the attribute to the list
699 DateFormat.Field attr = patternCharToDateFormatField(item.type);
700 FieldPosition fp = new FieldPosition(attr);
701 fp.setBeginIndex(start);
713 // Map pattern character to index
714 private static final int PATTERN_CHAR_BASE = 0x40;
715 private static final int[] PATTERN_CHAR_TO_INDEX =
717 // A B C D E F G H I J K L M N O
718 -1, 22, -1, -1, 10, 9, 11, 0, 5, -1, -1, 16, 26, 2, -1, -1,
719 // P Q R S T U V W X Y Z
720 -1, 27, -1, 8, -1, -1, 29, 13, -1, 18, 23, -1, -1, -1, -1, -1,
721 // a b c d e f g h i j k l m n o
722 -1, 14, -1, 25, 3, 19, -1, 21, 15, -1, -1, 4, -1, 6, -1, -1,
723 // p q r s t u v w x y z
724 -1, 28, -1, 7, -1, 20, 24, 12, -1, 1, 17, -1, -1, -1, -1, -1
727 // Map pattern character index to Calendar field number
728 private static final int[] PATTERN_INDEX_TO_CALENDAR_FIELD =
730 /*GyM*/ Calendar.ERA, Calendar.YEAR, Calendar.MONTH,
731 /*dkH*/ Calendar.DATE, Calendar.HOUR_OF_DAY, Calendar.HOUR_OF_DAY,
732 /*msS*/ Calendar.MINUTE, Calendar.SECOND, Calendar.MILLISECOND,
733 /*EDF*/ Calendar.DAY_OF_WEEK, Calendar.DAY_OF_YEAR, Calendar.DAY_OF_WEEK_IN_MONTH,
734 /*wWa*/ Calendar.WEEK_OF_YEAR, Calendar.WEEK_OF_MONTH, Calendar.AM_PM,
735 /*hKz*/ Calendar.HOUR, Calendar.HOUR, Calendar.ZONE_OFFSET,
736 /*Yeu*/ Calendar.YEAR_WOY, Calendar.DOW_LOCAL, Calendar.EXTENDED_YEAR,
737 /*gAZ*/ Calendar.JULIAN_DAY, Calendar.MILLISECONDS_IN_DAY, Calendar.ZONE_OFFSET,
738 /*v*/ Calendar.ZONE_OFFSET,
739 /*c*/ Calendar.DOW_LOCAL,
740 /*L*/ Calendar.MONTH,
741 /*Qq*/ Calendar.MONTH, Calendar.MONTH,
742 /*V*/ Calendar.ZONE_OFFSET,
745 // Map pattern character index to DateFormat field number
746 private static final int[] PATTERN_INDEX_TO_DATE_FORMAT_FIELD = {
747 /*GyM*/ DateFormat.ERA_FIELD, DateFormat.YEAR_FIELD, DateFormat.MONTH_FIELD,
748 /*dkH*/ DateFormat.DATE_FIELD, DateFormat.HOUR_OF_DAY1_FIELD, DateFormat.HOUR_OF_DAY0_FIELD,
749 /*msS*/ DateFormat.MINUTE_FIELD, DateFormat.SECOND_FIELD, DateFormat.FRACTIONAL_SECOND_FIELD,
750 /*EDF*/ DateFormat.DAY_OF_WEEK_FIELD, DateFormat.DAY_OF_YEAR_FIELD, DateFormat.DAY_OF_WEEK_IN_MONTH_FIELD,
751 /*wWa*/ DateFormat.WEEK_OF_YEAR_FIELD, DateFormat.WEEK_OF_MONTH_FIELD, DateFormat.AM_PM_FIELD,
752 /*hKz*/ DateFormat.HOUR1_FIELD, DateFormat.HOUR0_FIELD, DateFormat.TIMEZONE_FIELD,
753 /*Yeu*/ DateFormat.YEAR_WOY_FIELD, DateFormat.DOW_LOCAL_FIELD, DateFormat.EXTENDED_YEAR_FIELD,
754 /*gAZ*/ DateFormat.JULIAN_DAY_FIELD, DateFormat.MILLISECONDS_IN_DAY_FIELD, DateFormat.TIMEZONE_RFC_FIELD,
755 /*v*/ DateFormat.TIMEZONE_GENERIC_FIELD,
756 /*c*/ DateFormat.STANDALONE_DAY_FIELD,
757 /*L*/ DateFormat.STANDALONE_MONTH_FIELD,
758 /*Qq*/ DateFormat.QUARTER_FIELD, DateFormat.STANDALONE_QUARTER_FIELD,
759 /*V*/ DateFormat.TIMEZONE_SPECIAL_FIELD,
762 //#if defined(FOUNDATION10) || defined(J2SE13)
764 // Map pattern character index to DateFormat.Field
765 private static final DateFormat.Field[] PATTERN_INDEX_TO_DATE_FORMAT_ATTRIBUTE = {
766 /*GyM*/ DateFormat.Field.ERA, DateFormat.Field.YEAR, DateFormat.Field.MONTH,
767 /*dkH*/ DateFormat.Field.DAY_OF_MONTH, DateFormat.Field.HOUR_OF_DAY1, DateFormat.Field.HOUR_OF_DAY0,
768 /*msS*/ DateFormat.Field.MINUTE, DateFormat.Field.SECOND, DateFormat.Field.MILLISECOND,
769 /*EDF*/ DateFormat.Field.DAY_OF_WEEK, DateFormat.Field.DAY_OF_YEAR, DateFormat.Field.DAY_OF_WEEK_IN_MONTH,
770 /*wWa*/ DateFormat.Field.WEEK_OF_YEAR, DateFormat.Field.WEEK_OF_MONTH, DateFormat.Field.AM_PM,
771 /*hKz*/ DateFormat.Field.HOUR1, DateFormat.Field.HOUR0, DateFormat.Field.TIME_ZONE,
772 /*Yeu*/ DateFormat.Field.YEAR_WOY, DateFormat.Field.DOW_LOCAL, DateFormat.Field.EXTENDED_YEAR,
773 /*gAZ*/ DateFormat.Field.JULIAN_DAY, DateFormat.Field.MILLISECONDS_IN_DAY, DateFormat.Field.TIME_ZONE,
774 /*v*/ DateFormat.Field.TIME_ZONE,
775 /*c*/ DateFormat.Field.DAY_OF_WEEK,
776 /*L*/ DateFormat.Field.MONTH,
777 /*Qq*/ DateFormat.Field.QUARTER, DateFormat.Field.QUARTER,
778 /*V*/ DateFormat.Field.TIME_ZONE,
782 * Return a DateFormat.Field constant associated with the specified format pattern
785 * @param ch The pattern character
786 * @return DateFormat.Field associated with the pattern character
790 protected DateFormat.Field patternCharToDateFormatField(char ch) {
791 int patternCharIndex = -1;
792 if ('A' <= ch && ch <= 'z') {
793 patternCharIndex = PATTERN_CHAR_TO_INDEX[(int)ch - PATTERN_CHAR_BASE];
795 if (patternCharIndex != -1) {
796 return PATTERN_INDEX_TO_DATE_FORMAT_ATTRIBUTE[patternCharIndex];
803 * Format a single field, given its pattern character. Subclasses may
804 * override this method in order to modify or add formatting
806 * @param ch the pattern character
807 * @param count the number of times ch is repeated in the pattern
808 * @param beginOffset the offset of the output string at the start of
809 * this field; used to set pos when appropriate
810 * @param pos receives the position of a field, when appropriate
811 * @param fmtData the symbols for this formatter
814 protected String subFormat(char ch, int count, int beginOffset,
815 FieldPosition pos, DateFormatSymbols fmtData,
817 throws IllegalArgumentException
819 // Note: formatData is ignored
820 StringBuffer buf = new StringBuffer();
821 subFormat(buf, ch, count, beginOffset, pos, cal);
822 return buf.toString();
826 * Format a single field; useFastFormat variant. Reuses a
827 * StringBuffer for results instead of creating a String on the
828 * heap for each call.
830 * NOTE We don't really need the beginOffset parameter, EXCEPT for
831 * the need to support the slow subFormat variant (above) which
832 * has to pass it in to us.
834 * TODO make this API public
837 * @deprecated This API is ICU internal only.
839 protected void subFormat(StringBuffer buf,
840 char ch, int count, int beginOffset,
843 final int maxIntCount = Integer.MAX_VALUE;
844 final int bufstart = buf.length();
846 // final int patternCharIndex = DateFormatSymbols.patternChars.indexOf(ch);
847 int patternCharIndex = -1;
848 if ('A' <= ch && ch <= 'z') {
849 patternCharIndex = PATTERN_CHAR_TO_INDEX[(int)ch - PATTERN_CHAR_BASE];
852 if (patternCharIndex == -1) {
853 throw new IllegalArgumentException("Illegal pattern character " +
854 "'" + ch + "' in \"" +
855 new String(pattern) + '"');
858 final int field = PATTERN_INDEX_TO_CALENDAR_FIELD[patternCharIndex];
859 int value = cal.get(field);
861 String zoneString = null;
863 NumberFormat currentNumberFormat = getNumberFormat(ch);
865 switch (patternCharIndex) {
868 safeAppend(formatData.narrowEras, value, buf);
869 } else if (count == 4) {
870 safeAppend(formatData.eraNames, value, buf);
872 safeAppend(formatData.eras, value, buf);
875 case 1: // 'y' - YEAR
876 /* According to the specification, if the number of pattern letters ('y') is 2,
877 * the year is truncated to 2 digits; otherwise it is interpreted as a number.
878 * But the original code process 'y', 'yy', 'yyy' in the same way. and process
879 * patterns with 4 or more than 4 'y' characters in the same way.
880 * So I change the codes to meet the specification. [Richard/GCl]
883 zeroPaddingNumber(currentNumberFormat,buf, value, 2, 2); // clip 1996 to 96
884 else //count = 1 or count > 2
885 zeroPaddingNumber(currentNumberFormat,buf, value, count, maxIntCount);
887 case 2: // 'M' - MONTH
889 safeAppend(formatData.narrowMonths, value, buf);
890 } else if (count == 4) {
891 safeAppend(formatData.months, value, buf);
892 } else if (count == 3) {
893 safeAppend(formatData.shortMonths, value, buf);
895 zeroPaddingNumber(currentNumberFormat,buf, value+1, count, maxIntCount);
898 case 4: // 'k' - HOUR_OF_DAY (1..24)
900 zeroPaddingNumber(currentNumberFormat,buf,
901 cal.getMaximum(Calendar.HOUR_OF_DAY)+1,
904 zeroPaddingNumber(currentNumberFormat,buf, value, count, maxIntCount);
906 case 8: // 'S' - FRACTIONAL_SECOND
907 // Fractional seconds left-justify
909 numberFormat.setMinimumIntegerDigits(Math.min(3, count));
910 numberFormat.setMaximumIntegerDigits(maxIntCount);
912 value = (value + 50) / 100;
913 } else if (count == 2) {
914 value = (value + 5) / 10;
916 FieldPosition p = new FieldPosition(-1);
917 numberFormat.format((long) value, buf, p);
919 numberFormat.setMinimumIntegerDigits(count - 3);
920 numberFormat.format(0L, buf, p);
924 case 19: // 'e' - DOW_LOCAL (use DOW_LOCAL for numeric, DAY_OF_WEEK for format names)
926 zeroPaddingNumber(currentNumberFormat,buf, value, count, maxIntCount);
929 // For alpha day-of-week, we don't want DOW_LOCAL,
930 // we need the standard DAY_OF_WEEK.
931 value = cal.get(Calendar.DAY_OF_WEEK);
932 // fall through, do not break here
933 case 9: // 'E' - DAY_OF_WEEK
935 safeAppend(formatData.narrowWeekdays, value, buf);
936 } else if (count == 4) {
937 safeAppend(formatData.weekdays, value, buf);
938 } else {// count <= 3, use abbreviated form if exists
939 safeAppend(formatData.shortWeekdays, value, buf);
942 case 14: // 'a' - AM_PM
943 safeAppend(formatData.ampms, value, buf);
945 case 15: // 'h' - HOUR (1..12)
947 zeroPaddingNumber(currentNumberFormat,buf,
948 cal.getLeastMaximum(Calendar.HOUR)+1,
951 zeroPaddingNumber(currentNumberFormat,buf, value, count, maxIntCount);
953 case 17: // 'z' - ZONE_OFFSET
956 zoneString = formatData.getZoneStringFormat().getSpecificShortString(cal, true /* commonly used only */);
958 zoneString = formatData.getZoneStringFormat().getSpecificLongString(cal);
960 if (zoneString != null && zoneString.length() != 0) {
961 buf.append(zoneString);
963 // Use localized GMT format as fallback
964 appendGMT(currentNumberFormat,buf, cal);
967 case 23: // 'Z' - TIMEZONE_RFC
969 // RFC822 format, must use ASCII digits
970 int val = (cal.get(Calendar.ZONE_OFFSET) + cal.get(Calendar.DST_OFFSET));
978 int offsetH = val / millisPerHour;
979 val = val % millisPerHour;
980 int offsetM = val / millisPerMinute;
981 val = val % millisPerMinute;
982 int offsetS = val / millisPerSecond;
984 int num = 0, denom = 0;
986 val = offsetH*100 + offsetM; // HHmm
990 val = offsetH*10000 + offsetM*100 + offsetS; // HHmmss
995 char digit = (char)((num / denom) + '0');
1001 // long form, localized GMT pattern
1002 appendGMT(currentNumberFormat,buf, cal);
1005 case 24: // 'v' - TIMEZONE_GENERIC
1008 zoneString = formatData.getZoneStringFormat().getGenericShortString(cal, true /* commonly used only */);
1009 } else if (count == 4) {
1011 zoneString = formatData.getZoneStringFormat().getGenericLongString(cal);
1013 if (zoneString != null && zoneString.length() != 0) {
1014 buf.append(zoneString);
1016 // Use localized GMT format as fallback
1017 appendGMT(currentNumberFormat,buf, cal);
1020 case 25: // 'c' - STANDALONE DAY (use DOW_LOCAL for numeric, DAY_OF_WEEK for standalone names)
1022 zeroPaddingNumber(currentNumberFormat,buf, value, 1, maxIntCount);
1025 // For alpha day-of-week, we don't want DOW_LOCAL,
1026 // we need the standard DAY_OF_WEEK.
1027 value = cal.get(Calendar.DAY_OF_WEEK);
1029 safeAppend(formatData.standaloneNarrowWeekdays, value, buf);
1030 } else if (count == 4) {
1031 safeAppend(formatData.standaloneWeekdays, value, buf);
1032 } else { // count == 3
1033 safeAppend(formatData.standaloneShortWeekdays, value, buf);
1036 case 26: // 'L' - STANDALONE MONTH
1038 safeAppend(formatData.standaloneNarrowMonths, value, buf);
1039 } else if (count == 4) {
1040 safeAppend(formatData.standaloneMonths, value, buf);
1041 } else if (count == 3) {
1042 safeAppend(formatData.standaloneShortMonths, value, buf);
1044 zeroPaddingNumber(currentNumberFormat,buf, value+1, count, maxIntCount);
1047 case 27: // 'Q' - QUARTER
1049 safeAppend(formatData.quarters, value/3, buf);
1050 } else if (count == 3) {
1051 safeAppend(formatData.shortQuarters, value/3, buf);
1053 zeroPaddingNumber(currentNumberFormat,buf, (value/3)+1, count, maxIntCount);
1056 case 28: // 'q' - STANDALONE QUARTER
1058 safeAppend(formatData.standaloneQuarters, value/3, buf);
1059 } else if (count == 3) {
1060 safeAppend(formatData.standaloneShortQuarters, value/3, buf);
1062 zeroPaddingNumber(currentNumberFormat,buf, (value/3)+1, count, maxIntCount);
1065 case 29: // 'V' - TIMEZONE_SPECIAL
1068 zoneString = formatData.getZoneStringFormat().getSpecificShortString(cal, false /* ignoring commonly used */);
1069 } else if (count == 4) {
1071 zoneString = formatData.getZoneStringFormat().getGenericLocationString(cal);
1073 if (zoneString != null && zoneString.length() != 0) {
1074 buf.append(zoneString);
1076 // Use localized GMT format as fallback
1077 appendGMT(currentNumberFormat,buf, cal);
1081 // case 3: // 'd' - DATE
1082 // case 5: // 'H' - HOUR_OF_DAY (0..23)
1083 // case 6: // 'm' - MINUTE
1084 // case 7: // 's' - SECOND
1085 // case 10: // 'D' - DAY_OF_YEAR
1086 // case 11: // 'F' - DAY_OF_WEEK_IN_MONTH
1087 // case 12: // 'w' - WEEK_OF_YEAR
1088 // case 13: // 'W' - WEEK_OF_MONTH
1089 // case 16: // 'K' - HOUR (0..11)
1090 // case 18: // 'Y' - YEAR_WOY
1091 // case 20: // 'u' - EXTENDED_YEAR
1092 // case 21: // 'g' - JULIAN_DAY
1093 // case 22: // 'A' - MILLISECONDS_IN_DAY
1095 zeroPaddingNumber(currentNumberFormat,buf, value, count, maxIntCount);
1097 } // switch (patternCharIndex)
1099 // Set the FieldPosition (for the first occurrence only)
1100 if (pos.getBeginIndex() == pos.getEndIndex()) {
1101 if (pos.getField() == PATTERN_INDEX_TO_DATE_FORMAT_FIELD[patternCharIndex]) {
1102 pos.setBeginIndex(beginOffset);
1103 pos.setEndIndex(beginOffset + buf.length() - bufstart);
1105 //#if defined(FOUNDATION10) || defined(J2SE13)
1107 else if (pos.getFieldAttribute() == PATTERN_INDEX_TO_DATE_FORMAT_ATTRIBUTE[patternCharIndex]) {
1108 pos.setBeginIndex(beginOffset);
1109 pos.setEndIndex(beginOffset + buf.length() - bufstart);
1115 private static void safeAppend(String[] array, int value, StringBuffer appendTo) {
1116 if (array != null && value >= 0 && value < array.length) {
1117 appendTo.append(array[value]);
1122 * PatternItem store parsed date/time field pattern information.
1124 private static class PatternItem {
1127 final boolean isNumeric;
1129 PatternItem(char type, int length) {
1131 this.length = length;
1132 isNumeric = isNumeric(type, length);
1136 private static ICUCache PARSED_PATTERN_CACHE = new SimpleCache();
1137 private transient Object[] patternItems;
1140 * Returns parsed pattern items. Each item is either String or
1143 private Object[] getPatternItems() {
1144 if (patternItems != null) {
1145 return patternItems;
1148 patternItems = (Object[])PARSED_PATTERN_CACHE.get(pattern);
1149 if (patternItems != null) {
1150 return patternItems;
1153 boolean isPrevQuote = false;
1154 boolean inQuote = false;
1155 StringBuffer text = new StringBuffer();
1156 char itemType = 0; // 0 for string literal, otherwise date/time pattern character
1159 List items = new ArrayList();
1161 for (int i = 0; i < pattern.length(); i++) {
1162 char ch = pattern.charAt(i);
1166 isPrevQuote = false;
1169 if (itemType != 0) {
1170 items.add(new PatternItem(itemType, itemLength));
1176 isPrevQuote = false;
1180 if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')) {
1181 // a date/time pattern character
1182 if (ch == itemType) {
1185 if (itemType == 0) {
1186 if (text.length() > 0) {
1187 items.add(text.toString());
1191 items.add(new PatternItem(itemType, itemLength));
1198 if (itemType != 0) {
1199 items.add(new PatternItem(itemType, itemLength));
1208 if (itemType == 0) {
1209 if (text.length() > 0) {
1210 items.add(text.toString());
1214 items.add(new PatternItem(itemType, itemLength));
1217 patternItems = new Object[items.size()];
1218 items.toArray(patternItems);
1220 PARSED_PATTERN_CACHE.put(pattern, patternItems);
1222 return patternItems;
1226 * Time zone localized GMT format stuffs
1228 private static final String STR_GMT = "GMT";
1229 private static final String STR_UT = "UT";
1230 private static final String STR_UTC = "UTC";
1231 private static final int STR_GMT_LEN = 3;
1232 private static final int STR_UT_LEN = 2;
1233 private static final int STR_UTC_LEN = 3;
1234 private static final char PLUS = '+';
1235 private static final char MINUS = '-';
1236 private static final char COLON = ':';
1238 private void appendGMT(NumberFormat currentNumberFormat,StringBuffer buf, Calendar cal) {
1239 int offset = cal.get(Calendar.ZONE_OFFSET) + cal.get(Calendar.DST_OFFSET);
1241 if (isDefaultGMTFormat()) {
1242 formatGMTDefault(currentNumberFormat,buf, offset);
1244 int sign = DateFormatSymbols.OFFSET_POSITIVE;
1247 sign = DateFormatSymbols.OFFSET_NEGATIVE;
1249 int width = offset%(60*1000) == 0 ? DateFormatSymbols.OFFSET_HM : DateFormatSymbols.OFFSET_HMS;
1251 MessageFormat fmt = getGMTFormatter(sign, width);
1252 fmt.format(new Object[] {new Long(offset)}, buf, null);
1256 private void formatGMTDefault(NumberFormat currentNumberFormat,StringBuffer buf, int offset) {
1257 buf.append(STR_GMT);
1264 offset /= 1000; // now in seconds
1265 int sec = offset % 60;
1267 int min = offset % 60;
1268 int hour = offset / 60;
1270 zeroPaddingNumber(currentNumberFormat,buf, hour, 2, 2);
1272 zeroPaddingNumber(currentNumberFormat,buf, min, 2, 2);
1275 zeroPaddingNumber(currentNumberFormat,buf, sec, 2, 2);
1279 private Integer parseGMT(String text, ParsePosition pos, NumberFormat currentNumberFormat) {
1280 if (!isDefaultGMTFormat()) {
1281 int start = pos.getIndex();
1282 String gmtPattern = formatData.gmtFormat;
1285 boolean prefixMatch = false;
1286 int prefixLen = gmtPattern.indexOf('{');
1287 if (prefixLen > 0 && text.regionMatches(start, gmtPattern, 0, prefixLen)) {
1294 Object[] parsedObjects;
1298 fmt = getGMTFormatter(DateFormatSymbols.OFFSET_NEGATIVE, DateFormatSymbols.OFFSET_HMS);
1299 parsedObjects = fmt.parse(text, pos);
1300 if ((parsedObjects != null) && (parsedObjects[0] instanceof Date)
1301 && (pos.getIndex() - start) >= getGMTFormatMinHMSLen(DateFormatSymbols.OFFSET_NEGATIVE)) {
1302 offset = (int)((Date)parsedObjects[0]).getTime();
1303 return new Integer(-offset /* negative */);
1306 // Reset ParsePosition
1307 pos.setIndex(start);
1308 pos.setErrorIndex(-1);
1311 fmt = getGMTFormatter(DateFormatSymbols.OFFSET_POSITIVE, DateFormatSymbols.OFFSET_HMS);
1312 parsedObjects = fmt.parse(text, pos);
1313 if ((parsedObjects != null) && (parsedObjects[0] instanceof Date)
1314 && (pos.getIndex() - start) >= getGMTFormatMinHMSLen(DateFormatSymbols.OFFSET_POSITIVE)) {
1315 offset = (int)((Date)parsedObjects[0]).getTime();
1316 return new Integer(offset);
1319 // Reset ParsePosition
1320 pos.setIndex(start);
1321 pos.setErrorIndex(-1);
1324 fmt = getGMTFormatter(DateFormatSymbols.OFFSET_NEGATIVE, DateFormatSymbols.OFFSET_HM);
1325 parsedObjects = fmt.parse(text, pos);
1326 if ((parsedObjects != null) && (parsedObjects[0] instanceof Date)) {
1327 offset = (int)((Date)parsedObjects[0]).getTime();
1328 return new Integer(-offset /* negative */);
1331 // Reset ParsePosition
1332 pos.setIndex(start);
1333 pos.setErrorIndex(-1);
1336 fmt = getGMTFormatter(DateFormatSymbols.OFFSET_POSITIVE, DateFormatSymbols.OFFSET_HM);
1337 parsedObjects = fmt.parse(text, pos);
1338 if ((parsedObjects != null) && (parsedObjects[0] instanceof Date)) {
1339 offset = (int)((Date)parsedObjects[0]).getTime();
1340 return new Integer(offset);
1343 // Reset ParsePosition
1344 pos.setIndex(start);
1345 pos.setErrorIndex(-1);
1349 return parseGMTDefault(text, pos, currentNumberFormat);
1352 private Integer parseGMTDefault(String text, ParsePosition pos, NumberFormat currentNumberFormat) {
1353 int start = pos.getIndex();
1355 if (start + STR_UT_LEN + 1 >= text.length()) {
1356 pos.setErrorIndex(start);
1362 if (text.regionMatches(true, start, STR_GMT, 0, STR_GMT_LEN)) {
1364 } else if (text.regionMatches(true, start, STR_UT, 0, STR_UT_LEN)) {
1367 pos.setErrorIndex(start);
1371 boolean negative = false;
1372 if (text.charAt(cur) == MINUS) {
1374 } else if (text.charAt(cur) != PLUS) {
1375 pos.setErrorIndex(cur);
1384 Number n = parseInt(text, 6, pos, false,currentNumberFormat);
1385 numLen = pos.getIndex() - cur;
1387 if (n == null || numLen <= 0 || numLen > 6) {
1388 pos.setIndex(start);
1389 pos.setErrorIndex(cur);
1393 int numVal = n.intValue();
1403 if (cur + 2 < text.length() && text.charAt(cur) == COLON) {
1406 n = parseInt(text, 2, pos, false,currentNumberFormat);
1407 numLen = pos.getIndex() - cur;
1408 if (n != null && numLen == 2) {
1412 if (cur + 2 < text.length() && text.charAt(cur) == COLON) {
1415 n = parseInt(text, 2, pos, false,currentNumberFormat);
1416 numLen = pos.getIndex() - cur;
1417 if (n != null && numLen == 2) {
1422 pos.setIndex(cur - 1);
1423 pos.setErrorIndex(-1);
1428 pos.setIndex(cur - 1);
1429 pos.setErrorIndex(-1);
1432 } else if (numLen == 3 || numLen == 4) {
1434 hour = numVal / 100;
1436 } else { // numLen == 5 || numLen == 6
1438 hour = numVal / 10000;
1439 min = (numVal % 10000) / 100;
1443 int offset = ((hour*60 + min)*60 + sec)*1000;
1447 return new Integer(offset);
1450 transient private WeakReference[] gmtfmtCache;
1452 private MessageFormat getGMTFormatter(int sign, int width) {
1453 MessageFormat fmt = null;
1454 if (gmtfmtCache == null) {
1455 gmtfmtCache = new WeakReference[4];
1457 int cacheIdx = sign*2 + width;
1458 if (gmtfmtCache[cacheIdx] != null) {
1459 fmt = (MessageFormat)gmtfmtCache[cacheIdx].get();
1462 fmt = new MessageFormat(formatData.gmtFormat);
1463 GregorianCalendar gcal = new GregorianCalendar(TimeZone.getTimeZone("Etc/UTC"));
1464 SimpleDateFormat sdf = (SimpleDateFormat)this.clone();
1465 sdf.setCalendar(gcal);
1466 sdf.applyPattern(formatData.getGmtHourFormat(sign, width));
1467 fmt.setFormat(0, sdf);
1468 gmtfmtCache[cacheIdx] = new WeakReference(fmt);
1473 transient private int[] gmtFormatHmsMinLen = null;
1475 private int getGMTFormatMinHMSLen(int sign) {
1476 if (gmtFormatHmsMinLen == null) {
1477 gmtFormatHmsMinLen = new int[2];
1478 Long offset = new Long(60*60*1000); // 1 hour
1480 StringBuffer buf = new StringBuffer();
1481 MessageFormat fmtNeg = getGMTFormatter(DateFormatSymbols.OFFSET_NEGATIVE, DateFormatSymbols.OFFSET_HMS);
1482 fmtNeg.format(new Object[] {offset}, buf, null);
1483 gmtFormatHmsMinLen[0] = buf.length();
1486 MessageFormat fmtPos = getGMTFormatter(DateFormatSymbols.OFFSET_POSITIVE, DateFormatSymbols.OFFSET_HMS);
1487 fmtPos.format(new Object[] {offset}, buf, null);
1488 gmtFormatHmsMinLen[1] = buf.length();
1490 return gmtFormatHmsMinLen[(sign < 0 ? 0 : 1)];
1493 private boolean isDefaultGMTFormat() {
1495 if (!DateFormatSymbols.DEFAULT_GMT_PATTERN.equals(formatData.getGmtFormat())) {
1498 // GMT offset hour patters
1500 for (int sign = 0; sign < 2 && res; sign++) {
1501 for (int width = 0; width < 2; width++) {
1502 if (!DateFormatSymbols.DEFAULT_GMT_HOUR_PATTERNS[sign][width].equals(formatData.getGmtHourFormat(sign, width))) {
1512 * Internal method. Returns null if the value of an array is empty, or if the
1513 * index is out of bounds
1515 /* private String getZoneArrayValue(String[] zs, int ix) {
1516 if (ix >= 0 && ix < zs.length) {
1517 String result = zs[ix];
1518 if (result != null && result.length() != 0) {
1526 * Internal high-speed method. Reuses a StringBuffer for results
1527 * instead of creating a String on the heap for each call.
1529 * @deprecated This API is ICU internal only.
1531 protected void zeroPaddingNumber(NumberFormat nf,StringBuffer buf, int value,
1532 int minDigits, int maxDigits) {
1533 if (useLocalZeroPaddingNumberFormat) {
1534 fastZeroPaddingNumber(buf, value, minDigits, maxDigits);
1536 nf.setMinimumIntegerDigits(minDigits);
1537 nf.setMaximumIntegerDigits(maxDigits);
1538 nf.format(value, buf, new FieldPosition(-1));
1543 * Overrides superclass method
1546 public void setNumberFormat(NumberFormat newNumberFormat) {
1547 // Override this method to update local zero padding number formatter
1548 super.setNumberFormat(newNumberFormat);
1549 initLocalZeroPaddingNumberFormat();
1552 private void initLocalZeroPaddingNumberFormat() {
1553 if (numberFormat instanceof DecimalFormat) {
1554 zeroDigit = ((DecimalFormat)numberFormat).getDecimalFormatSymbols().getZeroDigit();
1555 useLocalZeroPaddingNumberFormat = true;
1556 } else if (numberFormat instanceof DateNumberFormat) {
1557 zeroDigit = ((DateNumberFormat)numberFormat).getZeroDigit();
1558 useLocalZeroPaddingNumberFormat = true;
1560 useLocalZeroPaddingNumberFormat = false;
1563 if (useLocalZeroPaddingNumberFormat) {
1564 decimalBuf = new char[10]; // sufficient for int numbers
1568 // If true, use local version of zero padding number format
1569 private transient boolean useLocalZeroPaddingNumberFormat;
1570 private transient char zeroDigit;
1571 private transient char[] decimalBuf;
1574 * Lightweight zero padding integer number format function.
1576 * Note: This implementation is almost equivalent to format method in DateNumberFormat.
1577 * In the method zeroPaddingNumber above should be able to use the one in DateNumberFormat,
1578 * but, it does not help IBM J9's JIT to optimize the performance much. In simple repeative
1579 * date format test case, having local implementation is ~10% faster than using one in
1580 * DateNumberFormat on IBM J9 VM. On Sun Hotspot VM, I do not see such difference.
1584 private void fastZeroPaddingNumber(StringBuffer buf, int value, int minDigits, int maxDigits) {
1585 int limit = decimalBuf.length < maxDigits ? decimalBuf.length : maxDigits;
1586 int index = limit - 1;
1588 decimalBuf[index] = (char)((value % 10) + zeroDigit);
1590 if (index == 0 || value == 0) {
1595 int padding = minDigits - (limit - index);
1596 for (; padding > 0; padding--) {
1597 decimalBuf[--index] = zeroDigit;
1599 int length = limit - index;
1600 buf.append(decimalBuf, index, length);
1604 * Formats a number with the specified minimum and maximum number of digits.
1607 protected String zeroPaddingNumber(long value, int minDigits, int maxDigits)
1609 numberFormat.setMinimumIntegerDigits(minDigits);
1610 numberFormat.setMaximumIntegerDigits(maxDigits);
1611 return numberFormat.format(value);
1615 * Format characters that indicate numeric fields. The character
1616 * at index 0 is treated specially.
1618 private static final String NUMERIC_FORMAT_CHARS = "MYyudehHmsSDFwWkK";
1621 * Return true if the given format character, occuring count
1622 * times, represents a numeric field.
1624 private static final boolean isNumeric(char formatChar, int count) {
1625 int i = NUMERIC_FORMAT_CHARS.indexOf(formatChar);
1626 return (i > 0 || (i == 0 && count < 3));
1630 * Overrides DateFormat
1634 public void parse(String text, Calendar cal, ParsePosition parsePos)
1636 TimeZone backupTZ = null;
1637 Calendar resultCal = null;
1638 if (cal != calendar && !cal.getType().equals(calendar.getType())) {
1639 // Different calendar type
1640 // We use the time/zone from the input calendar, but
1641 // do not use the input calendar for field calculation.
1642 calendar.setTimeInMillis(cal.getTimeInMillis());
1643 backupTZ = calendar.getTimeZone();
1644 calendar.setTimeZone(cal.getTimeZone());
1649 int pos = parsePos.getIndex();
1653 tztype = TZTYPE_UNK;
1654 boolean[] ambiguousYear = { false };
1656 // item index for the first numeric field within a contiguous numeric run
1657 int numericFieldStart = -1;
1658 // item length for the first numeric field within a contiguous numeric run
1659 int numericFieldLength = 0;
1660 // start index of numeric text run in the input text
1661 int numericStartPos = 0;
1663 Object[] items = getPatternItems();
1665 while (i < items.length) {
1666 if (items[i] instanceof PatternItem) {
1667 // Handle pattern field
1668 PatternItem field = (PatternItem)items[i];
1669 if (field.isNumeric) {
1670 // Handle fields within a run of abutting numeric fields. Take
1671 // the pattern "HHmmss" as an example. We will try to parse
1672 // 2/2/2 characters of the input text, then if that fails,
1673 // 1/2/2. We only adjust the width of the leftmost field; the
1674 // others remain fixed. This allows "123456" => 12:34:56, but
1675 // "12345" => 1:23:45. Likewise, for the pattern "yyyyMMdd" we
1676 // try 4/2/2, 3/2/2, 2/2/2, and finally 1/2/2.
1677 if (numericFieldStart == -1) {
1678 // check if this field is followed by abutting another numeric field
1679 if ((i + 1) < items.length
1680 && (items[i + 1] instanceof PatternItem)
1681 && ((PatternItem)items[i + 1]).isNumeric) {
1682 // record the first numeric field within a numeric text run
1683 numericFieldStart = i;
1684 numericFieldLength = field.length;
1685 numericStartPos = pos;
1689 if (numericFieldStart != -1) {
1690 // Handle a numeric field within abutting numeric fields
1691 int len = field.length;
1692 if (numericFieldStart == i) {
1693 len = numericFieldLength;
1696 // Parse a numeric field
1697 pos = subParse(text, pos, field.type, len,
1698 true, false, ambiguousYear, cal);
1701 // If the parse fails anywhere in the numeric run, back up to the
1702 // start of the run and use shorter pattern length for the first
1704 --numericFieldLength;
1705 if (numericFieldLength == 0) {
1706 // can not make shorter any more
1707 parsePos.setIndex(start);
1708 parsePos.setErrorIndex(pos);
1709 if (backupTZ != null) {
1710 calendar.setTimeZone(backupTZ);
1714 i = numericFieldStart;
1715 pos = numericStartPos;
1720 // Handle a non-numeric field or a non-abutting numeric field
1721 numericFieldStart = -1;
1724 pos = subParse(text, pos, field.type, field.length,
1725 false, true, ambiguousYear, cal);
1727 parsePos.setIndex(start);
1728 parsePos.setErrorIndex(s);
1729 if (backupTZ != null) {
1730 calendar.setTimeZone(backupTZ);
1736 // Handle literal pattern text literal
1737 numericFieldStart = -1;
1739 String patl = (String)items[i];
1740 int plen = patl.length();
1741 int tlen = text.length();
1743 while (idx < plen && pos < tlen) {
1744 char pch = patl.charAt(idx);
1745 char ich = text.charAt(pos);
1746 if (UCharacterProperty.isRuleWhiteSpace(pch) && UCharacterProperty.isRuleWhiteSpace(ich)) {
1747 // White space characters found in both patten and input.
1748 // Skip contiguous white spaces.
1749 while ((idx + 1) < plen &&
1750 UCharacterProperty.isRuleWhiteSpace(patl.charAt(idx + 1))) {
1753 while ((pos + 1) < tlen &&
1754 UCharacterProperty.isRuleWhiteSpace(text.charAt(pos + 1))) {
1757 } else if (pch != ich) {
1764 // Set the position of mismatch
1765 parsePos.setIndex(start);
1766 parsePos.setErrorIndex(pos);
1767 if (backupTZ != null) {
1768 calendar.setTimeZone(backupTZ);
1776 // At this point the fields of Calendar have been set. Calendar
1777 // will fill in default values for missing fields when the time
1780 parsePos.setIndex(pos);
1782 // This part is a problem: When we call parsedDate.after, we compute the time.
1783 // Take the date April 3 2004 at 2:30 am. When this is first set up, the year
1784 // will be wrong if we're parsing a 2-digit year pattern. It will be 1904.
1785 // April 3 1904 is a Sunday (unlike 2004) so it is the DST onset day. 2:30 am
1786 // is therefore an "impossible" time, since the time goes from 1:59 to 3:00 am
1787 // on that day. It is therefore parsed out to fields as 3:30 am. Then we
1788 // add 100 years, and get April 3 2004 at 3:30 am. Note that April 3 2004 is
1789 // a Saturday, so it can have a 2:30 am -- and it should. [LIU]
1791 Date parsedDate = cal.getTime();
1792 if( ambiguousYear[0] && !parsedDate.after(getDefaultCenturyStart()) ) {
1793 cal.add(Calendar.YEAR, 100);
1794 parsedDate = cal.getTime();
1797 // Because of the above condition, save off the fields in case we need to readjust.
1798 // The procedure we use here is not particularly efficient, but there is no other
1799 // way to do this given the API restrictions present in Calendar. We minimize
1800 // inefficiency by only performing this computation when it might apply, that is,
1801 // when the two-digit year is equal to the start year, and thus might fall at the
1802 // front or the back of the default century. This only works because we adjust
1803 // the year correctly to start with in other cases -- see subParse().
1805 if (ambiguousYear[0] || tztype != TZTYPE_UNK) {
1806 // We need a copy of the fields, and we need to avoid triggering a call to
1807 // complete(), which will recalculate the fields. Since we can't access
1808 // the fields[] array in Calendar, we clone the entire object. This will
1809 // stop working if Calendar.clone() is ever rewritten to call complete().
1811 if (ambiguousYear[0]) { // the two-digit year == the default start year
1812 copy = (Calendar)cal.clone();
1813 Date parsedDate = copy.getTime();
1814 if (parsedDate.before(getDefaultCenturyStart())) {
1815 // We can't use add here because that does a complete() first.
1816 cal.set(Calendar.YEAR, getDefaultCenturyStartYear() + 100);
1819 if (tztype != TZTYPE_UNK) {
1820 copy = (Calendar)cal.clone();
1821 TimeZone tz = copy.getTimeZone();
1822 BasicTimeZone btz = null;
1823 if (tz instanceof BasicTimeZone) {
1824 btz = (BasicTimeZone)tz;
1828 copy.set(Calendar.ZONE_OFFSET, 0);
1829 copy.set(Calendar.DST_OFFSET, 0);
1830 long localMillis = copy.getTimeInMillis();
1832 // Make sure parsed time zone type (Standard or Daylight)
1833 // matches the rule used by the parsed time zone.
1834 int[] offsets = new int[2];
1836 if (tztype == TZTYPE_STD) {
1837 btz.getOffsetFromLocal(localMillis,
1838 BasicTimeZone.LOCAL_STD, BasicTimeZone.LOCAL_STD, offsets);
1840 btz.getOffsetFromLocal(localMillis,
1841 BasicTimeZone.LOCAL_DST, BasicTimeZone.LOCAL_DST, offsets);
1844 // No good way to resolve ambiguous time at transition,
1845 // but following code work in most case.
1846 tz.getOffset(localMillis, true, offsets);
1848 if (tztype == TZTYPE_STD && offsets[1] != 0 || tztype == TZTYPE_DST && offsets[1] == 0) {
1849 // Roll back one day and try it again.
1850 // Note: This code assumes 1. timezone transition only happens once within 24 hours at max
1851 // 2. the difference of local offsets at the transition is less than 24 hours.
1852 tz.getOffset(localMillis - (24*60*60*1000), true, offsets);
1856 // Now, compare the results with parsed type, either standard or daylight saving time
1857 int resolvedSavings = offsets[1];
1858 if (tztype == TZTYPE_STD) {
1859 if (offsets[1] != 0) {
1860 // Override DST_OFFSET = 0 in the result calendar
1861 resolvedSavings = 0;
1863 } else { // tztype == TZTYPE_DST
1864 if (offsets[1] == 0) {
1866 long time = localMillis + offsets[0];
1867 // We use the nearest daylight saving time rule.
1868 TimeZoneTransition beforeTrs, afterTrs;
1869 long beforeT = time, afterT = time;
1870 int beforeSav = 0, afterSav = 0;
1872 // Search for DST rule before or on the time
1874 beforeTrs = btz.getPreviousTransition(beforeT, true);
1875 if (beforeTrs == null) {
1878 beforeT = beforeTrs.getTime() - 1;
1879 beforeSav = beforeTrs.getFrom().getDSTSavings();
1880 if (beforeSav != 0) {
1885 // Search for DST rule after the time
1887 afterTrs = btz.getNextTransition(afterT, false);
1888 if (afterTrs == null) {
1891 afterT = afterTrs.getTime();
1892 afterSav = afterTrs.getTo().getDSTSavings();
1893 if (afterSav != 0) {
1898 if (beforeTrs != null && afterTrs != null) {
1899 if (time - beforeT > afterT - time) {
1900 resolvedSavings = afterSav;
1902 resolvedSavings = beforeSav;
1904 } else if (beforeTrs != null && beforeSav != 0) {
1905 resolvedSavings = beforeSav;
1906 } else if (afterTrs != null && afterSav != 0) {
1907 resolvedSavings = afterSav;
1909 resolvedSavings = btz.getDSTSavings();
1912 resolvedSavings = tz.getDSTSavings();
1914 if (resolvedSavings == 0) {
1916 resolvedSavings = millisPerHour;
1920 cal.set(Calendar.ZONE_OFFSET, offsets[0]);
1921 cal.set(Calendar.DST_OFFSET, resolvedSavings);
1925 // An IllegalArgumentException will be thrown by Calendar.getTime()
1926 // if any fields are out of range, e.g., MONTH == 17.
1927 catch (IllegalArgumentException e) {
1928 parsePos.setErrorIndex(pos);
1929 parsePos.setIndex(start);
1930 if (backupTZ != null) {
1931 calendar.setTimeZone(backupTZ);
1935 // Set the parsed result if local calendar is used
1936 // instead of the input calendar
1937 if (resultCal != null) {
1938 resultCal.setTimeZone(cal.getTimeZone());
1939 resultCal.setTimeInMillis(cal.getTimeInMillis());
1941 // Restore the original time zone if required
1942 if (backupTZ != null) {
1943 calendar.setTimeZone(backupTZ);
1948 * Attempt to match the text at a given position against an array of
1949 * strings. Since multiple strings in the array may match (for
1950 * example, if the array contains "a", "ab", and "abc", all will match
1951 * the input string "abcd") the longest match is returned. As a side
1952 * effect, the given field of <code>cal</code> is set to the index
1953 * of the best match, if there is one.
1954 * @param text the time text being parsed.
1955 * @param start where to start parsing.
1956 * @param field the date field being parsed.
1957 * @param data the string array to parsed.
1958 * @return the new start position if matching succeeded; a negative
1959 * number indicating matching failure, otherwise. As a side effect,
1960 * sets the <code>cal</code> field <code>field</code> to the index
1961 * of the best match, if matching succeeded.
1964 protected int matchString(String text, int start, int field, String[] data, Calendar cal)
1967 int count = data.length;
1969 if (field == Calendar.DAY_OF_WEEK) i = 1;
1971 // There may be multiple strings in the data[] array which begin with
1972 // the same prefix (e.g., Cerven and Cervenec (June and July) in Czech).
1973 // We keep track of the longest match, and return that. Note that this
1974 // unfortunately requires us to test all array elements.
1975 int bestMatchLength = 0, bestMatch = -1;
1976 for (; i<count; ++i)
1978 int length = data[i].length();
1979 // Always compare if we have no match yet; otherwise only compare
1980 // against potentially better matches (longer strings).
1981 if (length > bestMatchLength &&
1982 text.regionMatches(true, start, data[i], 0, length))
1985 bestMatchLength = length;
1990 cal.set(field, bestMatch);
1991 return start + bestMatchLength;
1997 * Attempt to match the text at a given position against an array of quarter
1998 * strings. Since multiple strings in the array may match (for
1999 * example, if the array contains "a", "ab", and "abc", all will match
2000 * the input string "abcd") the longest match is returned. As a side
2001 * effect, the given field of <code>cal</code> is set to the index
2002 * of the best match, if there is one.
2003 * @param text the time text being parsed.
2004 * @param start where to start parsing.
2005 * @param field the date field being parsed.
2006 * @param data the string array to parsed.
2007 * @return the new start position if matching succeeded; a negative
2008 * number indicating matching failure, otherwise. As a side effect,
2009 * sets the <code>cal</code> field <code>field</code> to the index
2010 * of the best match, if matching succeeded.
2013 protected int matchQuarterString(String text, int start, int field, String[] data, Calendar cal)
2016 int count = data.length;
2018 // There may be multiple strings in the data[] array which begin with
2019 // the same prefix (e.g., Cerven and Cervenec (June and July) in Czech).
2020 // We keep track of the longest match, and return that. Note that this
2021 // unfortunately requires us to test all array elements.
2022 int bestMatchLength = 0, bestMatch = -1;
2023 for (; i<count; ++i) {
2024 int length = data[i].length();
2025 // Always compare if we have no match yet; otherwise only compare
2026 // against potentially better matches (longer strings).
2027 if (length > bestMatchLength &&
2028 text.regionMatches(true, start, data[i], 0, length)) {
2030 bestMatchLength = length;
2034 if (bestMatch >= 0) {
2035 cal.set(field, bestMatch * 3);
2036 return start + bestMatchLength;
2043 * Protected method that converts one field of the input string into a
2044 * numeric field value in <code>cal</code>. Returns -start (for
2045 * ParsePosition) if failed. Subclasses may override this method to
2046 * modify or add parsing capabilities.
2047 * @param text the time text to be parsed.
2048 * @param start where to start parsing.
2049 * @param ch the pattern character for the date field text to be parsed.
2050 * @param count the count of a pattern character.
2051 * @param obeyCount if true, then the next field directly abuts this one,
2052 * and we should use the count to know when to stop parsing.
2053 * @param ambiguousYear return parameter; upon return, if ambiguousYear[0]
2054 * is true, then a two-digit year was parsed and may need to be readjusted.
2055 * @return the new start position if matching succeeded; a negative
2056 * number indicating matching failure, otherwise. As a side effect,
2057 * set the appropriate field of <code>cal</code> with the parsed
2061 protected int subParse(String text, int start, char ch, int count,
2062 boolean obeyCount, boolean allowNegative,
2063 boolean[] ambiguousYear, Calendar cal)
2065 Number number = null;
2066 NumberFormat currentNumberFormat = null;
2069 ParsePosition pos = new ParsePosition(0);
2070 //int patternCharIndex = DateFormatSymbols.patternChars.indexOf(ch);c
2071 int patternCharIndex = -1;
2072 if ('A' <= ch && ch <= 'z') {
2073 patternCharIndex = PATTERN_CHAR_TO_INDEX[(int)ch - PATTERN_CHAR_BASE];
2076 if (patternCharIndex == -1) {
2080 currentNumberFormat = getNumberFormat(ch);
2082 int field = PATTERN_INDEX_TO_CALENDAR_FIELD[patternCharIndex];
2084 // If there are any spaces here, skip over them. If we hit the end
2085 // of the string, then fail.
2087 if (start >= text.length()) {
2090 int c = UTF16.charAt(text, start);
2091 if (!UCharacter.isUWhiteSpace(c)) {
2094 start += UTF16.getCharCount(c);
2096 pos.setIndex(start);
2098 // We handle a few special cases here where we need to parse
2099 // a number value. We handle further, more generic cases below. We need
2100 // to handle some of them here because some fields require extra processing on
2101 // the parsed value.
2102 if (patternCharIndex == 4 /*HOUR_OF_DAY1_FIELD*/ ||
2103 patternCharIndex == 15 /*HOUR1_FIELD*/ ||
2104 (patternCharIndex == 2 /*MONTH_FIELD*/ && count <= 2) ||
2105 patternCharIndex == 1 ||
2106 patternCharIndex == 8)
2108 // It would be good to unify this with the obeyCount logic below,
2109 // but that's going to be difficult.
2112 if ((start+count) > text.length()) return -start;
2113 number = parseInt(text, count, pos, allowNegative,currentNumberFormat);
2115 else number = parseInt(text, pos, allowNegative,currentNumberFormat);
2118 value = number.intValue();
2121 switch (patternCharIndex)
2123 case 0: // 'G' - ERA
2125 return matchString(text, start, Calendar.ERA, formatData.eraNames, cal);
2127 return matchString(text, start, Calendar.ERA, formatData.eras, cal);
2129 case 1: // 'y' - YEAR
2130 // If there are 3 or more YEAR pattern characters, this indicates
2131 // that the year value is to be treated literally, without any
2132 // two-digit year adjustments (e.g., from "01" to 2001). Otherwise
2133 // we made adjustments to place the 2-digit year in the proper
2134 // century, for parsed strings from "00" to "99". Any other string
2135 // is treated literally: "2250", "-1", "1", "002".
2136 /* 'yy' is the only special case, 'y' is interpreted as number. [Richard/GCL]*/
2137 if (count == 2 && (pos.getIndex() - start) == 2
2138 && UCharacter.isDigit(text.charAt(start))
2139 && UCharacter.isDigit(text.charAt(start+1)))
2141 // Assume for example that the defaultCenturyStart is 6/18/1903.
2142 // This means that two-digit years will be forced into the range
2143 // 6/18/1903 to 6/17/2003. As a result, years 00, 01, and 02
2144 // correspond to 2000, 2001, and 2002. Years 04, 05, etc. correspond
2145 // to 1904, 1905, etc. If the year is 03, then it is 2003 if the
2146 // other fields specify a date before 6/18, or 1903 if they specify a
2147 // date afterwards. As a result, 03 is an ambiguous year. All other
2148 // two-digit years are unambiguous.
2149 int ambiguousTwoDigitYear = getDefaultCenturyStartYear() % 100;
2150 ambiguousYear[0] = value == ambiguousTwoDigitYear;
2151 value += (getDefaultCenturyStartYear()/100)*100 +
2152 (value < ambiguousTwoDigitYear ? 100 : 0);
2154 cal.set(Calendar.YEAR, value);
2155 return pos.getIndex();
2156 case 2: // 'M' - MONTH
2157 if (count <= 2) // i.e., M or MM.
2159 // Don't want to parse the month if it is a string
2160 // while pattern uses numeric style: M or MM.
2161 // [We computed 'value' above.]
2162 cal.set(Calendar.MONTH, value - 1);
2163 return pos.getIndex();
2167 // count >= 3 // i.e., MMM or MMMM
2168 // Want to be able to parse both short and long forms.
2169 // Try count == 4 first:
2170 int newStart = matchString(text, start, Calendar.MONTH,
2171 formatData.months, cal);
2174 } else { // count == 4 failed, now try count == 3
2175 return matchString(text, start, Calendar.MONTH,
2176 formatData.shortMonths, cal);
2179 case 26: // 'L' - STAND_ALONE_MONTH
2180 if (count <= 2) // i.e., M or MM.
2182 // Don't want to parse the month if it is a string
2183 // while pattern uses numeric style: M or MM.
2184 // [We computed 'value' above.]
2185 cal.set(Calendar.MONTH, value - 1);
2186 return pos.getIndex();
2190 // count >= 3 // i.e., MMM or MMMM
2191 // Want to be able to parse both short and long forms.
2192 // Try count == 4 first:
2193 int newStart = matchString(text, start, Calendar.MONTH,
2194 formatData.standaloneMonths, cal);
2197 } else { // count == 4 failed, now try count == 3
2198 return matchString(text, start, Calendar.MONTH,
2199 formatData.standaloneShortMonths, cal);
2202 case 4: // 'k' - HOUR_OF_DAY (1..24)
2203 // [We computed 'value' above.]
2204 if (value == cal.getMaximum(Calendar.HOUR_OF_DAY)+1) value = 0;
2205 cal.set(Calendar.HOUR_OF_DAY, value);
2206 return pos.getIndex();
2207 case 8: // 'S' - FRACTIONAL_SECOND
2208 // Fractional seconds left-justify
2209 i = pos.getIndex() - start;
2221 value = (value + (a>>1)) / a;
2223 cal.set(Calendar.MILLISECOND, value);
2224 return pos.getIndex();
2225 case 9: { // 'E' - DAY_OF_WEEK
2226 // Want to be able to parse both short and long forms.
2227 // Try count == 4 (EEEE) first:
2228 int newStart = matchString(text, start, Calendar.DAY_OF_WEEK,
2229 formatData.weekdays, cal);
2232 } else { // EEEE failed, now try EEE
2233 return matchString(text, start, Calendar.DAY_OF_WEEK,
2234 formatData.shortWeekdays, cal);
2237 case 25: { // 'c' - STAND_ALONE_DAY_OF_WEEK
2238 // Want to be able to parse both short and long forms.
2239 // Try count == 4 (cccc) first:
2240 int newStart = matchString(text, start, Calendar.DAY_OF_WEEK,
2241 formatData.standaloneWeekdays, cal);
2244 } else { // cccc failed, now try ccc
2245 return matchString(text, start, Calendar.DAY_OF_WEEK,
2246 formatData.standaloneShortWeekdays, cal);
2249 case 14: // 'a' - AM_PM
2250 return matchString(text, start, Calendar.AM_PM, formatData.ampms, cal);
2251 case 15: // 'h' - HOUR (1..12)
2252 // [We computed 'value' above.]
2253 if (value == cal.getLeastMaximum(Calendar.HOUR)+1) value = 0;
2254 cal.set(Calendar.HOUR, value);
2255 return pos.getIndex();
2256 case 17: // 'z' - ZONE_OFFSET
2257 case 23: // 'Z' - TIMEZONE_RFC
2258 case 24: // 'v' - TIMEZONE_GENERIC
2259 case 29: // 'V' - TIMEZONE_SPECIAL
2263 boolean parsed = false;
2266 // Check if this is a long GMT offset string (either localized or default)
2267 Integer gmtoff = parseGMT(text, pos, currentNumberFormat);
2268 if (gmtoff != null) {
2269 offset = gmtoff.intValue();
2275 // Check if this is an RFC822 time zone offset.
2276 // ICU supports the standard RFC822 format [+|-]HHmm
2277 // and its extended form [+|-]HHmmSS.
2281 char signChar = text.charAt(start);
2282 if (signChar == '+') {
2284 } else if (signChar == '-') {
2287 // Not an RFC822 offset string
2292 int orgPos = start + 1;
2293 pos.setIndex(orgPos);
2294 number = parseInt(text, 6, pos, false,currentNumberFormat);
2295 int numLen = pos.getIndex() - orgPos;
2300 // Followings are possible format (excluding sign char)
2307 int val = number.intValue();
2308 int hour = 0, min = 0, sec = 0;
2322 min = (val % 10000) / 100;
2326 if (hour > 23 || min > 59 || sec > 59) {
2327 // Invalid value range
2330 offset = (((hour * 60) + min) * 60 + sec) * 1000 * sign;
2335 // Failed to parse. Reset the position.
2336 pos.setIndex(start);
2341 // offset was successfully parsed as either a long GMT string or RFC822 zone offset
2342 // string. Create normalized zone ID for the offset.
2343 tz = ZoneMeta.getCustomTimeZone(offset);
2344 cal.setTimeZone(tz);
2345 return pos.getIndex();
2349 // At this point, check for named time zones by looking through
2350 // the locale data from the DateFormatZoneData strings.
2351 // Want to be able to parse both short and long forms.
2352 // optimize for calendar's current time zone
2353 ZoneStringInfo zsinfo = null;
2354 switch (patternCharIndex) {
2355 case 17: // 'z' - ZONE_OFFSET
2357 zsinfo = formatData.getZoneStringFormat().findSpecificShort(text, start);
2359 zsinfo = formatData.getZoneStringFormat().findSpecificLong(text, start);
2362 case 24: // 'v' - TIMEZONE_GENERIC
2364 zsinfo = formatData.getZoneStringFormat().findGenericShort(text, start);
2365 } else if (count == 4) {
2366 zsinfo = formatData.getZoneStringFormat().findGenericLong(text, start);
2369 case 29: // 'V' - TIMEZONE_SPECIAL
2371 zsinfo = formatData.getZoneStringFormat().findSpecificShort(text, start);
2372 } else if (count == 4) {
2373 zsinfo = formatData.getZoneStringFormat().findGenericLocation(text, start);
2377 if (zsinfo != null) {
2378 if (zsinfo.isStandard()) {
2379 tztype = TZTYPE_STD;
2380 } else if (zsinfo.isDaylight()) {
2381 tztype = TZTYPE_DST;
2383 tz = TimeZone.getTimeZone(zsinfo.getID());
2384 cal.setTimeZone(tz);
2385 return start + zsinfo.getString().length();
2388 // Final attempt - is this standalone GMT/UT/UTC?
2390 if (text.regionMatches(true, start, STR_GMT, 0, STR_GMT_LEN)) {
2391 gmtLen = STR_GMT_LEN;
2392 } else if (text.regionMatches(true, start, STR_UTC, 0, STR_UTC_LEN)) {
2393 gmtLen = STR_UTC_LEN;
2394 } else if (text.regionMatches(true, start, STR_UT, 0, STR_UT_LEN)) {
2395 gmtLen = STR_UT_LEN;
2398 tz = TimeZone.getTimeZone("Etc/GMT");
2399 cal.setTimeZone(tz);
2400 return start + gmtLen;
2407 case 27: // 'Q' - QUARTER
2408 if (count <= 2) // i.e., Q or QQ.
2410 // Don't want to parse the quarter if it is a string
2411 // while pattern uses numeric style: Q or QQ.
2412 // [We computed 'value' above.]
2413 cal.set(Calendar.MONTH, (value - 1) * 3);
2414 return pos.getIndex();
2418 // count >= 3 // i.e., QQQ or QQQQ
2419 // Want to be able to parse both short and long forms.
2420 // Try count == 4 first:
2421 int newStart = matchQuarterString(text, start, Calendar.MONTH,
2422 formatData.quarters, cal);
2425 } else { // count == 4 failed, now try count == 3
2426 return matchQuarterString(text, start, Calendar.MONTH,
2427 formatData.shortQuarters, cal);
2431 case 28: // 'q' - STANDALONE QUARTER
2432 if (count <= 2) // i.e., q or qq.
2434 // Don't want to parse the quarter if it is a string
2435 // while pattern uses numeric style: q or qq.
2436 // [We computed 'value' above.]
2437 cal.set(Calendar.MONTH, (value - 1) * 3);
2438 return pos.getIndex();
2442 // count >= 3 // i.e., qqq or qqqq
2443 // Want to be able to parse both short and long forms.
2444 // Try count == 4 first:
2445 int newStart = matchQuarterString(text, start, Calendar.MONTH,
2446 formatData.standaloneQuarters, cal);
2449 } else { // count == 4 failed, now try count == 3
2450 return matchQuarterString(text, start, Calendar.MONTH,
2451 formatData.standaloneShortQuarters, cal);
2456 // case 3: // 'd' - DATE
2457 // case 5: // 'H' - HOUR_OF_DAY (0..23)
2458 // case 6: // 'm' - MINUTE
2459 // case 7: // 's' - SECOND
2460 // case 10: // 'D' - DAY_OF_YEAR
2461 // case 11: // 'F' - DAY_OF_WEEK_IN_MONTH
2462 // case 12: // 'w' - WEEK_OF_YEAR
2463 // case 13: // 'W' - WEEK_OF_MONTH
2464 // case 16: // 'K' - HOUR (0..11)
2465 // case 18: // 'Y' - YEAR_WOY
2466 // case 19: // 'e' - DOW_LOCAL
2467 // case 20: // 'u' - EXTENDED_YEAR
2468 // case 21: // 'g' - JULIAN_DAY
2469 // case 22: // 'A' - MILLISECONDS_IN_DAY
2471 // Handle "generic" fields
2474 if ((start+count) > text.length()) return -start;
2475 number = parseInt(text, count, pos, allowNegative,currentNumberFormat);
2477 else number = parseInt(text, pos, allowNegative,currentNumberFormat);
2478 if (number != null) {
2479 cal.set(field, number.intValue());
2480 return pos.getIndex();
2487 * Parse an integer using numberFormat. This method is semantically
2488 * const, but actually may modify fNumberFormat.
2490 private Number parseInt(String text,
2492 boolean allowNegative,
2494 return parseInt(text, -1, pos, allowNegative, fmt);
2498 * Parse an integer using numberFormat up to maxDigits.
2500 private Number parseInt(String text,
2503 boolean allowNegative,
2506 int oldPos = pos.getIndex();
2507 if (allowNegative) {
2508 number = fmt.parse(text, pos);
2510 // Invalidate negative numbers
2511 if (fmt instanceof DecimalFormat) {
2512 String oldPrefix = ((DecimalFormat)fmt).getNegativePrefix();
2513 ((DecimalFormat)fmt).setNegativePrefix(SUPPRESS_NEGATIVE_PREFIX);
2514 number = fmt.parse(text, pos);
2515 ((DecimalFormat)fmt).setNegativePrefix(oldPrefix);
2517 boolean dateNumberFormat = (fmt instanceof DateNumberFormat);
2518 if (dateNumberFormat) {
2519 ((DateNumberFormat)fmt).setParsePositiveOnly(true);
2521 number = fmt.parse(text, pos);
2522 if (dateNumberFormat) {
2523 ((DateNumberFormat)fmt).setParsePositiveOnly(false);
2527 if (maxDigits > 0) {
2528 // adjust the result to fit into
2529 // the maxDigits and move the position back
2530 int nDigits = pos.getIndex() - oldPos;
2531 if (nDigits > maxDigits) {
2532 double val = number.doubleValue();
2533 nDigits -= maxDigits;
2534 while (nDigits > 0) {
2538 pos.setIndex(oldPos + maxDigits);
2539 number = new Integer((int)val);
2547 * Translate a pattern, mapping each character in the from string to the
2548 * corresponding character in the to string.
2550 private String translatePattern(String pat, String from, String to) {
2551 StringBuffer result = new StringBuffer();
2552 boolean inQuote = false;
2553 for (int i = 0; i < pat.length(); ++i) {
2554 char c = pat.charAt(i);
2562 else if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) {
2563 int ci = from.indexOf(c);
2567 // do not worry on translatepattern if the character is not listed
2568 // we do the validity check elsewhere
2574 throw new IllegalArgumentException("Unfinished quote in pattern");
2575 return result.toString();
2579 * Return a pattern string describing this date format.
2582 public String toPattern() {
2587 * Return a localized pattern string describing this date format.
2590 public String toLocalizedPattern() {
2591 return translatePattern(pattern,
2592 DateFormatSymbols.patternChars,
2593 formatData.localPatternChars);
2597 * Apply the given unlocalized pattern string to this date format.
2600 public void applyPattern(String pat)
2603 setLocale(null, null);
2604 // reset parsed pattern items
2605 patternItems = null;
2609 * Apply the given localized pattern string to this date format.
2612 public void applyLocalizedPattern(String pat) {
2613 this.pattern = translatePattern(pat,
2614 formatData.localPatternChars,
2615 DateFormatSymbols.patternChars);
2616 setLocale(null, null);
2620 * Gets the date/time formatting data.
2621 * @return a copy of the date-time formatting data associated
2622 * with this date-time formatter.
2625 public DateFormatSymbols getDateFormatSymbols()
2627 return (DateFormatSymbols)formatData.clone();
2631 * Allows you to set the date/time formatting data.
2632 * @param newFormatSymbols the new symbols
2635 public void setDateFormatSymbols(DateFormatSymbols newFormatSymbols)
2637 this.formatData = (DateFormatSymbols)newFormatSymbols.clone();
2642 * Method for subclasses to access the DateFormatSymbols.
2645 protected DateFormatSymbols getSymbols() {
2650 * Overrides Cloneable
2653 public Object clone() {
2654 SimpleDateFormat other = (SimpleDateFormat) super.clone();
2655 other.formatData = (DateFormatSymbols) formatData.clone();
2660 * Override hashCode.
2661 * Generates the hash code for the SimpleDateFormat object
2664 public int hashCode()
2666 return pattern.hashCode();
2667 // just enough fields for a reasonable distribution
2674 public boolean equals(Object obj)
2676 if (!super.equals(obj)) return false; // super does class check
2677 SimpleDateFormat that = (SimpleDateFormat) obj;
2678 return (pattern.equals(that.pattern)
2679 && formatData.equals(that.formatData));
2683 * Override writeObject.
2685 private void writeObject(ObjectOutputStream stream) throws IOException{
2686 if (defaultCenturyStart == null) {
2687 // if defaultCenturyStart is not yet initialized,
2688 // calculate and set value before serialization.
2689 initializeDefaultCenturyStart(defaultCenturyBase);
2691 stream.defaultWriteObject();
2695 * Override readObject.
2697 private void readObject(ObjectInputStream stream)
2698 throws IOException, ClassNotFoundException {
2699 stream.defaultReadObject();
2701 // don't have old serial data to test with
2702 if (serialVersionOnStream < 1) {
2703 // didn't have defaultCenturyStart field
2704 defaultCenturyBase = System.currentTimeMillis();
2708 // fill in dependent transient field
2709 parseAmbiguousDatesAsAfter(defaultCenturyStart);
2711 serialVersionOnStream = currentSerialVersion;
2712 locale = getLocale(ULocale.VALID_LOCALE);
2714 initLocalZeroPaddingNumberFormat();
2717 //#if defined(FOUNDATION10) || defined(J2SE13)
2720 * Format the object to an attributed string, and return the corresponding iterator
2721 * Overrides superclass method.
2723 * @param obj The object to format
2724 * @return <code>AttributedCharacterIterator</code> describing the formatted value.
2728 public AttributedCharacterIterator formatToCharacterIterator(Object obj) {
2729 Calendar cal = calendar;
2730 if (obj instanceof Calendar) {
2731 cal = (Calendar)obj;
2732 } else if (obj instanceof Date) {
2733 calendar.setTime((Date)obj);
2734 } else if (obj instanceof Number) {
2735 calendar.setTimeInMillis(((Number)obj).longValue());
2737 throw new IllegalArgumentException("Cannot format given Object as a Date");
2739 StringBuffer toAppendTo = new StringBuffer();
2740 FieldPosition pos = new FieldPosition(0);
2741 List attributes = new LinkedList();
2742 format(cal, toAppendTo, pos, attributes);
2744 AttributedString as = new AttributedString(toAppendTo.toString());
2746 // add DateFormat field attributes to the AttributedString
2747 for (int i = 0; i < attributes.size(); i++) {
2748 FieldPosition fp = (FieldPosition) attributes.get(i);
2749 Format.Field attribute = fp.getFieldAttribute();
2750 as.addAttribute(attribute, attribute, fp.getBeginIndex(), fp.getEndIndex());
2752 // return the CharacterIterator from AttributedString
2753 return as.getIterator();
2759 * Get the locale of this simple date formatter.
2760 * It is package accessible. also used in DateIntervalFormat.
2762 * @return locale in this simple date formatter
2772 * Check whether the 'field' is smaller than all the fields covered in
2773 * pattern, return true if it is.
2774 * The sequence of calendar field,
2775 * from large to small is: ERA, YEAR, MONTH, DATE, AM_PM, HOUR, MINUTE,...
2776 * @param field the calendar field need to check against
2777 * @return true if the 'field' is smaller than all the fields
2778 * covered in pattern. false otherwise.
2781 boolean isFieldUnitIgnored(int field) {
2782 return isFieldUnitIgnored(pattern, field);
2787 * Check whether the 'field' is smaller than all the fields covered in
2788 * pattern, return true if it is.
2789 * The sequence of calendar field,
2790 * from large to small is: ERA, YEAR, MONTH, DATE, AM_PM, HOUR, MINUTE,...
2791 * @param pattern the pattern to check against
2792 * @param field the calendar field need to check against
2793 * @return true if the 'field' is smaller than all the fields
2794 * covered in pattern. false otherwise.
2797 static boolean isFieldUnitIgnored(String pattern, int field) {
2798 int fieldLevel = CALENDAR_FIELD_TO_LEVEL[field];
2801 boolean inQuote = false;
2805 for (int i = 0; i < pattern.length(); ++i) {
2806 ch = pattern.charAt(i);
2807 if (ch != prevCh && count > 0) {
2808 level = PATTERN_CHAR_TO_LEVEL[prevCh - PATTERN_CHAR_BASE];
2809 if ( fieldLevel <= level ) {
2815 if ((i+1) < pattern.length() && pattern.charAt(i+1) == '\'') {
2818 inQuote = ! inQuote;
2821 else if ( ! inQuote && ((ch >= 0x0061 /*'a'*/ && ch <= 0x007A /*'z'*/)
2822 || (ch >= 0x0041 /*'A'*/ && ch <= 0x005A /*'Z'*/))) {
2829 level = PATTERN_CHAR_TO_LEVEL[prevCh - PATTERN_CHAR_BASE];
2830 if ( fieldLevel <= level ) {
2839 * Format date interval by algorithm.
2840 * It is supposed to be used only by CLDR survey tool.
2842 * @param fromCalendar calendar set to the from date in date interval
2843 * to be formatted into date interval stirng
2844 * @param toCalendar calendar set to the to date in date interval
2845 * to be formatted into date interval stirng
2846 * @param appendTo Output parameter to receive result.
2847 * Result is appended to existing contents.
2848 * @param pos On input: an alignment field, if desired.
2849 * On output: the offsets of the alignment field.
2850 * @exception IllegalArgumentException when there is non-recognized
2852 * @return Reference to 'appendTo' parameter.
2855 public final StringBuffer intervalFormatByAlgorithm(Calendar fromCalendar,
2856 Calendar toCalendar,
2857 StringBuffer appendTo,
2859 throws IllegalArgumentException
2861 // not support different calendar types and time zones
2862 if ( !fromCalendar.isEquivalentTo(toCalendar) ) {
2863 throw new IllegalArgumentException("can not format on two different calendars");
2866 Object[] items = getPatternItems();
2870 /* look for different formatting string range */
2871 // look for start of difference
2873 for (int i = 0; i < items.length; i++) {
2874 if ( diffCalFieldValue(fromCalendar, toCalendar, items, i) ) {
2880 if ( diffBegin == -1 ) {
2881 // no difference, single date format
2882 return format(fromCalendar, appendTo, pos);
2885 // look for end of difference
2886 for (int i = items.length-1; i >= diffBegin; i--) {
2887 if ( diffCalFieldValue(fromCalendar, toCalendar, items, i) ) {
2892 } catch ( IllegalArgumentException e ) {
2893 throw new IllegalArgumentException(e.toString());
2896 // full range is different
2897 if ( diffBegin == 0 && diffEnd == items.length-1 ) {
2898 format(fromCalendar, appendTo, pos);
2899 appendTo.append(" \u2013 "); // default separator
2900 format(toCalendar, appendTo, pos);
2905 /* search for largest calendar field within the different range */
2906 int highestLevel = 1000;
2907 for (int i = diffBegin; i <= diffEnd; i++) {
2908 if ( items[i] instanceof String) {
2911 PatternItem item = (PatternItem)items[i];
2912 char ch = item.type;
2913 int patternCharIndex = -1;
2914 if ('A' <= ch && ch <= 'z') {
2915 patternCharIndex = PATTERN_CHAR_TO_LEVEL[(int)ch - PATTERN_CHAR_BASE];
2918 if (patternCharIndex == -1) {
2919 throw new IllegalArgumentException("Illegal pattern character " +
2920 "'" + ch + "' in \"" +
2921 new String(pattern) + '"');
2924 if ( patternCharIndex < highestLevel ) {
2925 highestLevel = patternCharIndex;
2929 /* re-calculate diff range, including those calendar field which
2930 is in lower level than the largest calendar field covered
2931 in diff range calculated. */
2933 for (int i = 0; i < diffBegin; i++) {
2934 if ( lowerLevel(items, i, highestLevel) ) {
2941 for (int i = items.length-1; i > diffEnd; i--) {
2942 if ( lowerLevel(items, i, highestLevel) ) {
2947 } catch ( IllegalArgumentException e ) {
2948 throw new IllegalArgumentException(e.toString());
2952 // full range is different
2953 if ( diffBegin == 0 && diffEnd == items.length-1 ) {
2954 format(fromCalendar, appendTo, pos);
2955 appendTo.append(" \u2013 "); // default separator
2956 format(toCalendar, appendTo, pos);
2963 pos.setBeginIndex(0);
2966 // formatting date 1
2967 for (int i = 0; i <= diffEnd; i++) {
2968 if (items[i] instanceof String) {
2969 appendTo.append((String)items[i]);
2971 PatternItem item = (PatternItem)items[i];
2972 if (useFastFormat) {
2973 subFormat(appendTo, item.type, item.length, appendTo.length(), pos, fromCalendar);
2975 appendTo.append(subFormat(item.type, item.length, appendTo.length(), pos, formatData, fromCalendar));
2980 appendTo.append(" \u2013 "); // default separator
2982 // formatting date 2
2983 for (int i = diffBegin; i < items.length; i++) {
2984 if (items[i] instanceof String) {
2985 appendTo.append((String)items[i]);
2987 PatternItem item = (PatternItem)items[i];
2988 if (useFastFormat) {
2989 subFormat(appendTo, item.type, item.length, appendTo.length(), pos, toCalendar);
2991 appendTo.append(subFormat(item.type, item.length, appendTo.length(), pos, formatData, toCalendar));
3000 * check whether the i-th item in 2 calendar is in different value.
3002 * It is supposed to be used only by CLDR survey tool.
3003 * It is used by intervalFormatByAlgorithm().
3005 * @param fromCalendar one calendar
3006 * @param toCalendar the other calendar
3007 * @param items pattern items
3008 * @param i the i-th item in pattern items
3009 * @exception IllegalArgumentException when there is non-recognized
3011 * @return true is i-th item in 2 calendar is in different
3012 * value, false otherwise.
3014 private boolean diffCalFieldValue(Calendar fromCalendar,
3015 Calendar toCalendar,
3017 int i) throws IllegalArgumentException {
3018 if ( items[i] instanceof String) {
3021 PatternItem item = (PatternItem)items[i];
3022 char ch = item.type;
3023 int patternCharIndex = -1;
3024 if ('A' <= ch && ch <= 'z') {
3025 patternCharIndex = PATTERN_CHAR_TO_INDEX[(int)ch - PATTERN_CHAR_BASE];
3028 if (patternCharIndex == -1) {
3029 throw new IllegalArgumentException("Illegal pattern character " +
3030 "'" + ch + "' in \"" +
3031 new String(pattern) + '"');
3034 final int field = PATTERN_INDEX_TO_CALENDAR_FIELD[patternCharIndex];
3035 int value = fromCalendar.get(field);
3036 int value_2 = toCalendar.get(field);
3037 if ( value != value_2 ) {
3045 * check whether the i-th item's level is lower than the input 'level'
3047 * It is supposed to be used only by CLDR survey tool.
3048 * It is used by intervalFormatByAlgorithm().
3050 * @param items the pattern items
3051 * @param i the i-th item in pattern items
3052 * @param level the level with which the i-th pattern item compared to
3053 * @exception IllegalArgumentException when there is non-recognized
3055 * @return true if i-th pattern item is lower than 'level',
3058 private boolean lowerLevel(Object[] items, int i, int level)
3059 throws IllegalArgumentException {
3060 if ( items[i] instanceof String) {
3063 PatternItem item = (PatternItem)items[i];
3064 char ch = item.type;
3065 int patternCharIndex = -1;
3066 if ('A' <= ch && ch <= 'z') {
3067 patternCharIndex = PATTERN_CHAR_TO_LEVEL[(int)ch - PATTERN_CHAR_BASE];
3070 if (patternCharIndex == -1) {
3071 throw new IllegalArgumentException("Illegal pattern character " +
3072 "'" + ch + "' in \"" +
3073 new String(pattern) + '"');
3076 if ( patternCharIndex >= level ) {
3082 protected NumberFormat getNumberFormat(char ch) {
3085 ovrField = new Character(ch);
3086 if (overrideMap != null && overrideMap.containsKey(ovrField)) {
3087 String nsName = overrideMap.get(ovrField).toString();
3088 NumberFormat nf = (NumberFormat)numberFormatters.get(nsName);
3091 return numberFormat;
3095 private void initNumberFormatters(ULocale loc) {
3097 numberFormatters = new HashMap();
3098 overrideMap = new HashMap();
3099 processOverrideString(loc,override);
3103 private void processOverrideString(ULocale loc, String str) {
3105 if ( str == null || str.length() == 0 )
3112 boolean moreToProcess = true;
3113 boolean fullOverride;
3115 while (moreToProcess) {
3116 int delimiterPosition = str.indexOf(";",start);
3117 if (delimiterPosition == -1) {
3118 moreToProcess = false;
3121 end = delimiterPosition;
3124 String currentString = str.substring(start,end);
3125 int equalSignPosition = currentString.indexOf("=");
3126 if (equalSignPosition == -1) { // Simple override string such as "hebrew"
3127 nsName = currentString;
3128 fullOverride = true;
3129 } else { // Field specific override string such as "y=hebrew"
3130 nsName = currentString.substring(equalSignPosition+1);
3131 ovrField = new Character(currentString.charAt(0));
3132 overrideMap.put(ovrField,nsName);
3133 fullOverride = false;
3136 ULocale ovrLoc = new ULocale(loc.getBaseName()+"@numbers="+nsName);
3137 NumberFormat nf = NumberFormat.createInstance(ovrLoc,NumberFormat.NUMBERSTYLE);
3140 setNumberFormat(nf);
3142 // Since one or more of the override number formatters might be complex,
3143 // we can't rely on the fast numfmt where we have a partial field override.
3144 useLocalZeroPaddingNumberFormat = false;
3147 if (!numberFormatters.containsKey(nsName)) {
3148 numberFormatters.put(nsName,nf);
3151 start = delimiterPosition + 1;