]> gitweb.fperrin.net Git - Dictionary.git/blob - jars/icu4j-4_2_1-src/src/com/ibm/icu/text/SimpleDateFormat.java
go
[Dictionary.git] / jars / icu4j-4_2_1-src / src / com / ibm / icu / text / SimpleDateFormat.java
1 //##header J2SE15
2 /*
3  *******************************************************************************
4  * Copyright (C) 1996-2009, International Business Machines Corporation and    *
5  * others. All Rights Reserved.                                                *
6  *******************************************************************************
7  */
8
9 package com.ibm.icu.text;
10
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)
25 //#else
26 import java.text.AttributedCharacterIterator;
27 import java.text.AttributedString;
28 import java.text.Format;
29 import java.util.LinkedList;
30 //#endif
31
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;
46
47
48 /**
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.
52  *
53  * <p>
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
63  * {@link DateFormat}.
64  *
65  * <p>
66  * <strong>Time Format Syntax:</strong>
67  * <p>
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:
71  * <blockquote>
72  * <pre>
73  * Symbol   Meaning                 Presentation        Example
74  * ------   -------                 ------------        -------
75  * G        era designator          (Text)              AD
76  * y&#x2020;       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'
107  * </pre>
108  * </blockquote>
109  * <tt><b>*</b></tt> These items are not supported by Java's SimpleDateFormat.<br>
110  * <tt><b>&#x2020;</b></tt> ICU interprets a single 'y' differently than Java.</p>
111  * <p>
112  * The count of pattern letters determine the format.
113  * <p>
114  * <strong>(Text)</strong>: 4 or more pattern letters--use full form,
115  * &lt; 4--use short or abbreviated form if one exists.
116  * <p>
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.
122  * <p>
123  * <strong>(Text & Number)</strong>: 3 or over, use text, otherwise use number.
124  * <p>
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.
129  * <p>
130  * A pattern containing any invalid pattern letter will result in a thrown
131  * exception during formatting or parsing.
132  *
133  * <p>
134  * <strong>Examples Using the US Locale:</strong>
135  * <blockquote>
136  * <pre>
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
145  * </pre>
146  * </blockquote>
147  * <strong>Code Sample:</strong>
148  * <blockquote>
149  * <pre>
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);
153  * <br>
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);
159  * <br>
160  * // Parse the previous string back into a Date.
161  * ParsePosition pos = new ParsePosition(0);
162  * Date currentTime_2 = formatter.parse(dateString, pos);
163  * </pre>
164  * </blockquote>
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.
170  *
171  * <p>
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
182  * century.
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.
187  *
188  * <p>
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.
192  *
193  * <p>
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.
204  *
205  * <p>
206  * For time zones that have no names, use strings GMT+hours:minutes or
207  * GMT-hours:minutes.
208  *
209  * <p>
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.
214  *
215  * <h4>Synchronization</h4>
216  *
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.
220  *
221  * @see          com.ibm.icu.util.Calendar
222  * @see          com.ibm.icu.util.GregorianCalendar
223  * @see          com.ibm.icu.util.TimeZone
224  * @see          DateFormat
225  * @see          DateFormatSymbols
226  * @see          DecimalFormat
227  * @author       Mark Davis, Chen-Lieh Huang, Alan Liu
228  * @stable ICU 2.0
229  */
230 public class SimpleDateFormat extends DateFormat {
231
232     // the official serial version ID which says cryptically
233     // which version we're compatible with
234     private static final long serialVersionUID = 4774881970558875024L;
235
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;
240
241     
242     /*
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
249      */
250     private static final int[] CALENDAR_FIELD_TO_LEVEL =
251     {
252         /*GyM*/ 0, 10, 20,
253         /*wW*/ 20, 30,
254         /*dDEF*/ 30, 20, 30, 30,
255         /*ahHm*/ 40, 50, 50, 60,
256         /*sS..*/ 70, 80, 
257         /*z?Y*/ 0, 0, 10, 
258         /*eug*/ 30, 10, 0,
259         /*A*/ 40 
260     };
261
262
263     
264     /*
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
270      */
271     private static final int[] PATTERN_CHAR_TO_LEVEL = 
272     {
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
281     };
282
283
284     /**
285      * The version of the serialized data on the stream.  Possible values:
286      * <ul>
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>.
291      * </ul>
292      * When streaming out this class, the most recent format
293      * and the highest allowable <code>serialVersionOnStream</code>
294      * is written.
295      * @serial
296      */
297     private int serialVersionOnStream = currentSerialVersion;
298
299     /**
300      * The pattern string of this formatter.  This is always a non-localized
301      * pattern.  May not be null.  See class documentation for details.
302      * @serial
303      */
304     private String pattern;
305
306     /**
307      * The override string of this formatter.  Used to override the
308      * numbering system for one or more fields.
309      * @serial
310      */
311     private String override;
312
313     /**
314      * The hash map used for number format overrides.
315      * @serial
316      */
317     private HashMap numberFormatters;
318
319     /**
320      * The hash map used for number format overrides.
321      * @serial
322      */
323     private HashMap overrideMap;
324
325     /**
326      * The symbols used by this formatter for week names, month names,
327      * etc.  May not be null.
328      * @serial
329      * @see DateFormatSymbols
330      */
331     private DateFormatSymbols formatData;
332
333     private transient ULocale locale;
334
335     /**
336      * We map dates with two-digit years into the century starting at
337      * <code>defaultCenturyStart</code>, which may be any date.  May
338      * not be null.
339      * @serial
340      * @since JDK1.1.4
341      */
342     private Date defaultCenturyStart;
343
344     private transient int defaultCenturyStartYear;
345
346     // defaultCenturyBase is set when an instance is created
347     // and may be used for calculating defaultCenturyStart when needed.
348     private transient long defaultCenturyBase;
349
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;
354
355     private static final int millisPerHour = 60 * 60 * 1000;
356     private static final int millisPerMinute = 60 * 1000;
357     private static final int millisPerSecond = 1000;
358
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";
363
364     /**
365      * If true, this object supports fast formatting using the
366      * subFormat variant that takes a StringBuffer.
367      */
368     private transient boolean useFastFormat;
369
370     /**
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.
374      *
375      * @see DateFormat
376      * @stable ICU 2.0
377      */
378     public SimpleDateFormat() {
379         this(getDefaultPattern(), null, null, null, null, true, null);
380     }
381
382     /**
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.
386      * @stable ICU 2.0
387      */
388     public SimpleDateFormat(String pattern)
389     {
390         this(pattern, null, null, null, null, true, null);
391     }
392
393     /**
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.
397      * @stable ICU 2.0
398      */
399     public SimpleDateFormat(String pattern, Locale loc)
400     {
401         this(pattern, null, null, null, ULocale.forLocale(loc), true, null);
402     }
403
404     /**
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.
408      * @stable ICU 3.2
409      */
410     public SimpleDateFormat(String pattern, ULocale loc)
411     {
412         this(pattern, null, null, null, loc, true, null);
413     }
414
415     /**
416      * Construct a SimpleDateFormat using the given pattern , override and locale.
417      * @draft ICU 4.2
418      * @provisional This API might change or be removed in a future release.
419      */
420     public SimpleDateFormat(String pattern, String override, ULocale loc)
421     {
422         this(pattern, null, null, null, loc, false,override);
423     }
424
425     /**
426      * Construct a SimpleDateFormat using the given pattern and
427      * locale-specific symbol data.
428      * Warning: uses default locale for digits!
429      * @stable ICU 2.0
430      */
431     public SimpleDateFormat(String pattern, DateFormatSymbols formatData)
432     {
433         this(pattern, (DateFormatSymbols)formatData.clone(), null, null, null, true, null);
434     }
435
436     /**
437      * @internal ICU 3.2
438      * @deprecated This API is ICU internal only.
439      */
440     public SimpleDateFormat(String pattern, DateFormatSymbols formatData, ULocale loc)
441     {
442         this(pattern, (DateFormatSymbols)formatData.clone(), null, null, loc, true,null);
443     }
444
445     /**
446      * Package-private constructor that allows a subclass to specify
447      * whether it supports fast formatting.
448      *
449      * TODO make this API public.
450      */
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);
454     }
455
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);
459     }
460
461     /*
462      * The constructor called from all other SimpleDateFormat constructors
463      */
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;
473         initialize();
474     }
475
476     /**
477      * Create an instance of SimpleDateForamt for the given format configuration
478      * @param formatConfig the format configuration
479      * @return A SimpleDateFormat instance
480      * @internal ICU 3.8
481      * @deprecated This API is ICU internal only.
482      */
483     public static SimpleDateFormat getInstance(Calendar.FormatConfiguration formatConfig) {
484        
485         String ostr = formatConfig.getOverrideString();
486         boolean useFast = ( ostr != null && ostr.length() > 0 );
487
488         return new SimpleDateFormat(formatConfig.getPatternString(),
489                     formatConfig.getDateFormatSymbols(),
490                     formatConfig.getCalendar(),
491                     null,
492                     formatConfig.getLocale(),
493                     useFast,
494                     formatConfig.getOverrideString());
495     }
496
497     /*
498      * Initialized fields
499      */
500     private void initialize() {
501         if (locale == null) {
502             locale = ULocale.getDefault();
503         }
504         if (formatData == null) {
505             formatData = new DateFormatSymbols(locale);
506         }
507         if (calendar == null) {
508             calendar = Calendar.getInstance(locale);
509         }
510         if (numberFormat == null) {
511             NumberingSystem ns = NumberingSystem.getInstance(locale);
512             if ( ns.isAlgorithmic() ) {
513                 numberFormat = NumberFormat.getInstance(locale);
514             } else {
515                 char digit0 = ns.getDescription().charAt(0);
516                 // Use a NumberFormat optimized for date formatting
517                 numberFormat = new DateNumberFormat(locale, digit0);
518             }
519         }
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();
523
524         setLocale(calendar.getLocale(ULocale.VALID_LOCALE ), calendar.getLocale(ULocale.ACTUAL_LOCALE));
525         initLocalZeroPaddingNumberFormat();
526
527         if (override != null) {
528            initNumberFormatters(locale);
529         }
530
531     }
532
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";
537
538     /*
539      * Returns the default date and time pattern (SHORT) for the default locale.
540      * This method is only used by the default SimpleDateFormat constructor.
541      */
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);
547             try {
548                 CalendarData calData = new CalendarData(cachedDefaultLocale, cal.getType());
549                 String[] dateTimePatterns = calData.getDateTimePatterns();
550                 int glueIndex = 8;
551                 if (dateTimePatterns.length >= 13)
552                 {
553                     glueIndex += (SHORT + 1);
554                 }
555                 cachedDefaultPattern = MessageFormat.format(dateTimePatterns[glueIndex],
556                         new Object[] {dateTimePatterns[SHORT], dateTimePatterns[SHORT + 4]});
557             } catch (MissingResourceException e) {
558                 cachedDefaultPattern = FALLBACKPATTERN;
559             }
560         }
561         return cachedDefaultPattern;
562     }
563
564     /* Define one-century window into which to disambiguate dates using
565      * two-digit years.
566      */
567     private void parseAmbiguousDatesAsAfter(Date startDate) {
568         defaultCenturyStart = startDate;
569         calendar.setTime(startDate);
570         defaultCenturyStartYear = calendar.get(Calendar.YEAR);
571     }
572
573     /* Initialize defaultCenturyStart and defaultCenturyStartYear by base time.
574      * The default start time is 80 years before the creation time of this object.
575      */
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);
585     }
586
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);
592         }
593         return defaultCenturyStart;
594     }
595
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);
601         }
602         return defaultCenturyStartYear;
603     }
604
605     /**
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>.
610      * @stable ICU 2.0
611      */
612     public void set2DigitYearStart(Date startDate) {
613         parseAmbiguousDatesAsAfter(startDate);
614     }
615
616     /**
617      * Returns the beginning date of the 100-year period 2-digit years are interpreted
618      * as being within.
619      * @return the start of the 100-year period into which two digit years are
620      * parsed
621      * @stable ICU 2.0
622      */
623     public Date get2DigitYearStart() {
624         return getDefaultCenturyStart();
625     }
626
627     /**
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.
638      * @see DateFormat
639      * @stable ICU 2.0
640      */
641     public StringBuffer format(Calendar cal, StringBuffer toAppendTo,
642                                FieldPosition pos) {
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());
651             cal = calendar;
652         }
653         StringBuffer result = format(cal, toAppendTo, pos, null);
654         if (backupTZ != null) {
655             // Restore the original time zone
656             calendar.setTimeZone(backupTZ);
657         }
658         return result;
659     }
660
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) {
665         // Initialize
666         pos.setBeginIndex(0);
667         pos.setEndIndex(0);
668
669         // Careful: For best performance, minimize the number of calls
670         // to StringBuffer.append() by consolidating appends when
671         // possible.
672
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]);
677             } else {
678                 PatternItem item = (PatternItem)items[i];
679 //#if defined(FOUNDATION10) || defined(J2SE13)
680 //#else
681                 int start = 0;
682                 if (attributes != null) {
683                     // Save the current length
684                     start = toAppendTo.length();
685                 }
686 //#endif
687                 if (useFastFormat) {
688                     subFormat(toAppendTo, item.type, item.length, toAppendTo.length(), pos, cal);
689                 } else {
690                     toAppendTo.append(subFormat(item.type, item.length, toAppendTo.length(), pos, formatData, cal));
691                 }
692 //#if defined(FOUNDATION10) || defined(J2SE13)
693 //#else
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);
702                         fp.setEndIndex(end);
703                         attributes.add(fp);
704                     }
705                 }
706 //#endif
707             }
708         }
709         return toAppendTo;
710         
711     }
712
713     // Map pattern character to index
714     private static final int PATTERN_CHAR_BASE = 0x40;
715     private static final int[] PATTERN_CHAR_TO_INDEX =
716     {
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
725     };
726     
727     // Map pattern character index to Calendar field number
728     private static final int[] PATTERN_INDEX_TO_CALENDAR_FIELD =
729     {
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,
743     };
744
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, 
760     };
761
762 //#if defined(FOUNDATION10) || defined(J2SE13)
763 //#else
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,
779     };
780
781     /**
782      * Return a DateFormat.Field constant associated with the specified format pattern
783      * character.
784      * 
785      * @param ch The pattern character
786      * @return DateFormat.Field associated with the pattern character
787      * 
788      * @stable ICU 3.8
789      */
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];
794         }
795         if (patternCharIndex != -1) {
796             return PATTERN_INDEX_TO_DATE_FORMAT_ATTRIBUTE[patternCharIndex];
797         }
798         return null;
799     }
800 //#endif
801
802     /**
803      * Format a single field, given its pattern character.  Subclasses may
804      * override this method in order to modify or add formatting
805      * capabilities.
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
812      * @stable ICU 2.0
813      */
814     protected String subFormat(char ch, int count, int beginOffset,
815                                FieldPosition pos, DateFormatSymbols fmtData,
816                                Calendar cal)
817         throws IllegalArgumentException
818     {
819         // Note: formatData is ignored
820         StringBuffer buf = new StringBuffer();
821         subFormat(buf, ch, count, beginOffset, pos, cal);
822         return buf.toString();
823     }
824
825     /**
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.
829      *
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.
833      *
834      * TODO make this API public
835      *
836      * @internal
837      * @deprecated This API is ICU internal only.
838      */
839     protected void subFormat(StringBuffer buf,
840                              char ch, int count, int beginOffset,
841                              FieldPosition pos,
842                              Calendar cal) {
843         final int maxIntCount = Integer.MAX_VALUE;
844         final int bufstart = buf.length();
845
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];
850         }
851
852         if (patternCharIndex == -1) {
853             throw new IllegalArgumentException("Illegal pattern character " +
854                                                "'" + ch + "' in \"" +
855                                                new String(pattern) + '"');
856         }
857
858         final int field = PATTERN_INDEX_TO_CALENDAR_FIELD[patternCharIndex];
859         int value = cal.get(field);
860
861         String zoneString = null;
862
863         NumberFormat currentNumberFormat = getNumberFormat(ch);
864
865         switch (patternCharIndex) {
866         case 0: // 'G' - ERA
867             if (count == 5) {
868                 safeAppend(formatData.narrowEras, value, buf);
869             } else if (count == 4) {
870                 safeAppend(formatData.eraNames, value, buf);
871             } else {
872                 safeAppend(formatData.eras, value, buf);
873             }
874             break;
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]
881              */
882             if (count == 2)
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);
886             break;
887         case 2: // 'M' - MONTH
888             if (count == 5) {
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);
894             } else {
895                 zeroPaddingNumber(currentNumberFormat,buf, value+1, count, maxIntCount);
896             }
897             break;
898         case 4: // 'k' - HOUR_OF_DAY (1..24)
899             if (value == 0)
900                 zeroPaddingNumber(currentNumberFormat,buf,
901                                   cal.getMaximum(Calendar.HOUR_OF_DAY)+1,
902                                   count, maxIntCount);
903             else
904                 zeroPaddingNumber(currentNumberFormat,buf, value, count, maxIntCount);
905             break;
906         case 8: // 'S' - FRACTIONAL_SECOND
907             // Fractional seconds left-justify
908             {
909                 numberFormat.setMinimumIntegerDigits(Math.min(3, count));
910                 numberFormat.setMaximumIntegerDigits(maxIntCount);
911                 if (count == 1) {
912                     value = (value + 50) / 100;
913                 } else if (count == 2) {
914                     value = (value + 5) / 10;
915                 }
916                 FieldPosition p = new FieldPosition(-1);
917                 numberFormat.format((long) value, buf, p);
918                 if (count > 3) {
919                     numberFormat.setMinimumIntegerDigits(count - 3);
920                     numberFormat.format(0L, buf, p);
921                 }
922             }
923             break;
924         case 19: // 'e' - DOW_LOCAL (use DOW_LOCAL for numeric, DAY_OF_WEEK for format names)
925             if (count < 3) {
926                 zeroPaddingNumber(currentNumberFormat,buf, value, count, maxIntCount);
927                 break;
928             }
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
934             if (count == 5) {
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);
940             }
941             break;
942         case 14: // 'a' - AM_PM
943             safeAppend(formatData.ampms, value, buf);
944             break;
945         case 15: // 'h' - HOUR (1..12)
946             if (value == 0)
947                 zeroPaddingNumber(currentNumberFormat,buf,
948                                   cal.getLeastMaximum(Calendar.HOUR)+1,
949                                   count, maxIntCount);
950             else
951                 zeroPaddingNumber(currentNumberFormat,buf, value, count, maxIntCount);
952             break;
953         case 17: // 'z' - ZONE_OFFSET
954             if (count < 4) {
955                 // "z", "zz", "zzz"
956                 zoneString = formatData.getZoneStringFormat().getSpecificShortString(cal, true /* commonly used only */);
957             } else {
958                 zoneString = formatData.getZoneStringFormat().getSpecificLongString(cal);
959             }
960             if (zoneString != null && zoneString.length() != 0) {
961                 buf.append(zoneString);
962             } else {
963                 // Use localized GMT format as fallback
964                 appendGMT(currentNumberFormat,buf, cal);
965             }
966             break;
967         case 23: // 'Z' - TIMEZONE_RFC
968             if (count < 4) {
969                 // RFC822 format, must use ASCII digits
970                 int val = (cal.get(Calendar.ZONE_OFFSET) + cal.get(Calendar.DST_OFFSET));
971                 char sign = '+';
972                 if (val < 0) {
973                     val = -val;
974                     sign = '-';
975                 }
976                 buf.append(sign);
977
978                 int offsetH = val / millisPerHour;
979                 val = val % millisPerHour;
980                 int offsetM = val / millisPerMinute;
981                 val = val % millisPerMinute;
982                 int offsetS = val / millisPerSecond;
983
984                 int num = 0, denom = 0;
985                 if (offsetS == 0) {
986                     val = offsetH*100 + offsetM; // HHmm
987                     num = val % 10000;
988                     denom = 1000;
989                 } else {
990                     val = offsetH*10000 + offsetM*100 + offsetS; // HHmmss
991                     num = val % 1000000;
992                     denom = 100000;
993                 }
994                 while (denom >= 1) {
995                     char digit = (char)((num / denom) + '0');
996                     buf.append(digit);
997                     num = num % denom;
998                     denom /= 10;
999                 }
1000             } else {
1001                 // long form, localized GMT pattern
1002                 appendGMT(currentNumberFormat,buf, cal);
1003             }
1004             break;
1005         case 24: // 'v' - TIMEZONE_GENERIC
1006             if (count == 1) {
1007                 // "v"
1008                 zoneString = formatData.getZoneStringFormat().getGenericShortString(cal, true /* commonly used only */);
1009             } else if (count == 4) {
1010                 // "vvvv"
1011                 zoneString = formatData.getZoneStringFormat().getGenericLongString(cal);
1012             }
1013             if (zoneString != null && zoneString.length() != 0) {
1014                 buf.append(zoneString);
1015             } else {
1016                 // Use localized GMT format as fallback
1017                 appendGMT(currentNumberFormat,buf, cal);
1018             }
1019             break;
1020         case 25: // 'c' - STANDALONE DAY (use DOW_LOCAL for numeric, DAY_OF_WEEK for standalone names)
1021             if (count < 3) {
1022                 zeroPaddingNumber(currentNumberFormat,buf, value, 1, maxIntCount);
1023                 break;
1024             }
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);
1028             if (count == 5) {
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);
1034             }
1035             break;
1036         case 26: // 'L' - STANDALONE MONTH
1037             if (count == 5) {
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);
1043             } else {
1044                 zeroPaddingNumber(currentNumberFormat,buf, value+1, count, maxIntCount);
1045             }
1046             break;
1047         case 27: // 'Q' - QUARTER
1048             if (count >= 4) {
1049                 safeAppend(formatData.quarters, value/3, buf);
1050             } else if (count == 3) {
1051                 safeAppend(formatData.shortQuarters, value/3, buf);
1052             } else {
1053                 zeroPaddingNumber(currentNumberFormat,buf, (value/3)+1, count, maxIntCount);
1054             }
1055             break;
1056         case 28: // 'q' - STANDALONE QUARTER
1057             if (count >= 4) {
1058                 safeAppend(formatData.standaloneQuarters, value/3, buf);
1059             } else if (count == 3) {
1060                 safeAppend(formatData.standaloneShortQuarters, value/3, buf);
1061             } else {
1062                 zeroPaddingNumber(currentNumberFormat,buf, (value/3)+1, count, maxIntCount);
1063             }
1064             break;
1065         case 29: // 'V' - TIMEZONE_SPECIAL
1066             if (count == 1) {
1067                 // "V"
1068                 zoneString = formatData.getZoneStringFormat().getSpecificShortString(cal, false /* ignoring commonly used */);
1069             } else if (count == 4) {
1070                 // "VVVV"
1071                 zoneString = formatData.getZoneStringFormat().getGenericLocationString(cal);
1072             }
1073             if (zoneString != null && zoneString.length() != 0) {
1074                 buf.append(zoneString);
1075             } else {
1076                 // Use localized GMT format as fallback
1077                 appendGMT(currentNumberFormat,buf, cal);
1078             }
1079             break;
1080         default:
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
1094
1095             zeroPaddingNumber(currentNumberFormat,buf, value, count, maxIntCount);
1096             break;
1097         } // switch (patternCharIndex)
1098
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);
1104             }
1105 //#if defined(FOUNDATION10) || defined(J2SE13)
1106 //#else
1107             else if (pos.getFieldAttribute() == PATTERN_INDEX_TO_DATE_FORMAT_ATTRIBUTE[patternCharIndex]) {
1108                 pos.setBeginIndex(beginOffset);
1109                 pos.setEndIndex(beginOffset + buf.length() - bufstart);
1110             }
1111 //#endif
1112         }
1113     }
1114
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]);
1118         }
1119     }
1120
1121     /*
1122      * PatternItem store parsed date/time field pattern information.
1123      */
1124     private static class PatternItem {
1125         final char type;
1126         final int length;
1127         final boolean isNumeric;
1128
1129         PatternItem(char type, int length) {
1130             this.type = type;
1131             this.length = length;
1132             isNumeric = isNumeric(type, length);
1133         }
1134     }
1135
1136     private static ICUCache PARSED_PATTERN_CACHE = new SimpleCache();
1137     private transient Object[] patternItems;
1138
1139     /*
1140      * Returns parsed pattern items.  Each item is either String or
1141      * PatternItem.
1142      */
1143     private Object[] getPatternItems() {
1144         if (patternItems != null) {
1145             return patternItems;
1146         }
1147
1148         patternItems = (Object[])PARSED_PATTERN_CACHE.get(pattern);
1149         if (patternItems != null) {
1150             return patternItems;
1151         }
1152
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
1157         int itemLength = 1;
1158
1159         List items = new ArrayList();
1160
1161         for (int i = 0; i < pattern.length(); i++) {
1162             char ch = pattern.charAt(i);
1163             if (ch == '\'') {
1164                 if (isPrevQuote) {
1165                     text.append('\'');
1166                     isPrevQuote = false;
1167                 } else {
1168                     isPrevQuote = true;
1169                     if (itemType != 0) {
1170                         items.add(new PatternItem(itemType, itemLength));
1171                         itemType = 0;
1172                     }
1173                 }
1174                 inQuote = !inQuote;
1175             } else {
1176                 isPrevQuote = false;
1177                 if (inQuote) {
1178                     text.append(ch);
1179                 } else {
1180                     if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')) {
1181                         // a date/time pattern character
1182                         if (ch == itemType) {
1183                             itemLength++;
1184                         } else {
1185                             if (itemType == 0) {
1186                                 if (text.length() > 0) {
1187                                     items.add(text.toString());
1188                                     text.setLength(0);
1189                                 }
1190                             } else {
1191                                 items.add(new PatternItem(itemType, itemLength));
1192                             }
1193                             itemType = ch;
1194                             itemLength = 1;
1195                         }
1196                     } else {
1197                         // a string literal
1198                         if (itemType != 0) {
1199                             items.add(new PatternItem(itemType, itemLength));
1200                             itemType = 0;
1201                         }
1202                         text.append(ch);
1203                     }
1204                 }
1205             }
1206         }
1207         // handle last item
1208         if (itemType == 0) {
1209             if (text.length() > 0) {
1210                 items.add(text.toString());
1211                 text.setLength(0);
1212             }
1213         } else {
1214             items.add(new PatternItem(itemType, itemLength));
1215         }
1216
1217         patternItems = new Object[items.size()];
1218         items.toArray(patternItems);
1219
1220         PARSED_PATTERN_CACHE.put(pattern, patternItems);
1221         
1222         return patternItems;
1223     }
1224
1225     /*
1226      * Time zone localized GMT format stuffs
1227      */
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 = ':';
1237
1238     private void appendGMT(NumberFormat currentNumberFormat,StringBuffer buf, Calendar cal) {
1239         int offset = cal.get(Calendar.ZONE_OFFSET) + cal.get(Calendar.DST_OFFSET);
1240
1241         if (isDefaultGMTFormat()) {
1242             formatGMTDefault(currentNumberFormat,buf, offset);
1243         } else {
1244             int sign = DateFormatSymbols.OFFSET_POSITIVE;
1245             if (offset < 0) {
1246                 offset = -offset;
1247                 sign = DateFormatSymbols.OFFSET_NEGATIVE;
1248             }
1249             int width = offset%(60*1000) == 0 ? DateFormatSymbols.OFFSET_HM : DateFormatSymbols.OFFSET_HMS;
1250
1251             MessageFormat fmt = getGMTFormatter(sign, width);
1252             fmt.format(new Object[] {new Long(offset)}, buf, null);
1253         }
1254     }
1255
1256     private void formatGMTDefault(NumberFormat currentNumberFormat,StringBuffer buf, int offset) {
1257         buf.append(STR_GMT);
1258         if (offset >= 0) {
1259             buf.append(PLUS);
1260         } else {
1261             buf.append(MINUS);
1262             offset = -offset;
1263         }
1264         offset /= 1000; // now in seconds
1265         int sec = offset % 60;
1266         offset /= 60;
1267         int min = offset % 60;
1268         int hour = offset / 60;
1269
1270         zeroPaddingNumber(currentNumberFormat,buf, hour, 2, 2);
1271         buf.append(COLON);
1272         zeroPaddingNumber(currentNumberFormat,buf, min, 2, 2);
1273         if (sec != 0) {
1274             buf.append(COLON);
1275             zeroPaddingNumber(currentNumberFormat,buf, sec, 2, 2);
1276         }
1277     }
1278
1279     private Integer parseGMT(String text, ParsePosition pos, NumberFormat currentNumberFormat) {
1280         if (!isDefaultGMTFormat()) {
1281             int start = pos.getIndex();
1282             String gmtPattern = formatData.gmtFormat;
1283
1284             // Quick check
1285             boolean prefixMatch = false;
1286             int prefixLen = gmtPattern.indexOf('{');
1287             if (prefixLen > 0 && text.regionMatches(start, gmtPattern, 0, prefixLen)) {
1288                 prefixMatch = true;
1289             }
1290
1291             if (prefixMatch) {
1292                 // Prefix matched
1293                 MessageFormat fmt;
1294                 Object[] parsedObjects;
1295                 int offset;
1296
1297                 // Try negative Hms
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 */);
1304                 }
1305
1306                 // Reset ParsePosition
1307                 pos.setIndex(start);
1308                 pos.setErrorIndex(-1);
1309
1310                 // Try positive Hms
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);
1317                 }
1318
1319                 // Reset ParsePosition
1320                 pos.setIndex(start);
1321                 pos.setErrorIndex(-1);
1322
1323                 // Try negative Hm
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 */);
1329                 }
1330
1331                 // Reset ParsePosition
1332                 pos.setIndex(start);
1333                 pos.setErrorIndex(-1);
1334
1335                 // Try positive Hm
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);
1341                 }
1342
1343                 // Reset ParsePosition
1344                 pos.setIndex(start);
1345                 pos.setErrorIndex(-1);
1346             }
1347         }
1348
1349         return parseGMTDefault(text, pos, currentNumberFormat);
1350     }
1351
1352     private Integer parseGMTDefault(String text, ParsePosition pos, NumberFormat currentNumberFormat) {
1353         int start = pos.getIndex();
1354
1355         if (start + STR_UT_LEN + 1 >= text.length()) {
1356             pos.setErrorIndex(start);
1357             return null;
1358         }
1359
1360         int cur = start;
1361         // "GMT"
1362         if (text.regionMatches(true, start, STR_GMT, 0, STR_GMT_LEN)) {
1363             cur += STR_GMT_LEN;
1364         } else if (text.regionMatches(true, start, STR_UT, 0, STR_UT_LEN)) {
1365             cur += STR_UT_LEN;
1366         } else {
1367             pos.setErrorIndex(start);
1368             return null;
1369         }
1370         // Sign
1371         boolean negative = false;
1372         if (text.charAt(cur) == MINUS) {
1373             negative = true;
1374         } else if (text.charAt(cur) != PLUS) {
1375             pos.setErrorIndex(cur);
1376             return null;
1377         }
1378         cur++;
1379
1380         // Numbers
1381         int numLen;
1382         pos.setIndex(cur);
1383
1384         Number n = parseInt(text, 6, pos, false,currentNumberFormat);
1385         numLen = pos.getIndex() - cur;
1386
1387         if (n == null || numLen <= 0 || numLen > 6) {
1388             pos.setIndex(start);
1389             pos.setErrorIndex(cur);
1390             return null;
1391         }
1392
1393         int numVal = n.intValue();
1394
1395         int hour = 0;
1396         int min = 0;
1397         int sec = 0;
1398
1399         if (numLen <= 2) {
1400             // H[H][:mm[:ss]]
1401             hour = numVal;
1402             cur += numLen;
1403             if (cur + 2 < text.length() && text.charAt(cur) == COLON) {
1404                 cur++;
1405                 pos.setIndex(cur);
1406                 n = parseInt(text, 2, pos, false,currentNumberFormat);
1407                 numLen = pos.getIndex() - cur;
1408                 if (n != null && numLen == 2) {
1409                     // got minute field
1410                     min = n.intValue();
1411                     cur += numLen;
1412                     if (cur + 2 < text.length() && text.charAt(cur) == COLON) {
1413                         cur++;
1414                         pos.setIndex(cur);
1415                         n = parseInt(text, 2, pos, false,currentNumberFormat);
1416                         numLen = pos.getIndex() - cur;
1417                         if (n != null && numLen == 2) {
1418                             // got second field
1419                             sec = n.intValue();
1420                         } else {
1421                             // reset position
1422                             pos.setIndex(cur - 1);
1423                             pos.setErrorIndex(-1);
1424                         }
1425                     }
1426                 } else {
1427                     // reset postion
1428                     pos.setIndex(cur - 1);
1429                     pos.setErrorIndex(-1);
1430                 }
1431             }
1432         } else if (numLen == 3 || numLen == 4) {
1433             // Hmm or HHmm
1434             hour = numVal / 100;
1435             min = numVal % 100;
1436         } else { // numLen == 5 || numLen == 6
1437             // Hmmss or HHmmss
1438             hour = numVal / 10000;
1439             min = (numVal % 10000) / 100;
1440             sec = numVal % 100;
1441         }
1442
1443         int offset = ((hour*60 + min)*60 + sec)*1000;
1444         if (negative) {
1445             offset = -offset;
1446         }
1447         return new Integer(offset);
1448     }
1449
1450     transient private WeakReference[] gmtfmtCache;
1451
1452     private MessageFormat getGMTFormatter(int sign, int width) {
1453         MessageFormat fmt = null;
1454         if (gmtfmtCache == null) {
1455             gmtfmtCache = new WeakReference[4];
1456         }
1457         int cacheIdx = sign*2 + width;
1458         if (gmtfmtCache[cacheIdx] != null) {
1459             fmt = (MessageFormat)gmtfmtCache[cacheIdx].get();
1460         }
1461         if (fmt == null) {
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);
1469         }
1470         return fmt;
1471     }
1472
1473     transient private int[] gmtFormatHmsMinLen = null;
1474
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
1479
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();
1484
1485             buf.setLength(0);
1486             MessageFormat fmtPos = getGMTFormatter(DateFormatSymbols.OFFSET_POSITIVE, DateFormatSymbols.OFFSET_HMS);
1487             fmtPos.format(new Object[] {offset}, buf, null);
1488             gmtFormatHmsMinLen[1] = buf.length();
1489         }
1490         return gmtFormatHmsMinLen[(sign < 0 ? 0 : 1)];
1491     }
1492
1493     private boolean isDefaultGMTFormat() {
1494         // GMT pattern
1495         if (!DateFormatSymbols.DEFAULT_GMT_PATTERN.equals(formatData.getGmtFormat())) {
1496             return false;
1497         }
1498         // GMT offset hour patters
1499         boolean res = true;
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))) {
1503                     res = false;
1504                     break;
1505                 }
1506             }
1507         }
1508         return res;
1509     }
1510
1511     /*
1512      * Internal method. Returns null if the value of an array is empty, or if the
1513      * index is out of bounds
1514      */
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) {
1519                 return result;
1520             }
1521         }
1522         return null;
1523     }*/
1524
1525     /**
1526      * Internal high-speed method.  Reuses a StringBuffer for results
1527      * instead of creating a String on the heap for each call.
1528      * @internal
1529      * @deprecated This API is ICU internal only.
1530      */
1531     protected void zeroPaddingNumber(NumberFormat nf,StringBuffer buf, int value,
1532                                      int minDigits, int maxDigits) {
1533         if (useLocalZeroPaddingNumberFormat) {
1534             fastZeroPaddingNumber(buf, value, minDigits, maxDigits);
1535         } else {
1536             nf.setMinimumIntegerDigits(minDigits);
1537             nf.setMaximumIntegerDigits(maxDigits);
1538             nf.format(value, buf, new FieldPosition(-1));
1539         }
1540     }
1541
1542     /**
1543      * Overrides superclass method
1544      * @stable ICU 2.0
1545      */
1546     public void setNumberFormat(NumberFormat newNumberFormat) {
1547         // Override this method to update local zero padding number formatter
1548         super.setNumberFormat(newNumberFormat);
1549         initLocalZeroPaddingNumberFormat();
1550     }
1551
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;
1559         } else {
1560             useLocalZeroPaddingNumberFormat = false;
1561         }
1562
1563         if (useLocalZeroPaddingNumberFormat) {
1564             decimalBuf = new char[10];  // sufficient for int numbers
1565         }
1566     }
1567
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;
1572
1573     /*
1574      * Lightweight zero padding integer number format function.
1575      * 
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.
1581      * 
1582      * -Yoshito
1583      */
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;
1587         while (true) {
1588             decimalBuf[index] = (char)((value % 10) + zeroDigit);
1589             value /= 10;
1590             if (index == 0 || value == 0) {
1591                 break;
1592             }
1593             index--;
1594         }
1595         int padding = minDigits - (limit - index);
1596         for (; padding > 0; padding--) {
1597             decimalBuf[--index] = zeroDigit;
1598         }
1599         int length = limit - index;
1600         buf.append(decimalBuf, index, length);        
1601     }
1602
1603     /**
1604      * Formats a number with the specified minimum and maximum number of digits.
1605      * @stable ICU 2.0
1606      */
1607     protected String zeroPaddingNumber(long value, int minDigits, int maxDigits)
1608     {
1609         numberFormat.setMinimumIntegerDigits(minDigits);
1610         numberFormat.setMaximumIntegerDigits(maxDigits);
1611         return numberFormat.format(value);
1612     }
1613
1614     /**
1615      * Format characters that indicate numeric fields.  The character
1616      * at index 0 is treated specially.
1617      */
1618     private static final String NUMERIC_FORMAT_CHARS = "MYyudehHmsSDFwWkK";
1619
1620     /**
1621      * Return true if the given format character, occuring count
1622      * times, represents a numeric field.
1623      */
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));
1627     }
1628
1629     /**
1630      * Overrides DateFormat
1631      * @see DateFormat
1632      * @stable ICU 2.0
1633      */
1634     public void parse(String text, Calendar cal, ParsePosition parsePos)
1635     {
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());
1645             resultCal = cal;
1646             cal = calendar;
1647         }
1648
1649         int pos = parsePos.getIndex();
1650         int start = pos;
1651
1652         // Reset tztype
1653         tztype = TZTYPE_UNK;
1654         boolean[] ambiguousYear = { false };
1655
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;
1662
1663         Object[] items = getPatternItems();
1664         int i = 0;
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; 
1686                         }
1687                     }
1688                 }
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;
1694                     }
1695
1696                     // Parse a numeric field
1697                     pos = subParse(text, pos, field.type, len,
1698                             true, false, ambiguousYear, cal);
1699
1700                     if (pos < 0) {
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
1703                         // numeric field.
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);
1711                             }
1712                             return;
1713                         }
1714                         i = numericFieldStart;
1715                         pos = numericStartPos;
1716                         continue;
1717                     }
1718
1719                 } else {
1720                     // Handle a non-numeric field or a non-abutting numeric field
1721                     numericFieldStart = -1;
1722
1723                     int s = pos;
1724                     pos = subParse(text, pos, field.type, field.length,
1725                             false, true, ambiguousYear, cal);
1726                     if (pos < 0) {
1727                         parsePos.setIndex(start);
1728                         parsePos.setErrorIndex(s);
1729                         if (backupTZ != null) {
1730                             calendar.setTimeZone(backupTZ);
1731                         }
1732                         return;
1733                     }
1734                 }
1735             } else {
1736                 // Handle literal pattern text literal
1737                 numericFieldStart = -1;
1738
1739                 String patl = (String)items[i];
1740                 int plen = patl.length();
1741                 int tlen = text.length();
1742                 int idx = 0;
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))) {
1751                              ++idx;
1752                         }
1753                         while ((pos + 1) < tlen &&
1754                                 UCharacterProperty.isRuleWhiteSpace(text.charAt(pos + 1))) {
1755                              ++pos;
1756                         }
1757                     } else if (pch != ich) {
1758                         break;
1759                     }
1760                     ++idx;
1761                     ++pos;
1762                 }
1763                 if (idx != plen) {
1764                     // Set the position of mismatch
1765                     parsePos.setIndex(start);
1766                     parsePos.setErrorIndex(pos);
1767                     if (backupTZ != null) {
1768                         calendar.setTimeZone(backupTZ);
1769                     }
1770                     return;
1771                 }
1772             }
1773             ++i;
1774         }
1775
1776         // At this point the fields of Calendar have been set.  Calendar
1777         // will fill in default values for missing fields when the time
1778         // is computed.
1779
1780         parsePos.setIndex(pos);
1781
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]
1790         /*
1791           Date parsedDate = cal.getTime();
1792           if( ambiguousYear[0] && !parsedDate.after(getDefaultCenturyStart()) ) {
1793           cal.add(Calendar.YEAR, 100);
1794           parsedDate = cal.getTime();
1795           }
1796         */
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().
1804         try {
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().
1810                 Calendar copy;
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);
1817                     }
1818                 }
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;
1825                     }
1826
1827                     // Get local millis
1828                     copy.set(Calendar.ZONE_OFFSET, 0);
1829                     copy.set(Calendar.DST_OFFSET, 0);
1830                     long localMillis = copy.getTimeInMillis();
1831
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];
1835                     if (btz != null) {
1836                         if (tztype == TZTYPE_STD) {
1837                             btz.getOffsetFromLocal(localMillis,
1838                                     BasicTimeZone.LOCAL_STD, BasicTimeZone.LOCAL_STD, offsets);
1839                         } else {
1840                             btz.getOffsetFromLocal(localMillis,
1841                                     BasicTimeZone.LOCAL_DST, BasicTimeZone.LOCAL_DST, offsets);
1842                         }
1843                     } else {
1844                         // No good way to resolve ambiguous time at transition,
1845                         // but following code work in most case.
1846                         tz.getOffset(localMillis, true, offsets);
1847
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);
1853                         }
1854                     }
1855
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;
1862                         }
1863                     } else { // tztype == TZTYPE_DST
1864                         if (offsets[1] == 0) {
1865                             if (btz != null) {
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;
1871
1872                                 // Search for DST rule before or on the time
1873                                 while (true) {
1874                                     beforeTrs = btz.getPreviousTransition(beforeT, true);
1875                                     if (beforeTrs == null) {
1876                                         break;
1877                                     }
1878                                     beforeT = beforeTrs.getTime() - 1;
1879                                     beforeSav = beforeTrs.getFrom().getDSTSavings();
1880                                     if (beforeSav != 0) {
1881                                         break;
1882                                     }
1883                                 }
1884
1885                                 // Search for DST rule after the time
1886                                 while (true) {
1887                                     afterTrs = btz.getNextTransition(afterT, false);
1888                                     if (afterTrs == null) {
1889                                         break;
1890                                     }
1891                                     afterT = afterTrs.getTime();
1892                                     afterSav = afterTrs.getTo().getDSTSavings();
1893                                     if (afterSav != 0) {
1894                                         break;
1895                                     }
1896                                 }
1897
1898                                 if (beforeTrs != null && afterTrs != null) {
1899                                     if (time - beforeT > afterT - time) {
1900                                         resolvedSavings = afterSav;
1901                                     } else {
1902                                         resolvedSavings = beforeSav;
1903                                     }
1904                                 } else if (beforeTrs != null && beforeSav != 0) {
1905                                     resolvedSavings = beforeSav;
1906                                 } else if (afterTrs != null && afterSav != 0) {
1907                                     resolvedSavings = afterSav;
1908                                 } else {
1909                                     resolvedSavings = btz.getDSTSavings();
1910                                 }
1911                             } else {
1912                                 resolvedSavings = tz.getDSTSavings();
1913                             }
1914                             if (resolvedSavings == 0) {
1915                                 // Final fallback
1916                                 resolvedSavings = millisPerHour;
1917                             }
1918                         }
1919                     }
1920                     cal.set(Calendar.ZONE_OFFSET, offsets[0]);
1921                     cal.set(Calendar.DST_OFFSET, resolvedSavings);
1922                 }
1923             }
1924         }
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);
1932             }
1933             return;
1934         }
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());
1940         }
1941         // Restore the original time zone if required
1942         if (backupTZ != null) {
1943             calendar.setTimeZone(backupTZ);
1944         }
1945     }
1946
1947     /**
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.
1962      * @stable ICU 2.0
1963      */
1964     protected int matchString(String text, int start, int field, String[] data, Calendar cal)
1965     {
1966         int i = 0;
1967         int count = data.length;
1968
1969         if (field == Calendar.DAY_OF_WEEK) i = 1;
1970
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)
1977             {
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))
1983                     {
1984                         bestMatch = i;
1985                         bestMatchLength = length;
1986                     }
1987             }
1988         if (bestMatch >= 0)
1989             {
1990                 cal.set(field, bestMatch);
1991                 return start + bestMatchLength;
1992             }
1993         return -start;
1994     }
1995
1996     /**
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.
2011      * @stable ICU 2.0
2012      */
2013     protected int matchQuarterString(String text, int start, int field, String[] data, Calendar cal)
2014     {
2015         int i = 0;
2016         int count = data.length;
2017
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)) {
2029                 bestMatch = i;
2030                 bestMatchLength = length;
2031             }
2032         }
2033         
2034         if (bestMatch >= 0) {
2035             cal.set(field, bestMatch * 3);
2036             return start + bestMatchLength;
2037         }
2038         
2039         return -start;
2040     }
2041     
2042     /**
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
2058      * value.
2059      * @stable ICU 2.0
2060      */
2061     protected int subParse(String text, int start, char ch, int count,
2062                            boolean obeyCount, boolean allowNegative,
2063                            boolean[] ambiguousYear, Calendar cal)
2064     {
2065         Number number = null;
2066         NumberFormat currentNumberFormat = null;
2067         int value = 0;
2068         int i;
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];
2074         }
2075
2076         if (patternCharIndex == -1) {
2077             return -start;
2078         }
2079
2080         currentNumberFormat = getNumberFormat(ch);
2081  
2082         int field = PATTERN_INDEX_TO_CALENDAR_FIELD[patternCharIndex];
2083
2084         // If there are any spaces here, skip over them.  If we hit the end
2085         // of the string, then fail.
2086         for (;;) {
2087             if (start >= text.length()) {
2088                 return -start;
2089             }
2090             int c = UTF16.charAt(text, start);
2091             if (!UCharacter.isUWhiteSpace(c)) {
2092                 break;
2093             }
2094             start += UTF16.getCharCount(c);
2095         }
2096         pos.setIndex(start);
2097
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)
2107             {
2108                 // It would be good to unify this with the obeyCount logic below,
2109                 // but that's going to be difficult.
2110                 if (obeyCount)
2111                     {
2112                         if ((start+count) > text.length()) return -start;
2113                         number = parseInt(text, count, pos, allowNegative,currentNumberFormat);
2114                     }
2115                 else number = parseInt(text, pos, allowNegative,currentNumberFormat);
2116                 if (number == null)
2117                     return -start;
2118                 value = number.intValue();
2119             }
2120
2121         switch (patternCharIndex)
2122             {
2123             case 0: // 'G' - ERA
2124                 if (count == 4) {
2125                     return matchString(text, start, Calendar.ERA, formatData.eraNames, cal);
2126                 } else {
2127                     return matchString(text, start, Calendar.ERA, formatData.eras, cal);
2128                 }
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)))
2140                     {
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);
2153                     }
2154                 cal.set(Calendar.YEAR, value);
2155                 return pos.getIndex();
2156             case 2: // 'M' - MONTH
2157                 if (count <= 2) // i.e., M or MM.
2158                     {
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();
2164                     }
2165                 else
2166                     {
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);
2172                         if (newStart > 0) {
2173                             return newStart;
2174                         } else { // count == 4 failed, now try count == 3
2175                             return matchString(text, start, Calendar.MONTH,
2176                                                formatData.shortMonths, cal);
2177                         }
2178                     }
2179             case 26: // 'L' - STAND_ALONE_MONTH
2180                 if (count <= 2) // i.e., M or MM.
2181                     {
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();
2187                     }
2188                 else
2189                     {
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);
2195                         if (newStart > 0) {
2196                             return newStart;
2197                         } else { // count == 4 failed, now try count == 3
2198                             return matchString(text, start, Calendar.MONTH,
2199                                                formatData.standaloneShortMonths, cal);
2200                         }
2201                     }
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;
2210                 if (i < 3) {
2211                     while (i < 3) {
2212                         value *= 10;
2213                         i++;
2214                     }
2215                 } else {
2216                     int a = 1;
2217                     while (i > 3) {
2218                         a *= 10;
2219                         i--;
2220                     }
2221                     value = (value + (a>>1)) / a;
2222                 }
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);
2230                 if (newStart > 0) {
2231                     return newStart;
2232                 } else { // EEEE failed, now try EEE
2233                     return matchString(text, start, Calendar.DAY_OF_WEEK,
2234                                        formatData.shortWeekdays, cal);
2235                 }
2236             }
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);
2242                 if (newStart > 0) {
2243                     return newStart;
2244                 } else { // cccc failed, now try ccc
2245                     return matchString(text, start, Calendar.DAY_OF_WEEK,
2246                                        formatData.standaloneShortWeekdays, cal);
2247                 }
2248             }
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
2260                 {
2261                     TimeZone tz = null;
2262                     int offset = 0;
2263                     boolean parsed = false;
2264
2265                     // Step 1
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();
2270                         parsed = true;
2271                     }
2272
2273                     if (!parsed) {
2274                         // Step 2
2275                         // Check if this is an RFC822 time zone offset.
2276                         // ICU supports the standard RFC822 format [+|-]HHmm
2277                         // and its extended form [+|-]HHmmSS.
2278                         
2279                         do {
2280                             int sign = 0;
2281                             char signChar = text.charAt(start);
2282                             if (signChar == '+') {
2283                                 sign = 1;
2284                             } else if (signChar == '-') {
2285                                 sign = -1;
2286                             } else {
2287                                 // Not an RFC822 offset string
2288                                 break;
2289                             }
2290
2291                             // Parse digits
2292                             int orgPos = start + 1;
2293                             pos.setIndex(orgPos);
2294                             number = parseInt(text, 6, pos, false,currentNumberFormat);
2295                             int numLen = pos.getIndex() - orgPos;
2296                             if (numLen <= 0) {
2297                                 break;
2298                             }
2299
2300                             // Followings are possible format (excluding sign char)
2301                             // HHmmSS
2302                             // HmmSS
2303                             // HHmm
2304                             // Hmm
2305                             // HH
2306                             // H
2307                             int val = number.intValue();
2308                             int hour = 0, min = 0, sec = 0;
2309                             switch(numLen) {
2310                             case 1: // H
2311                             case 2: // HH
2312                                 hour = val;
2313                                 break;
2314                             case 3: // Hmm
2315                             case 4: // HHmm
2316                                 hour = val / 100;
2317                                 min = val % 100;
2318                                 break;
2319                             case 5: // Hmmss
2320                             case 6: // HHmmss
2321                                 hour = val / 10000;
2322                                 min = (val % 10000) / 100;
2323                                 sec = val % 100;
2324                                 break;
2325                             }
2326                             if (hour > 23 || min > 59 || sec > 59) {
2327                                 // Invalid value range
2328                                 break;
2329                             }
2330                             offset = (((hour * 60) + min) * 60 + sec) * 1000 * sign;
2331                             parsed = true;
2332                         } while (false);
2333
2334                         if (!parsed) {
2335                             // Failed to parse.  Reset the position.
2336                             pos.setIndex(start);
2337                         }
2338                     }
2339
2340                     if (parsed) {
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();
2346                     }
2347
2348                     // Step 3
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
2356                         if (count < 4) {
2357                             zsinfo = formatData.getZoneStringFormat().findSpecificShort(text, start);
2358                         } else {
2359                             zsinfo = formatData.getZoneStringFormat().findSpecificLong(text, start);
2360                         }
2361                         break;
2362                     case 24: // 'v' - TIMEZONE_GENERIC
2363                         if (count == 1) {
2364                             zsinfo = formatData.getZoneStringFormat().findGenericShort(text, start);
2365                         } else if (count == 4) {
2366                             zsinfo = formatData.getZoneStringFormat().findGenericLong(text, start);
2367                         }
2368                         break;
2369                     case 29: // 'V' - TIMEZONE_SPECIAL
2370                         if (count == 1) {
2371                             zsinfo = formatData.getZoneStringFormat().findSpecificShort(text, start);
2372                         } else if (count == 4) {
2373                             zsinfo = formatData.getZoneStringFormat().findGenericLocation(text, start);
2374                         }
2375                         break;
2376                     }
2377                     if (zsinfo != null) {
2378                         if (zsinfo.isStandard()) {
2379                             tztype = TZTYPE_STD;
2380                         } else if (zsinfo.isDaylight()) {
2381                             tztype = TZTYPE_DST;
2382                         }
2383                         tz = TimeZone.getTimeZone(zsinfo.getID());
2384                         cal.setTimeZone(tz);
2385                         return start + zsinfo.getString().length();
2386                     }
2387                     // Step 4
2388                     // Final attempt - is this standalone GMT/UT/UTC?
2389                     int gmtLen = 0;
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;
2396                     }
2397                     if (gmtLen > 0) {
2398                         tz = TimeZone.getTimeZone("Etc/GMT");
2399                         cal.setTimeZone(tz);
2400                         return start + gmtLen;
2401                     }
2402
2403                     // complete failure
2404                     return -start;
2405                 }
2406
2407             case 27: // 'Q' - QUARTER
2408                 if (count <= 2) // i.e., Q or QQ.
2409                 {
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();
2415                 }
2416             else
2417                 {
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);
2423                     if (newStart > 0) {
2424                         return newStart;
2425                     } else { // count == 4 failed, now try count == 3
2426                         return matchQuarterString(text, start, Calendar.MONTH,
2427                                            formatData.shortQuarters, cal);
2428                     }
2429                 }
2430                 
2431             case 28: // 'q' - STANDALONE QUARTER
2432                 if (count <= 2) // i.e., q or qq.
2433                 {
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();
2439                 }
2440             else
2441                 {
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);
2447                     if (newStart > 0) {
2448                         return newStart;
2449                     } else { // count == 4 failed, now try count == 3
2450                         return matchQuarterString(text, start, Calendar.MONTH,
2451                                            formatData.standaloneShortQuarters, cal);
2452                     }
2453                 }
2454
2455             default:
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
2470
2471                 // Handle "generic" fields
2472                 if (obeyCount)
2473                     {
2474                         if ((start+count) > text.length()) return -start;
2475                         number = parseInt(text, count, pos, allowNegative,currentNumberFormat);
2476                     }
2477                 else number = parseInt(text, pos, allowNegative,currentNumberFormat);
2478                 if (number != null) {
2479                     cal.set(field, number.intValue());
2480                     return pos.getIndex();
2481                 }
2482                 return -start;
2483             }
2484     }
2485
2486     /**
2487      * Parse an integer using numberFormat.  This method is semantically
2488      * const, but actually may modify fNumberFormat.
2489      */
2490     private Number parseInt(String text,
2491                             ParsePosition pos,
2492                             boolean allowNegative,
2493                             NumberFormat fmt) {
2494         return parseInt(text, -1, pos, allowNegative, fmt);
2495     }
2496     
2497     /**
2498      * Parse an integer using numberFormat up to maxDigits.
2499      */
2500     private Number parseInt(String text,
2501                             int maxDigits,
2502                             ParsePosition pos,
2503                             boolean allowNegative,
2504                             NumberFormat fmt) {
2505         Number number;
2506         int oldPos = pos.getIndex();
2507         if (allowNegative) {
2508             number = fmt.parse(text, pos);
2509         } else {
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);
2516             } else {
2517                 boolean dateNumberFormat = (fmt instanceof DateNumberFormat);
2518                 if (dateNumberFormat) {
2519                     ((DateNumberFormat)fmt).setParsePositiveOnly(true);
2520                 }
2521                 number = fmt.parse(text, pos);                
2522                 if (dateNumberFormat) {
2523                     ((DateNumberFormat)fmt).setParsePositiveOnly(false);
2524                 }
2525             }
2526         }
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) {
2535                     val /= 10;
2536                     nDigits--;
2537                 }
2538                 pos.setIndex(oldPos + maxDigits);
2539                 number = new Integer((int)val);
2540             }
2541         }
2542         return number;
2543     }
2544
2545     
2546     /**
2547      * Translate a pattern, mapping each character in the from string to the
2548      * corresponding character in the to string.
2549      */
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);
2555             if (inQuote) {
2556                 if (c == '\'')
2557                     inQuote = false;
2558             }
2559             else {
2560                 if (c == '\'')
2561                     inQuote = true;
2562                 else if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) {
2563                     int ci = from.indexOf(c);
2564                     if (ci != -1) {
2565                         c = to.charAt(ci);
2566                     }
2567                     // do not worry on translatepattern if the character is not listed
2568                     // we do the validity check elsewhere
2569                 }
2570             }
2571             result.append(c);
2572         }
2573         if (inQuote)
2574             throw new IllegalArgumentException("Unfinished quote in pattern");
2575         return result.toString();
2576     }
2577
2578     /**
2579      * Return a pattern string describing this date format.
2580      * @stable ICU 2.0
2581      */
2582     public String toPattern() {
2583         return pattern;
2584     }
2585
2586     /**
2587      * Return a localized pattern string describing this date format.
2588      * @stable ICU 2.0
2589      */
2590     public String toLocalizedPattern() {
2591         return translatePattern(pattern,
2592                                 DateFormatSymbols.patternChars,
2593                                 formatData.localPatternChars);
2594     }
2595
2596     /**
2597      * Apply the given unlocalized pattern string to this date format.
2598      * @stable ICU 2.0
2599      */
2600     public void applyPattern(String pat)
2601     {
2602         this.pattern = pat;
2603         setLocale(null, null);
2604         // reset parsed pattern items
2605         patternItems = null;
2606     }
2607
2608     /**
2609      * Apply the given localized pattern string to this date format.
2610      * @stable ICU 2.0
2611      */
2612     public void applyLocalizedPattern(String pat) {
2613         this.pattern = translatePattern(pat,
2614                                         formatData.localPatternChars,
2615                                         DateFormatSymbols.patternChars);
2616         setLocale(null, null);
2617     }
2618
2619     /**
2620      * Gets the date/time formatting data.
2621      * @return a copy of the date-time formatting data associated
2622      * with this date-time formatter.
2623      * @stable ICU 2.0
2624      */
2625     public DateFormatSymbols getDateFormatSymbols()
2626     {
2627         return (DateFormatSymbols)formatData.clone();
2628     }
2629
2630     /**
2631      * Allows you to set the date/time formatting data.
2632      * @param newFormatSymbols the new symbols
2633      * @stable ICU 2.0
2634      */
2635     public void setDateFormatSymbols(DateFormatSymbols newFormatSymbols)
2636     {
2637         this.formatData = (DateFormatSymbols)newFormatSymbols.clone();
2638         gmtfmtCache = null;
2639     }
2640
2641     /**
2642      * Method for subclasses to access the DateFormatSymbols.
2643      * @stable ICU 2.0
2644      */
2645     protected DateFormatSymbols getSymbols() {
2646         return formatData;
2647     }
2648
2649     /**
2650      * Overrides Cloneable
2651      * @stable ICU 2.0
2652      */
2653     public Object clone() {
2654         SimpleDateFormat other = (SimpleDateFormat) super.clone();
2655         other.formatData = (DateFormatSymbols) formatData.clone();
2656         return other;
2657     }
2658
2659     /**
2660      * Override hashCode.
2661      * Generates the hash code for the SimpleDateFormat object
2662      * @stable ICU 2.0
2663      */
2664     public int hashCode()
2665     {
2666         return pattern.hashCode();
2667         // just enough fields for a reasonable distribution
2668     }
2669
2670     /**
2671      * Override equals.
2672      * @stable ICU 2.0
2673      */
2674     public boolean equals(Object obj)
2675     {
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));
2680     }
2681
2682     /**
2683      * Override writeObject.
2684      */
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);
2690         }
2691         stream.defaultWriteObject();
2692     }
2693     
2694     /**
2695      * Override readObject.
2696      */
2697     private void readObject(ObjectInputStream stream)
2698         throws IOException, ClassNotFoundException {
2699         stream.defaultReadObject();
2700         ///CLOVER:OFF
2701         // don't have old serial data to test with
2702         if (serialVersionOnStream < 1) {
2703             // didn't have defaultCenturyStart field
2704             defaultCenturyBase = System.currentTimeMillis();
2705         }
2706         ///CLOVER:ON
2707         else {
2708             // fill in dependent transient field
2709             parseAmbiguousDatesAsAfter(defaultCenturyStart);
2710         }
2711         serialVersionOnStream = currentSerialVersion;
2712         locale = getLocale(ULocale.VALID_LOCALE);
2713
2714         initLocalZeroPaddingNumberFormat();
2715     }
2716
2717 //#if defined(FOUNDATION10) || defined(J2SE13)
2718 //#else
2719     /**
2720      * Format the object to an attributed string, and return the corresponding iterator
2721      * Overrides superclass method.
2722      * 
2723      * @param obj The object to format
2724      * @return <code>AttributedCharacterIterator</code> describing the formatted value.
2725      * 
2726      * @stable ICU 3.8
2727      */
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());
2736         } else { 
2737             throw new IllegalArgumentException("Cannot format given Object as a Date");
2738         }
2739         StringBuffer toAppendTo = new StringBuffer();
2740         FieldPosition pos = new FieldPosition(0);
2741         List attributes = new LinkedList();
2742         format(cal, toAppendTo, pos, attributes);
2743
2744         AttributedString as = new AttributedString(toAppendTo.toString());
2745         
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());
2751         }
2752         // return the CharacterIterator from AttributedString
2753         return as.getIterator();
2754     }
2755 //#endif
2756
2757
2758     /**
2759      * Get the locale of this simple date formatter.
2760      * It is package accessible. also used in DateIntervalFormat.
2761      *
2762      * @return   locale in this simple date formatter
2763      */
2764     ULocale getLocale() 
2765     {
2766         return locale;
2767     }
2768
2769
2770     
2771     /**
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.
2779      */
2780
2781     boolean isFieldUnitIgnored(int field) {
2782         return isFieldUnitIgnored(pattern, field);
2783     }
2784
2785
2786     /*
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.
2795      * @internal ICU 4.0
2796      */
2797     static boolean isFieldUnitIgnored(String pattern, int field) {
2798         int fieldLevel = CALENDAR_FIELD_TO_LEVEL[field];
2799         int level;
2800         char ch;
2801         boolean inQuote = false;
2802         char prevCh = 0;
2803         int count = 0;
2804     
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 ) {
2810                     return false;
2811                 }
2812                 count = 0;
2813             }
2814             if (ch == '\'') {
2815                 if ((i+1) < pattern.length() && pattern.charAt(i+1) == '\'') {
2816                     ++i;
2817                 } else {
2818                     inQuote = ! inQuote;
2819                 }
2820             } 
2821             else if ( ! inQuote && ((ch >= 0x0061 /*'a'*/ && ch <= 0x007A /*'z'*/) 
2822                         || (ch >= 0x0041 /*'A'*/ && ch <= 0x005A /*'Z'*/))) {
2823                 prevCh = ch;
2824                 ++count;
2825             }
2826         }
2827         if ( count > 0 ) {
2828             // last item
2829             level = PATTERN_CHAR_TO_LEVEL[prevCh - PATTERN_CHAR_BASE];
2830                 if ( fieldLevel <= level ) {
2831                     return false;
2832                 }
2833         }
2834         return true;
2835     }
2836
2837
2838     /**
2839      * Format date interval by algorithm. 
2840      * It is supposed to be used only by CLDR survey tool.
2841      *
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
2851      *                                     pattern letter
2852      * @return                  Reference to 'appendTo' parameter.
2853      * @internal ICU 4.0
2854      */
2855     public final StringBuffer intervalFormatByAlgorithm(Calendar fromCalendar,
2856                                                         Calendar toCalendar,
2857                                                         StringBuffer appendTo,
2858                                                         FieldPosition pos)
2859                               throws IllegalArgumentException
2860     {
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");
2864         }
2865      
2866         Object[] items = getPatternItems();
2867         int diffBegin = -1;
2868         int diffEnd = -1;
2869
2870         /* look for different formatting string range */
2871         // look for start of difference
2872         try {
2873             for (int i = 0; i < items.length; i++) {
2874                 if ( diffCalFieldValue(fromCalendar, toCalendar, items, i) ) {
2875                     diffBegin = i;
2876                     break;
2877                 }
2878             } 
2879         
2880             if ( diffBegin == -1 ) { 
2881                 // no difference, single date format
2882                 return format(fromCalendar, appendTo, pos);
2883             }
2884     
2885             // look for end of difference
2886             for (int i = items.length-1; i >= diffBegin; i--) {
2887                 if ( diffCalFieldValue(fromCalendar, toCalendar, items, i) ) {
2888                     diffEnd = i;
2889                     break;
2890                 }
2891             }
2892         } catch ( IllegalArgumentException e ) {
2893             throw new IllegalArgumentException(e.toString());
2894         }
2895
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);
2901             return appendTo;
2902         }
2903
2904
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) {
2909                 continue;
2910             } 
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];
2916             }
2917     
2918             if (patternCharIndex == -1) {
2919                 throw new IllegalArgumentException("Illegal pattern character " +
2920                                                    "'" + ch + "' in \"" +
2921                                                    new String(pattern) + '"');
2922             }
2923     
2924             if ( patternCharIndex < highestLevel ) {
2925                 highestLevel = patternCharIndex;
2926             }
2927         }
2928
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. */
2932         try {
2933             for (int i = 0; i < diffBegin; i++) {
2934                 if ( lowerLevel(items, i, highestLevel) ) {
2935                     diffBegin = i;
2936                     break;
2937                 }
2938             }
2939     
2940     
2941             for (int i = items.length-1; i > diffEnd; i--) {
2942                 if ( lowerLevel(items, i, highestLevel) ) {
2943                     diffEnd = i;
2944                     break;
2945                 }
2946             }
2947         } catch ( IllegalArgumentException e ) {
2948             throw new IllegalArgumentException(e.toString());
2949         }
2950
2951
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);
2957             return appendTo;
2958         }
2959
2960
2961         // formatting
2962         // Initialize
2963         pos.setBeginIndex(0);
2964         pos.setEndIndex(0);
2965
2966         // formatting date 1
2967         for (int i = 0; i <= diffEnd; i++) {
2968             if (items[i] instanceof String) {
2969                 appendTo.append((String)items[i]);
2970             } else {
2971                 PatternItem item = (PatternItem)items[i];
2972                 if (useFastFormat) {
2973                     subFormat(appendTo, item.type, item.length, appendTo.length(), pos, fromCalendar);
2974                 } else {
2975                     appendTo.append(subFormat(item.type, item.length, appendTo.length(), pos, formatData, fromCalendar));
2976                 }
2977             }
2978         }
2979
2980         appendTo.append(" \u2013 "); // default separator
2981
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]);
2986             } else {
2987                 PatternItem item = (PatternItem)items[i];
2988                 if (useFastFormat) {
2989                     subFormat(appendTo, item.type, item.length, appendTo.length(), pos, toCalendar);
2990                 } else {
2991                     appendTo.append(subFormat(item.type, item.length, appendTo.length(), pos, formatData, toCalendar));
2992                 }
2993             }
2994         }
2995         return appendTo;
2996     }
2997
2998
2999     /**
3000      * check whether the i-th item in 2 calendar is in different value.
3001      *
3002      * It is supposed to be used only by CLDR survey tool.
3003      * It is used by intervalFormatByAlgorithm().
3004      *
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
3010      *                                     pattern letter
3011      * @return               true is i-th item in 2 calendar is in different 
3012      *                       value, false otherwise.
3013      */
3014     private boolean diffCalFieldValue(Calendar fromCalendar,
3015                                       Calendar toCalendar,
3016                                       Object[] items,
3017                                       int i) throws IllegalArgumentException {
3018         if ( items[i] instanceof String) {
3019             return false;
3020         } 
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];
3026         }
3027    
3028         if (patternCharIndex == -1) {
3029             throw new IllegalArgumentException("Illegal pattern character " +
3030                                                "'" + ch + "' in \"" +
3031                                                new String(pattern) + '"');
3032         }
3033   
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 ) {
3038             return true;
3039         }
3040         return false;
3041     }
3042
3043
3044     /**
3045      * check whether the i-th item's level is lower than the input 'level'
3046      *
3047      * It is supposed to be used only by CLDR survey tool.
3048      * It is used by intervalFormatByAlgorithm().
3049      *
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
3054      *                                     pattern letter
3055      * @return       true if i-th pattern item is lower than 'level',
3056      *               false otherwise
3057      */
3058     private boolean lowerLevel(Object[] items, int i, int level) 
3059                     throws IllegalArgumentException {
3060         if ( items[i] instanceof String) {
3061             return false;
3062         } 
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];
3068         }
3069     
3070         if (patternCharIndex == -1) {
3071             throw new IllegalArgumentException("Illegal pattern character " +
3072                                                "'" + ch + "' in \"" +
3073                                                new String(pattern) + '"');
3074         }
3075     
3076         if ( patternCharIndex >= level ) {
3077             return true;
3078         }    
3079         return false;
3080     }
3081
3082     protected NumberFormat getNumberFormat(char ch) {
3083
3084        Character ovrField;
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);
3089            return nf;
3090        } else {
3091            return numberFormat;
3092        }
3093     }
3094
3095     private void initNumberFormatters(ULocale loc) {
3096
3097        numberFormatters = new HashMap();
3098        overrideMap = new HashMap();
3099        processOverrideString(loc,override); 
3100
3101     }
3102
3103     private void processOverrideString(ULocale loc, String str) {
3104
3105         if ( str == null || str.length() == 0 )
3106             return;
3107
3108         int start = 0;
3109         int end;
3110         String nsName;
3111         Character ovrField;
3112         boolean moreToProcess = true;
3113         boolean fullOverride;
3114
3115         while (moreToProcess) {
3116             int delimiterPosition = str.indexOf(";",start);
3117             if (delimiterPosition == -1) {
3118                 moreToProcess = false;
3119                 end = str.length();
3120             } else {
3121                 end = delimiterPosition;
3122             }
3123
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;
3134             }
3135
3136             ULocale ovrLoc = new ULocale(loc.getBaseName()+"@numbers="+nsName);
3137             NumberFormat nf = NumberFormat.createInstance(ovrLoc,NumberFormat.NUMBERSTYLE);
3138             
3139             if (fullOverride) {
3140                 setNumberFormat(nf);
3141             } else {
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;
3145             }
3146
3147             if (!numberFormatters.containsKey(nsName)) {
3148                   numberFormatters.put(nsName,nf);
3149             }
3150
3151             start = delimiterPosition + 1;
3152
3153         }    
3154     }
3155 }