KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > ibm > icu > text > SimpleDateFormat


1 /*
2  *******************************************************************************
3  * Copyright (C) 1996-2007, International Business Machines Corporation and *
4  * others. All Rights Reserved. *
5  *******************************************************************************
6  */

7
8 package com.ibm.icu.text;
9
10 import java.io.IOException JavaDoc;
11 import java.io.ObjectInputStream JavaDoc;
12 import java.io.ObjectOutputStream JavaDoc;
13 import java.text.FieldPosition JavaDoc;
14 import java.text.MessageFormat JavaDoc;
15 import java.text.ParsePosition JavaDoc;
16 import java.util.ArrayList JavaDoc;
17 import java.util.Date JavaDoc;
18 import java.util.List JavaDoc;
19 import java.util.Locale JavaDoc;
20 import java.util.MissingResourceException JavaDoc;
21
22 import com.ibm.icu.impl.CalendarData;
23 import com.ibm.icu.impl.DateNumberFormat;
24 import com.ibm.icu.impl.ICUCache;
25 import com.ibm.icu.impl.SimpleCache;
26 import com.ibm.icu.impl.UCharacterProperty;
27 import com.ibm.icu.impl.ZoneMeta;
28 import com.ibm.icu.lang.UCharacter;
29 import com.ibm.icu.util.Calendar;
30 import com.ibm.icu.util.TimeZone;
31 import com.ibm.icu.util.ULocale;
32
33
34 /**
35  * <code>SimpleDateFormat</code> is a concrete class for formatting and
36  * parsing dates in a locale-sensitive manner. It allows for formatting
37  * (date -> text), parsing (text -> date), and normalization.
38  *
39  * <p>
40  * <code>SimpleDateFormat</code> allows you to start by choosing
41  * any user-defined patterns for date-time formatting. However, you
42  * are encouraged to create a date-time formatter with either
43  * <code>getTimeInstance</code>, <code>getDateInstance</code>, or
44  * <code>getDateTimeInstance</code> in <code>DateFormat</code>. Each
45  * of these class methods can return a date/time formatter initialized
46  * with a default format pattern. You may modify the format pattern
47  * using the <code>applyPattern</code> methods as desired.
48  * For more information on using these methods, see
49  * {@link DateFormat}.
50  *
51  * <p>
52  * <strong>Time Format Syntax:</strong>
53  * <p>
54  * To specify the time format use a <em>time pattern</em> string.
55  * In this pattern, all ASCII letters are reserved as pattern letters,
56  * which are defined as the following:
57  * <blockquote>
58  * <pre>
59  * Symbol Meaning Presentation Example
60  * ------ ------- ------------ -------
61  * G era designator (Text) AD
62  * y&#x2020; year (Number) 1996
63  * Y* year (week of year) (Number) 1997
64  * u* extended year (Number) 4601
65  * M month in year (Text & Number) July & 07
66  * d day in month (Number) 10
67  * h hour in am/pm (1~12) (Number) 12
68  * H hour in day (0~23) (Number) 0
69  * m minute in hour (Number) 30
70  * s second in minute (Number) 55
71  * S fractional second (Number) 978
72  * E day of week (Text) Tuesday
73  * e* day of week (local 1~7) (Number) 2
74  * D day in year (Number) 189
75  * F day of week in month (Number) 2 (2nd Wed in July)
76  * w week in year (Number) 27
77  * W week in month (Number) 2
78  * a am/pm marker (Text) PM
79  * k hour in day (1~24) (Number) 24
80  * K hour in am/pm (0~11) (Number) 0
81  * z time zone (Text) Pacific Standard Time
82  * Z time zone (RFC 822) (Number) -0800
83  * v time zone (generic) (Text) Pacific Time
84  * g* Julian day (Number) 2451334
85  * A* milliseconds in day (Number) 69540000
86  * ' escape for text (Delimiter) 'Date='
87  * '' single quote (Literal) 'o''clock'
88  * </pre>
89  * </blockquote>
90  * <tt><b>*</b></tt> These items are not supported by Java's SimpleDateFormat.<br>
91  * <tt><b>&#x2020;</b></tt> ICU interprets a single 'y' differently than Java.</p>
92  * <p>
93  * The count of pattern letters determine the format.
94  * <p>
95  * <strong>(Text)</strong>: 4 or more pattern letters--use full form,
96  * &lt; 4--use short or abbreviated form if one exists.
97  * <p>
98  * <strong>(Number)</strong>: the minimum number of digits. Shorter
99  * numbers are zero-padded to this amount. Year is handled specially;
100  * that is, if the count of 'y' is 2, the Year will be truncated to 2 digits.
101  * (e.g., if "yyyy" produces "1997", "yy" produces "97".)
102  * Unlike other fields, fractional seconds are padded on the right with zero.
103  * <p>
104  * <strong>(Text & Number)</strong>: 3 or over, use text, otherwise use number.
105  * <p>
106  * Any characters in the pattern that are not in the ranges of ['a'..'z']
107  * and ['A'..'Z'] will be treated as quoted text. For instance, characters
108  * like ':', '.', ' ', '#' and '@' will appear in the resulting time text
109  * even they are not embraced within single quotes.
110  * <p>
111  * A pattern containing any invalid pattern letter will result in a thrown
112  * exception during formatting or parsing.
113  *
114  * <p>
115  * <strong>Examples Using the US Locale:</strong>
116  * <blockquote>
117  * <pre>
118  * Format Pattern Result
119  * -------------- -------
120  * "yyyy.MM.dd G 'at' HH:mm:ss vvvv" ->> 1996.07.10 AD at 15:08:56 Pacific Time
121  * "EEE, MMM d, ''yy" ->> Wed, July 10, '96
122  * "h:mm a" ->> 12:08 PM
123  * "hh 'o''clock' a, zzzz" ->> 12 o'clock PM, Pacific Daylight Time
124  * "K:mm a, vvv" ->> 0:00 PM, PT
125  * "yyyyy.MMMMM.dd GGG hh:mm aaa" ->> 01996.July.10 AD 12:08 PM
126  * </pre>
127  * </blockquote>
128  * <strong>Code Sample:</strong>
129  * <blockquote>
130  * <pre>
131  * SimpleTimeZone pdt = new SimpleTimeZone(-8 * 60 * 60 * 1000, "PST");
132  * pdt.setStartRule(Calendar.APRIL, 1, Calendar.SUNDAY, 2*60*60*1000);
133  * pdt.setEndRule(Calendar.OCTOBER, -1, Calendar.SUNDAY, 2*60*60*1000);
134  * <br>
135  * // Format the current time.
136  * SimpleDateFormat formatter
137  * = new SimpleDateFormat ("yyyy.MM.dd G 'at' hh:mm:ss a zzz");
138  * Date currentTime_1 = new Date();
139  * String dateString = formatter.format(currentTime_1);
140  * <br>
141  * // Parse the previous string back into a Date.
142  * ParsePosition pos = new ParsePosition(0);
143  * Date currentTime_2 = formatter.parse(dateString, pos);
144  * </pre>
145  * </blockquote>
146  * In the example, the time value <code>currentTime_2</code> obtained from
147  * parsing will be equal to <code>currentTime_1</code>. However, they may not be
148  * equal if the am/pm marker 'a' is left out from the format pattern while
149  * the "hour in am/pm" pattern symbol is used. This information loss can
150  * happen when formatting the time in PM.
151  *
152  * <p>
153  * When parsing a date string using the abbreviated year pattern ("yy"),
154  * SimpleDateFormat must interpret the abbreviated year
155  * relative to some century. It does this by adjusting dates to be
156  * within 80 years before and 20 years after the time the SimpleDateFormat
157  * instance is created. For example, using a pattern of "MM/dd/yy" and a
158  * SimpleDateFormat instance created on Jan 1, 1997, the string
159  * "01/11/12" would be interpreted as Jan 11, 2012 while the string "05/04/64"
160  * would be interpreted as May 4, 1964.
161  * During parsing, only strings consisting of exactly two digits, as defined by
162  * {@link java.lang.Character#isDigit(char)}, will be parsed into the default
163  * century.
164  * Any other numeric string, such as a one digit string, a three or more digit
165  * string, or a two digit string that isn't all digits (for example, "-1"), is
166  * interpreted literally. So "01/02/3" or "01/02/003" are parsed, using the
167  * same pattern, as Jan 2, 3 AD. Likewise, "01/02/-3" is parsed as Jan 2, 4 BC.
168  *
169  * <p>
170  * If the year pattern does not have exactly two 'y' characters, the year is
171  * interpreted literally, regardless of the number of digits. So using the
172  * pattern "MM/dd/yyyy", "01/11/12" parses to Jan 11, 12 A.D.
173  *
174  * <p>
175  * When numeric fields abut one another directly, with no intervening delimiter
176  * characters, they constitute a run of abutting numeric fields. Such runs are
177  * parsed specially. For example, the format "HHmmss" parses the input text
178  * "123456" to 12:34:56, parses the input text "12345" to 1:23:45, and fails to
179  * parse "1234". In other words, the leftmost field of the run is flexible,
180  * while the others keep a fixed width. If the parse fails anywhere in the run,
181  * then the leftmost field is shortened by one character, and the entire run is
182  * parsed again. This is repeated until either the parse succeeds or the
183  * leftmost field is one character in length. If the parse still fails at that
184  * point, the parse of the run fails.
185  *
186  * <p>
187  * For time zones that have no names, use strings GMT+hours:minutes or
188  * GMT-hours:minutes.
189  *
190  * <p>
191  * The calendar defines what is the first day of the week, the first week
192  * of the year, whether hours are zero based or not (0 vs 12 or 24), and the
193  * time zone. There is one common decimal format to handle all the numbers;
194  * the digit count is handled programmatically according to the pattern.
195  *
196  * <h4>Synchronization</h4>
197  *
198  * Date formats are not synchronized. It is recommended to create separate
199  * format instances for each thread. If multiple threads access a format
200  * concurrently, it must be synchronized externally.
201  *
202  * @see com.ibm.icu.util.Calendar
203  * @see com.ibm.icu.util.GregorianCalendar
204  * @see com.ibm.icu.util.TimeZone
205  * @see DateFormat
206  * @see DateFormatSymbols
207  * @see DecimalFormat
208  * @author Mark Davis, Chen-Lieh Huang, Alan Liu
209  * @stable ICU 2.0
210  */

211 public class SimpleDateFormat extends DateFormat {
212
213     // the official serial version ID which says cryptically
214
// which version we're compatible with
215
private static final long serialVersionUID = 4774881970558875024L;
216
217     // the internal serial version which says which version was written
218
// - 0 (default) for version up to JDK 1.1.3
219
// - 1 for version from JDK 1.1.4, which includes a new field
220
static final int currentSerialVersion = 1;
221
222     /**
223      * The version of the serialized data on the stream. Possible values:
224      * <ul>
225      * <li><b>0</b> or not present on stream: JDK 1.1.3. This version
226      * has no <code>defaultCenturyStart</code> on stream.
227      * <li><b>1</b> JDK 1.1.4 or later. This version adds
228      * <code>defaultCenturyStart</code>.
229      * </ul>
230      * When streaming out this class, the most recent format
231      * and the highest allowable <code>serialVersionOnStream</code>
232      * is written.
233      * @serial
234      */

235     private int serialVersionOnStream = currentSerialVersion;
236
237     /**
238      * The pattern string of this formatter. This is always a non-localized
239      * pattern. May not be null. See class documentation for details.
240      * @serial
241      */

242     private String JavaDoc pattern;
243
244     /**
245      * The symbols used by this formatter for week names, month names,
246      * etc. May not be null.
247      * @serial
248      * @see DateFormatSymbols
249      */

250     private DateFormatSymbols formatData;
251
252     private transient ULocale locale;
253
254     /**
255      * We map dates with two-digit years into the century starting at
256      * <code>defaultCenturyStart</code>, which may be any date. May
257      * not be null.
258      * @serial
259      * @since JDK1.1.4
260      */

261     private Date JavaDoc defaultCenturyStart;
262
263     private transient int defaultCenturyStartYear;
264
265     // defaultCenturyBase is set when an instance is created
266
// and may be used for calculating defaultCenturyStart when needed.
267
private transient long defaultCenturyBase;
268
269     private transient TimeZone parsedTimeZone;
270
271     private static final int millisPerHour = 60 * 60 * 1000;
272     private static final int millisPerMinute = 60 * 1000;
273
274     // For time zones that have no names, use strings GMT+minutes and
275
// GMT-minutes. For instance, in France the time zone is GMT+60.
276
private static final String JavaDoc GMT_PLUS = "GMT+";
277     private static final String JavaDoc GMT_MINUS = "GMT-";
278     private static final String JavaDoc GMT = "GMT";
279
280     // This prefix is designed to NEVER MATCH real text, in order to
281
// suppress the parsing of negative numbers. Adjust as needed (if
282
// this becomes valid Unicode).
283
private static final String JavaDoc SUPPRESS_NEGATIVE_PREFIX = "\uAB00";
284
285     /**
286      * If true, this object supports fast formatting using the
287      * subFormat variant that takes a StringBuffer.
288      */

289     private transient boolean useFastFormat;
290
291     /**
292      * Construct a SimpleDateFormat using the default pattern for the default
293      * locale. <b>Note:</b> Not all locales support SimpleDateFormat; for full
294      * generality, use the factory methods in the DateFormat class.
295      *
296      * @see DateFormat
297      * @stable ICU 2.0
298      */

299     public SimpleDateFormat() {
300         this(getDefaultPattern(), null, null, null, null, true);
301     }
302
303     /**
304      * Construct a SimpleDateFormat using the given pattern in the default
305      * locale. <b>Note:</b> Not all locales support SimpleDateFormat; for full
306      * generality, use the factory methods in the DateFormat class.
307      * @stable ICU 2.0
308      */

309     public SimpleDateFormat(String JavaDoc pattern)
310     {
311         this(pattern, null, null, null, null, true);
312     }
313
314     /**
315      * Construct a SimpleDateFormat using the given pattern and locale.
316      * <b>Note:</b> Not all locales support SimpleDateFormat; for full
317      * generality, use the factory methods in the DateFormat class.
318      * @stable ICU 2.0
319      */

320     public SimpleDateFormat(String JavaDoc pattern, Locale JavaDoc loc)
321     {
322         this(pattern, null, null, null, ULocale.forLocale(loc), true);
323     }
324
325     /**
326      * Construct a SimpleDateFormat using the given pattern and locale.
327      * <b>Note:</b> Not all locales support SimpleDateFormat; for full
328      * generality, use the factory methods in the DateFormat class.
329      * @draft ICU 3.2
330      * @provisional This API might change or be removed in a future release.
331      */

332     public SimpleDateFormat(String JavaDoc pattern, ULocale loc)
333     {
334         this(pattern, null, null, null, loc, true);
335     }
336
337     /**
338      * Construct a SimpleDateFormat using the given pattern and
339      * locale-specific symbol data.
340      * Warning: uses default locale for digits!
341      * @stable ICU 2.0
342      */

343     public SimpleDateFormat(String JavaDoc pattern, DateFormatSymbols formatData)
344     {
345         this(pattern, (DateFormatSymbols)formatData.clone(), null, null, null, true);
346     }
347
348     /**
349      * @internal ICU 3.2
350      * @deprecated This API is ICU internal only.
351      */

352     public SimpleDateFormat(String JavaDoc pattern, DateFormatSymbols formatData, ULocale loc)
353     {
354         this(pattern, (DateFormatSymbols)formatData.clone(), null, null, loc, true);
355     }
356
357     /**
358      * Package-private constructor that allows a subclass to specify
359      * whether it supports fast formatting.
360      *
361      * TODO make this API public.
362      */

363     SimpleDateFormat(String JavaDoc pattern, DateFormatSymbols formatData, Calendar calendar, ULocale locale,
364                      boolean useFastFormat) {
365         this(pattern, (DateFormatSymbols)formatData.clone(), (Calendar)calendar.clone(), null, locale, useFastFormat);
366     }
367
368     /*
369      * The constructor called from all other SimpleDateFormat constructors
370      */

371     private SimpleDateFormat(String JavaDoc pattern, DateFormatSymbols formatData, Calendar calendar,
372             NumberFormat numberFormat, ULocale locale, boolean useFastFormat) {
373         this.pattern = pattern;
374         this.formatData = formatData;
375         this.calendar = calendar;
376         this.numberFormat = numberFormat;
377         this.locale = locale; // time zone formatting
378
this.useFastFormat = useFastFormat;
379         initialize();
380     }
381
382     public static SimpleDateFormat getInstance(Calendar.FormatConfiguration formatConfig) {
383         return new SimpleDateFormat(formatConfig.getPatternString(),
384                     formatConfig.getDateFormatSymbols(),
385                     formatConfig.getCalendar(),
386                     null,
387                     formatConfig.getLocale(),
388                     true);
389     }
390
391     /*
392      * Initialized fields
393      */

394     private void initialize() {
395         if (locale == null) {
396             locale = ULocale.getDefault();
397         }
398         if (formatData == null) {
399             formatData = new DateFormatSymbols(locale);
400         }
401         if (calendar == null) {
402             calendar = Calendar.getInstance(locale);
403         }
404         if (numberFormat == null) {
405             // Use a NumberFormat optimized for date formatting
406
numberFormat = new DateNumberFormat(locale);
407         }
408         // Note: deferring calendar calculation until when we really need it.
409
// Instead, we just record time of construction for backward compatibility.
410
defaultCenturyBase = System.currentTimeMillis();
411
412         initLocalZeroPaddingNumberFormat();
413     }
414
415     // privates for the default pattern
416
private static ULocale cachedDefaultLocale = null;
417     private static String JavaDoc cachedDefaultPattern = null;
418     private static final String JavaDoc FALLBACKPATTERN = "yy/MM/dd HH:mm";
419
420     /*
421      * Returns the default date and time pattern (SHORT) for the default locale.
422      * This method is only used by the default SimpleDateFormat constructor.
423      */

424     private static synchronized String JavaDoc getDefaultPattern() {
425         ULocale defaultLocale = ULocale.getDefault();
426         if (!defaultLocale.equals(cachedDefaultLocale)) {
427             cachedDefaultLocale = defaultLocale;
428             Calendar cal = Calendar.getInstance(cachedDefaultLocale);
429             try {
430                 CalendarData calData = new CalendarData(cachedDefaultLocale, cal.getType());
431                 String JavaDoc[] dateTimePatterns = calData.getStringArray("DateTimePatterns");
432                 cachedDefaultPattern = MessageFormat.format(dateTimePatterns[8],
433                         new Object JavaDoc[] {dateTimePatterns[SHORT], dateTimePatterns[SHORT + 4]});
434             } catch (MissingResourceException JavaDoc e) {
435                 cachedDefaultPattern = FALLBACKPATTERN;
436             }
437         }
438         return cachedDefaultPattern;
439     }
440
441     /* Define one-century window into which to disambiguate dates using
442      * two-digit years.
443      */

444     private void parseAmbiguousDatesAsAfter(Date JavaDoc startDate) {
445         defaultCenturyStart = startDate;
446         calendar.setTime(startDate);
447         defaultCenturyStartYear = calendar.get(Calendar.YEAR);
448     }
449
450     /* Initialize defaultCenturyStart and defaultCenturyStartYear by base time.
451      * The default start time is 80 years before the creation time of this object.
452      */

453     private void initializeDefaultCenturyStart(long baseTime) {
454         defaultCenturyBase = baseTime;
455         // clone to avoid messing up date stored in calendar object
456
// when this method is called while parsing
457
Calendar tmpCal = (Calendar)calendar.clone();
458         tmpCal.setTimeInMillis(baseTime);
459         tmpCal.add(Calendar.YEAR, -80);
460         defaultCenturyStart = tmpCal.getTime();
461         defaultCenturyStartYear = tmpCal.get(Calendar.YEAR);
462     }
463
464     /* Gets the default century start date for this object */
465     private Date JavaDoc getDefaultCenturyStart() {
466         if (defaultCenturyStart == null) {
467             // not yet initialized
468
initializeDefaultCenturyStart(defaultCenturyBase);
469         }
470         return defaultCenturyStart;
471     }
472
473     /* Gets the default century start year for this object */
474     private int getDefaultCenturyStartYear() {
475         if (defaultCenturyStart == null) {
476             // not yet initialized
477
initializeDefaultCenturyStart(defaultCenturyBase);
478         }
479         return defaultCenturyStartYear;
480     }
481
482     /**
483      * Sets the 100-year period 2-digit years will be interpreted as being in
484      * to begin on the date the user specifies.
485      * @param startDate During parsing, two digit years will be placed in the range
486      * <code>startDate</code> to <code>startDate + 100 years</code>.
487      * @stable ICU 2.0
488      */

489     public void set2DigitYearStart(Date JavaDoc startDate) {
490         parseAmbiguousDatesAsAfter(startDate);
491     }
492
493     /**
494      * Returns the beginning date of the 100-year period 2-digit years are interpreted
495      * as being within.
496      * @return the start of the 100-year period into which two digit years are
497      * parsed
498      * @stable ICU 2.0
499      */

500     public Date JavaDoc get2DigitYearStart() {
501         return getDefaultCenturyStart();
502     }
503
504     /**
505      * Overrides DateFormat.
506      * <p>Formats a date or time, which is the standard millis
507      * since January 1, 1970, 00:00:00 GMT.
508      * <p>Example: using the US locale:
509      * "yyyy.MM.dd G 'at' HH:mm:ss zzz" ->> 1996.07.10 AD at 15:08:56 PDT
510      * @param cal the calendar whose date-time value is to be formatted into a date-time string
511      * @param toAppendTo where the new date-time text is to be appended
512      * @param pos the formatting position. On input: an alignment field,
513      * if desired. On output: the offsets of the alignment field.
514      * @return the formatted date-time string.
515      * @see DateFormat
516      * @stable ICU 2.0
517      */

518     public StringBuffer JavaDoc format(Calendar cal, StringBuffer JavaDoc toAppendTo,
519                                FieldPosition JavaDoc pos) {
520         // Initialize
521
pos.setBeginIndex(0);
522         pos.setEndIndex(0);
523
524         // Careful: For best performance, minimize the number of calls
525
// to StringBuffer.append() by consolidating appends when
526
// possible.
527

528         Object JavaDoc[] items = getPatternItems();
529         for (int i = 0; i < items.length; i++) {
530             if (items[i] instanceof String JavaDoc) {
531                 toAppendTo.append((String JavaDoc)items[i]);
532             } else {
533                 PatternItem item = (PatternItem)items[i];
534                 if (useFastFormat) {
535                     subFormat(toAppendTo, item.type, item.length, toAppendTo.length(), pos, cal);
536                 } else {
537                     toAppendTo.append(subFormat(item.type, item.length, toAppendTo.length(), pos, formatData, cal));
538                 }
539             }
540         }
541         return toAppendTo;
542     }
543
544     // Map pattern character to index
545
private static final int PATTERN_CHAR_BASE = 0x40;
546     private static final int[] PATTERN_CHAR_TO_INDEX =
547     {
548     // A B C D E F G H I J K L M N O
549
-1, 22, -1, -1, 10, 9, 11, 0, 5, -1, -1, 16, 26, 2, -1, -1,
550     // P Q R S T U V W X Y Z
551
-1, 27, -1, 8, -1, -1, -1, 13, -1, 18, 23, -1, -1, -1, -1, -1,
552     // a b c d e f g h i j k l m n o
553
-1, 14, -1, 25, 3, 19, -1, 21, 15, -1, -1, 4, -1, 6, -1, -1,
554     // p q r s t u v w x y z
555
-1, 28, -1, 7, -1, 20, 24, 12, -1, 1, 17, -1, -1, -1, -1, -1
556     };
557     
558     // Map index into pattern character string to Calendar field number
559
private static final int[] PATTERN_INDEX_TO_CALENDAR_FIELD =
560     {
561         /*GyM*/ Calendar.ERA, Calendar.YEAR, Calendar.MONTH,
562         /*dkH*/ Calendar.DATE, Calendar.HOUR_OF_DAY, Calendar.HOUR_OF_DAY,
563         /*msS*/ Calendar.MINUTE, Calendar.SECOND, Calendar.MILLISECOND,
564         /*EDF*/ Calendar.DAY_OF_WEEK, Calendar.DAY_OF_YEAR, Calendar.DAY_OF_WEEK_IN_MONTH,
565         /*wWa*/ Calendar.WEEK_OF_YEAR, Calendar.WEEK_OF_MONTH, Calendar.AM_PM,
566         /*hKz*/ Calendar.HOUR, Calendar.HOUR, Calendar.ZONE_OFFSET,
567         /*Yeu*/ Calendar.YEAR_WOY, Calendar.DOW_LOCAL, Calendar.EXTENDED_YEAR,
568         /*gAZ*/ Calendar.JULIAN_DAY, Calendar.MILLISECONDS_IN_DAY, Calendar.ZONE_OFFSET,
569         /*v*/ Calendar.ZONE_OFFSET,
570         /*c*/ Calendar.DAY_OF_WEEK,
571         /*L*/ Calendar.MONTH,
572         /*Qq*/ Calendar.MONTH, Calendar.MONTH,
573     };
574
575     // Map index into pattern character string to DateFormat field number
576
private static final int[] PATTERN_INDEX_TO_DATE_FORMAT_FIELD = {
577         /*GyM*/ DateFormat.ERA_FIELD, DateFormat.YEAR_FIELD, DateFormat.MONTH_FIELD,
578         /*dkH*/ DateFormat.DATE_FIELD, DateFormat.HOUR_OF_DAY1_FIELD, DateFormat.HOUR_OF_DAY0_FIELD,
579         /*msS*/ DateFormat.MINUTE_FIELD, DateFormat.SECOND_FIELD, DateFormat.FRACTIONAL_SECOND_FIELD,
580         /*EDF*/ DateFormat.DAY_OF_WEEK_FIELD, DateFormat.DAY_OF_YEAR_FIELD, DateFormat.DAY_OF_WEEK_IN_MONTH_FIELD,
581         /*wWa*/ DateFormat.WEEK_OF_YEAR_FIELD, DateFormat.WEEK_OF_MONTH_FIELD, DateFormat.AM_PM_FIELD,
582         /*hKz*/ DateFormat.HOUR1_FIELD, DateFormat.HOUR0_FIELD, DateFormat.TIMEZONE_FIELD,
583         /*Yeu*/ DateFormat.YEAR_WOY_FIELD, DateFormat.DOW_LOCAL_FIELD, DateFormat.EXTENDED_YEAR_FIELD,
584         /*gAZ*/ DateFormat.JULIAN_DAY_FIELD, DateFormat.MILLISECONDS_IN_DAY_FIELD, DateFormat.TIMEZONE_RFC_FIELD,
585         /*v*/ DateFormat.TIMEZONE_GENERIC_FIELD,
586         /*c*/ DateFormat.STANDALONE_DAY_FIELD,
587         /*L*/ DateFormat.STANDALONE_MONTH_FIELD,
588         /*Q*/ DateFormat.QUARTER_FIELD,
589         /*q*/ DateFormat.STANDALONE_QUARTER_FIELD,
590     };
591
592     /**
593      * Format a single field, given its pattern character. Subclasses may
594      * override this method in order to modify or add formatting
595      * capabilities.
596      * @param ch the pattern character
597      * @param count the number of times ch is repeated in the pattern
598      * @param beginOffset the offset of the output string at the start of
599      * this field; used to set pos when appropriate
600      * @param pos receives the position of a field, when appropriate
601      * @param formatData the symbols for this formatter
602      * @stable ICU 2.0
603      */

604     protected String JavaDoc subFormat(char ch, int count, int beginOffset,
605                                FieldPosition JavaDoc pos, DateFormatSymbols formatData,
606                                Calendar cal)
607         throws IllegalArgumentException JavaDoc
608     {
609         // Note: formatData is ignored
610
StringBuffer JavaDoc buf = new StringBuffer JavaDoc();
611         subFormat(buf, ch, count, beginOffset, pos, cal);
612         return buf.toString();
613     }
614
615     /**
616      * Format a single field; useFastFormat variant. Reuses a
617      * StringBuffer for results instead of creating a String on the
618      * heap for each call.
619      *
620      * NOTE We don't really need the beginOffset parameter, EXCEPT for
621      * the need to support the slow subFormat variant (above) which
622      * has to pass it in to us.
623      *
624      * TODO make this API public
625      *
626      * @internal
627      * @deprecated This API is ICU internal only.
628      */

629     protected void subFormat(StringBuffer JavaDoc buf,
630                              char ch, int count, int beginOffset,
631                              FieldPosition JavaDoc pos,
632                              Calendar cal) {
633         final int maxIntCount = Integer.MAX_VALUE;
634         final int bufstart = buf.length();
635
636         // final int patternCharIndex = DateFormatSymbols.patternChars.indexOf(ch);
637
int patternCharIndex = -1;
638         if ('A' <= ch && ch <= 'z') {
639             patternCharIndex = PATTERN_CHAR_TO_INDEX[(int)ch - PATTERN_CHAR_BASE];
640         }
641
642         if (patternCharIndex == -1) {
643             throw new IllegalArgumentException JavaDoc("Illegal pattern character " +
644                                                "'" + ch + "' in \"" +
645                                                new String JavaDoc(pattern) + '"');
646         }
647
648         final int field = PATTERN_INDEX_TO_CALENDAR_FIELD[patternCharIndex];
649         int value = cal.get(field);
650
651         switch (patternCharIndex) {
652         case 0: // 'G' - ERA
653
if (count == 5) {
654                 buf.append(formatData.narrowEras[value]);
655             } else if (count == 4)
656                buf.append(formatData.eraNames[value]);
657             else
658                buf.append(formatData.eras[value]);
659             break;
660         case 1: // 'y' - YEAR
661
/* According to the specification, if the number of pattern letters ('y') is 2,
662              * the year is truncated to 2 digits; otherwise it is interpreted as a number.
663              * But the original code process 'y', 'yy', 'yyy' in the same way. and process
664              * patterns with 4 or more than 4 'y' characters in the same way.
665              * So I change the codes to meet the specification. [Richard/GCl]
666              */

667             if (count == 2)
668                 zeroPaddingNumber(buf, value, 2, 2); // clip 1996 to 96
669
else //count = 1 or count > 2
670
zeroPaddingNumber(buf, value, count, maxIntCount);
671             break;
672         case 2: // 'M' - MONTH
673
if (count == 5)
674                 buf.append(formatData.narrowMonths[value]);
675             else if (count == 4)
676                 buf.append(formatData.months[value]);
677             else if (count == 3)
678                 buf.append(formatData.shortMonths[value]);
679             else
680                 zeroPaddingNumber(buf, value+1, count, maxIntCount);
681             break;
682         case 4: // 'k' - HOUR_OF_DAY (1..24)
683
if (value == 0)
684                 zeroPaddingNumber(buf,
685                                   cal.getMaximum(Calendar.HOUR_OF_DAY)+1,
686                                   count, maxIntCount);
687             else
688                 zeroPaddingNumber(buf, value, count, maxIntCount);
689             break;
690         case 8: // 'S' - FRACTIONAL_SECOND
691
// Fractional seconds left-justify
692
{
693                 numberFormat.setMinimumIntegerDigits(Math.min(3, count));
694                 numberFormat.setMaximumIntegerDigits(maxIntCount);
695                 if (count == 1) {
696                     value = (value + 50) / 100;
697                 } else if (count == 2) {
698                     value = (value + 5) / 10;
699                 }
700                 FieldPosition JavaDoc p = new FieldPosition JavaDoc(-1);
701                 numberFormat.format((long) value, buf, p);
702                 if (count > 3) {
703                     numberFormat.setMinimumIntegerDigits(count - 3);
704                     numberFormat.format(0L, buf, p);
705                 }
706             }
707             break;
708         case 9: // 'E' - DAY_OF_WEEK
709
if (count == 5) {
710                 buf.append(formatData.narrowWeekdays[value]);
711             } else if (count == 4)
712                 buf.append(formatData.weekdays[value]);
713             else // count <= 3, use abbreviated form if exists
714
buf.append(formatData.shortWeekdays[value]);
715             break;
716         case 14: // 'a' - AM_PM
717
buf.append(formatData.ampms[value]);
718             break;
719         case 15: // 'h' - HOUR (1..12)
720
if (value == 0)
721                 zeroPaddingNumber(buf,
722                                   cal.getLeastMaximum(Calendar.HOUR)+1,
723                                   count, maxIntCount);
724             else
725                 zeroPaddingNumber(buf, value, count, maxIntCount);
726             break;
727         case 17: // 'z' - ZONE_OFFSET
728
case 24: // 'v' - TIMEZONE_GENERIC
729
{
730
731                 String JavaDoc zid;
732                 String JavaDoc res=null;
733                 zid = ZoneMeta.getCanonicalID(cal.getTimeZone().getID());
734                 boolean isGeneric = patternCharIndex == 24;
735                 if (zid != null) {
736                     if (patternCharIndex == TIMEZONE_GENERIC_FIELD) {
737                         if(count < 4){
738                             res = formatData.getZoneString(zid, DateFormatSymbols.TIMEZONE_SHORT_GENERIC);
739                         }else{
740                             res = formatData.getZoneString(zid, DateFormatSymbols.TIMEZONE_LONG_GENERIC);
741                         }
742                     } else {
743                         if (cal.get(Calendar.DST_OFFSET) != 0) {
744                             if(count < 4){
745                                 res = formatData.getZoneString(zid, DateFormatSymbols.TIMEZONE_SHORT_DAYLIGHT);
746                             }else{
747                                 res = formatData.getZoneString(zid, DateFormatSymbols.TIMEZONE_LONG_DAYLIGHT);
748                             }
749                         }else{
750                             if(count < 4){
751                                 res = formatData.getZoneString(zid, DateFormatSymbols.TIMEZONE_SHORT_STANDARD);
752                             }else{
753                                 res = formatData.getZoneString(zid, DateFormatSymbols.TIMEZONE_LONG_STANDARD);
754                             }
755                         }
756                     }
757                 }
758                 if (res == null || res.length() == 0) {
759                     // note, tr35 does not describe the special case for 'no country'
760
// implemented below, this is from discussion with Mark
761
if (zid == null || !isGeneric || ZoneMeta.getCanonicalCountry(zid) == null) {
762                          long offset = cal.get(Calendar.ZONE_OFFSET) +
763                             cal.get(Calendar.DST_OFFSET);
764                         res = ZoneMeta.displayGMT(offset, locale);
765                     } else {
766                         /*
767                         String city = formatData.getZoneString(zid, DateFormatSymbols.TIMEZONE_EXEMPLAR_CITY);
768                         res = ZoneMeta.displayFallback(zid, city, locale);
769                         */

770                         res = formatData.getZoneString(zid, DateFormatSymbols.TIMEZONE_EXEMPLAR_CITY);
771                         
772                         if (res == null) {
773                             res = ZoneMeta.displayFallback(zid, null, locale);
774                         }
775                     }
776                 }
777                 
778                 if(res.length()==0){
779                     appendGMT(buf, cal);
780                 }else{
781                     buf.append(res);
782                 }
783             } break;
784         case 23: // 'Z' - TIMEZONE_RFC
785
{
786                 if (count < 4) {
787                     // 'short' (standard Java) form, must use ASCII digits
788
long val= (cal.get(Calendar.ZONE_OFFSET) +
789                            cal.get(Calendar.DST_OFFSET)) / millisPerMinute;
790                     char sign = '+';
791                     if (val < 0) {
792                         val = -val;
793                         sign = '-';
794                     }
795                     val = (val / 60) * 100 + (val % 60); // minutes => KKmm
796
buf.append(sign);
797
798                     // Always use ASCII numbers
799
int num = (int)(val % 10000);
800                     int denom = 1000;
801                     while (denom >= 1) {
802                         char digit = (char)((num / denom) + '0');
803                         buf.append(digit);
804                         num = num % denom;
805                         denom /= 10;
806                     }
807                 } else {
808                     // long form, localized GMT pattern
809
// not in 3.4 locale data, need to add, so use same default as for general time zone names
810
long val = cal.get(Calendar.ZONE_OFFSET) +
811                         cal.get(Calendar.DST_OFFSET);
812                     buf.append(ZoneMeta.displayGMT(val, locale));
813                 }
814             }
815             break;
816         case 25: // 'c' - STANDALONE DAY
817
if (count == 5)
818                 buf.append(formatData.standaloneNarrowWeekdays[value]);
819             else if (count == 4)
820                 buf.append(formatData.standaloneWeekdays[value]);
821             else if (count == 3)
822                 buf.append(formatData.standaloneShortWeekdays[value]);
823             else
824                 zeroPaddingNumber(buf, value, 1, maxIntCount);
825             break;
826         case 26: // 'L' - STANDALONE MONTH
827
if (count == 5)
828                 buf.append(formatData.standaloneNarrowMonths[value]);
829             else if (count == 4)
830                 buf.append(formatData.standaloneMonths[value]);
831             else if (count == 3)
832                 buf.append(formatData.standaloneShortMonths[value]);
833             else
834                 zeroPaddingNumber(buf, value+1, count, maxIntCount);
835             break;
836         case 27: // 'Q' - QUARTER
837
if (count >= 4)
838                 buf.append(formatData.quarters[value/3]);
839             else if (count == 3)
840                 buf.append(formatData.shortQuarters[value/3]);
841             else
842                 zeroPaddingNumber(buf, (value/3)+1, count, maxIntCount);
843             break;
844         case 28: // 'q' - STANDALONE QUARTER
845
if (count >= 4)
846                 buf.append(formatData.standaloneQuarters[value/3]);
847             else if (count == 3)
848                 buf.append(formatData.standaloneShortQuarters[value/3]);
849             else
850                 zeroPaddingNumber(buf, (value/3)+1, count, maxIntCount);
851             break;
852         default:
853             // case 3: // 'd' - DATE
854
// case 5: // 'H' - HOUR_OF_DAY (0..23)
855
// case 6: // 'm' - MINUTE
856
// case 7: // 's' - SECOND
857
// case 10: // 'D' - DAY_OF_YEAR
858
// case 11: // 'F' - DAY_OF_WEEK_IN_MONTH
859
// case 12: // 'w' - WEEK_OF_YEAR
860
// case 13: // 'W' - WEEK_OF_MONTH
861
// case 16: // 'K' - HOUR (0..11)
862
// case 18: // 'Y' - YEAR_WOY
863
// case 19: // 'e' - DOW_LOCAL
864
// case 20: // 'u' - EXTENDED_YEAR
865
// case 21: // 'g' - JULIAN_DAY
866
// case 22: // 'A' - MILLISECONDS_IN_DAY
867

868             zeroPaddingNumber(buf, value, count, maxIntCount);
869             break;
870         } // switch (patternCharIndex)
871

872         // Set the FieldPosition (for the first occurence only)
873
if (pos.getBeginIndex() == pos.getEndIndex() &&
874             pos.getField() == PATTERN_INDEX_TO_DATE_FORMAT_FIELD[patternCharIndex]) {
875             pos.setBeginIndex(beginOffset);
876             pos.setEndIndex(beginOffset + buf.length() - bufstart);
877         }
878     }
879
880     /*
881      * PatternItem store parsed date/time field pattern information.
882      */

883     private static class PatternItem {
884         final char type;
885         final int length;
886         final boolean isNumeric;
887
888         PatternItem(char type, int length) {
889             this.type = type;
890             this.length = length;
891             isNumeric = isNumeric(type, length);
892         }
893     }
894
895     private static ICUCache PARSED_PATTERN_CACHE = new SimpleCache();
896     private transient Object JavaDoc[] patternItems;
897
898     /*
899      * Returns parsed pattern items. Each item is either String or
900      * PatternItem.
901      */

902     private Object JavaDoc[] getPatternItems() {
903         if (patternItems != null) {
904             return patternItems;
905         }
906
907         patternItems = (Object JavaDoc[])PARSED_PATTERN_CACHE.get(pattern);
908         if (patternItems != null) {
909             return patternItems;
910         }
911
912         boolean isPrevQuote = false;
913         boolean inQuote = false;
914         StringBuffer JavaDoc text = new StringBuffer JavaDoc();
915         char itemType = 0; // 0 for string literal, otherwise date/time pattern character
916
int itemLength = 1;
917
918         List JavaDoc items = new ArrayList JavaDoc();
919
920         for (int i = 0; i < pattern.length(); i++) {
921             char ch = pattern.charAt(i);
922             if (ch == '\'') {
923                 if (isPrevQuote) {
924                     text.append('\'');
925                     isPrevQuote = false;
926                 } else {
927                     isPrevQuote = true;
928                     if (itemType != 0) {
929                         items.add(new PatternItem(itemType, itemLength));
930                         itemType = 0;
931                     }
932                 }
933                 inQuote = !inQuote;
934             } else {
935                 isPrevQuote = false;
936                 if (inQuote) {
937                     text.append(ch);
938                 } else {
939                     if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')) {
940                         // a date/time pattern character
941
if (ch == itemType) {
942                             itemLength++;
943                         } else {
944                             if (itemType == 0) {
945                                 if (text.length() > 0) {
946                                     items.add(text.toString());
947                                     text.setLength(0);
948                                 }
949                             } else {
950                                 items.add(new PatternItem(itemType, itemLength));
951                             }
952                             itemType = ch;
953                             itemLength = 1;
954                         }
955                     } else {
956                         // a string literal
957
if (itemType != 0) {
958                             items.add(new PatternItem(itemType, itemLength));
959                             itemType = 0;
960                         }
961                         text.append(ch);
962                     }
963                 }
964             }
965         }
966         // handle last item
967
if (itemType == 0) {
968             if (text.length() > 0) {
969                 items.add(text.toString());
970                 text.setLength(0);
971             }
972         } else {
973             items.add(new PatternItem(itemType, itemLength));
974         }
975
976         patternItems = new Object JavaDoc[items.size()];
977         items.toArray(patternItems);
978
979         PARSED_PATTERN_CACHE.put(pattern, patternItems);
980         
981         return patternItems;
982     }
983
984     private void appendGMT(StringBuffer JavaDoc buf, Calendar cal){
985         int value = cal.get(Calendar.ZONE_OFFSET) +
986         cal.get(Calendar.DST_OFFSET);
987
988         if (value < 0) {
989             buf.append(GMT_MINUS);
990             value = -value; // suppress the '-' sign for text display.
991
}else{
992             buf.append(GMT_PLUS);
993         }
994
995         zeroPaddingNumber(buf, (int)(value/millisPerHour), 2, 2);
996         buf.append((char)0x003A) /*':'*/;
997         zeroPaddingNumber(buf, (int)((value%millisPerHour)/millisPerMinute), 2, 2);
998     }
999     /*
1000     * Internal method. Returns null if the value of an array is empty, or if the
1001     * index is out of bounds
1002     */

1003/* private String getZoneArrayValue(String[] zs, int ix) {
1004        if (ix >= 0 && ix < zs.length) {
1005            String result = zs[ix];
1006            if (result != null && result.length() != 0) {
1007                return result;
1008            }
1009        }
1010        return null;
1011    }*/

1012
1013    /**
1014     * Internal high-speed method. Reuses a StringBuffer for results
1015     * instead of creating a String on the heap for each call.
1016     * @internal
1017     * @deprecated This API is ICU internal only.
1018     */

1019    protected void zeroPaddingNumber(StringBuffer JavaDoc buf, int value,
1020                                     int minDigits, int maxDigits) {
1021        if (useLocalZeroPaddingNumberFormat) {
1022            fastZeroPaddingNumber(buf, value, minDigits, maxDigits);
1023        } else {
1024            numberFormat.setMinimumIntegerDigits(minDigits);
1025            numberFormat.setMaximumIntegerDigits(maxDigits);
1026            numberFormat.format(value, buf, new FieldPosition JavaDoc(-1));
1027        }
1028    }
1029
1030    /**
1031     * Overrides superclass method
1032     * @stable ICU 2.0
1033     */

1034    public void setNumberFormat(NumberFormat newNumberFormat) {
1035        // Override this method to update local zero padding number formatter
1036
super.setNumberFormat(newNumberFormat);
1037        initLocalZeroPaddingNumberFormat();
1038    }
1039
1040    private void initLocalZeroPaddingNumberFormat() {
1041        if (numberFormat instanceof DecimalFormat) {
1042            zeroDigit = ((DecimalFormat)numberFormat).getDecimalFormatSymbols().getZeroDigit();
1043            useLocalZeroPaddingNumberFormat = true;
1044        } else if (numberFormat instanceof DateNumberFormat) {
1045            zeroDigit = ((DateNumberFormat)numberFormat).getZeroDigit();
1046            useLocalZeroPaddingNumberFormat = true;
1047        } else {
1048            useLocalZeroPaddingNumberFormat = false;
1049        }
1050
1051        if (useLocalZeroPaddingNumberFormat) {
1052            decimalBuf = new char[10]; // sufficient for int numbers
1053
}
1054    }
1055
1056    // If true, use local version of zero padding number format
1057
private transient boolean useLocalZeroPaddingNumberFormat;
1058    private transient char zeroDigit;
1059    private transient char[] decimalBuf;
1060
1061    /*
1062     * Lightweight zero padding integer number format function.
1063     *
1064     * Note: This implementation is almost equivalent to format method in DateNumberFormat.
1065     * In the method zeroPaddingNumber above should be able to use the one in DateNumberFormat,
1066     * but, it does not help IBM J9's JIT to optimize the performance much. In simple repeative
1067     * date format test case, having local implementation is ~10% faster than using one in
1068     * DateNumberFormat on IBM J9 VM. On Sun Hotspot VM, I do not see such difference.
1069     *
1070     * -Yoshito
1071     */

1072    private void fastZeroPaddingNumber(StringBuffer JavaDoc buf, int value, int minDigits, int maxDigits) {
1073        int limit = decimalBuf.length < maxDigits ? decimalBuf.length : maxDigits;
1074        int index = limit - 1;
1075        while (true) {
1076            decimalBuf[index] = (char)((value % 10) + zeroDigit);
1077            value /= 10;
1078            if (index == 0 || value == 0) {
1079                break;
1080            }
1081            index--;
1082        }
1083        int padding = minDigits - (limit - index);
1084        for (; padding > 0; padding--) {
1085            decimalBuf[--index] = zeroDigit;
1086        }
1087        int length = limit - index;
1088        buf.append(decimalBuf, index, length);
1089    }
1090
1091    /**
1092     * Formats a number with the specified minimum and maximum number of digits.
1093     * @stable ICU 2.0
1094     */

1095    protected String JavaDoc zeroPaddingNumber(long value, int minDigits, int maxDigits)
1096    {
1097        numberFormat.setMinimumIntegerDigits(minDigits);
1098        numberFormat.setMaximumIntegerDigits(maxDigits);
1099        return numberFormat.format(value);
1100    }
1101
1102    /**
1103     * Format characters that indicate numeric fields. The character
1104     * at index 0 is treated specially.
1105     */

1106    private static final String JavaDoc NUMERIC_FORMAT_CHARS = "MyudhHmsSDFwWkK";
1107
1108    /**
1109     * Return true if the given format character, occuring count
1110     * times, represents a numeric field.
1111     */

1112    private static final boolean isNumeric(char formatChar, int count) {
1113        int i = NUMERIC_FORMAT_CHARS.indexOf(formatChar);
1114        return (i > 0 || (i == 0 && count < 3));
1115    }
1116
1117    /**
1118     * Overrides DateFormat
1119     * @see DateFormat
1120     * @stable ICU 2.0
1121     */

1122    public void parse(String JavaDoc text, Calendar cal, ParsePosition JavaDoc parsePos)
1123    {
1124        int pos = parsePos.getIndex();
1125        int start = pos;
1126        boolean[] ambiguousYear = { false };
1127
1128        // hack, clear parsedTimeZone
1129
parsedTimeZone = null;
1130
1131        // item index for the first numeric field within a countiguous numeric run
1132
int numericFieldStart = -1;
1133        // item length for the first numeric field within a countiguous numeric run
1134
int numericFieldLength = 0;
1135        // start index of numeric text run in the input text
1136
int numericStartPos = 0;
1137
1138        Object JavaDoc[] items = getPatternItems();
1139        int i = 0;
1140        while (i < items.length) {
1141            if (items[i] instanceof PatternItem) {
1142                // Handle pattern field
1143
PatternItem field = (PatternItem)items[i];
1144                if (field.isNumeric) {
1145                    // Handle fields within a run of abutting numeric fields. Take
1146
// the pattern "HHmmss" as an example. We will try to parse
1147
// 2/2/2 characters of the input text, then if that fails,
1148
// 1/2/2. We only adjust the width of the leftmost field; the
1149
// others remain fixed. This allows "123456" => 12:34:56, but
1150
// "12345" => 1:23:45. Likewise, for the pattern "yyyyMMdd" we
1151
// try 4/2/2, 3/2/2, 2/2/2, and finally 1/2/2.
1152
if (numericFieldStart == -1) {
1153                        // check if this field is followed by abutting another numeric field
1154
if ((i + 1) < items.length
1155                                && (items[i + 1] instanceof PatternItem)
1156                                && ((PatternItem)items[i + 1]).isNumeric) {
1157                            // record the first numeric field within a numeric text run
1158
numericFieldStart = i;
1159                            numericFieldLength = field.length;
1160                            numericStartPos = pos;
1161                        }
1162                    }
1163                }
1164                if (numericFieldStart != -1) {
1165                    // Handle a numeric field within abutting numeric fields
1166
int len = field.length;
1167                    if (numericFieldStart == i) {
1168                        len = numericFieldLength;
1169                    }
1170
1171                    // Parse a numeric field
1172
pos = subParse(text, pos, field.type, len,
1173                            true, false, ambiguousYear, cal);
1174
1175                    if (pos < 0) {
1176                        // If the parse fails anywhere in the numeric run, back up to the
1177
// start of the run and use shorter pattern length for the first
1178
// numeric field.
1179
--numericFieldLength;
1180                        if (numericFieldLength == 0) {
1181                            // can not make shorter any more
1182
parsePos.setIndex(start);
1183                            parsePos.setErrorIndex(pos);
1184                            return;
1185                        }
1186                        i = numericFieldStart;
1187                        pos = numericStartPos;
1188                        continue;
1189                    }
1190
1191                } else {
1192                    // Handle a non-numeric field or a non-abutting numeric field
1193
numericFieldStart = -1;
1194
1195                    int s = pos;
1196                    pos = subParse(text, pos, field.type, field.length,
1197                            false, true, ambiguousYear, cal);
1198                    if (pos < 0) {
1199                        parsePos.setIndex(start);
1200                        parsePos.setErrorIndex(s);
1201                        return;
1202                    }
1203                }
1204            } else {
1205                // Handle literal pattern text literal
1206
numericFieldStart = -1;
1207
1208                String JavaDoc patl = (String JavaDoc)items[i];
1209                int plen = patl.length();
1210                int tlen = text.length();
1211                int idx = 0;
1212                while (idx < plen && pos < tlen) {
1213                    char pch = patl.charAt(idx);
1214                    char ich = text.charAt(pos);
1215                    if (UCharacterProperty.isRuleWhiteSpace(pch) && UCharacterProperty.isRuleWhiteSpace(ich)) {
1216                        // White space characters found in both patten and input.
1217
// Skip contiguous white spaces.
1218
while ((idx + 1) < plen &&
1219                                UCharacterProperty.isRuleWhiteSpace(patl.charAt(idx + 1))) {
1220                             ++idx;
1221                        }
1222                        while ((pos + 1) < tlen &&
1223                                UCharacterProperty.isRuleWhiteSpace(text.charAt(pos + 1))) {
1224                             ++pos;
1225                        }
1226                    } else if (pch != ich) {
1227                        break;
1228                    }
1229                    ++idx;
1230                    ++pos;
1231                }
1232                if (idx != plen) {
1233                    // Set the position of mismatch
1234
parsePos.setIndex(start);
1235                    parsePos.setErrorIndex(pos);
1236                    return;
1237                }
1238            }
1239            ++i;
1240        }
1241
1242        // At this point the fields of Calendar have been set. Calendar
1243
// will fill in default values for missing fields when the time
1244
// is computed.
1245

1246        parsePos.setIndex(pos);
1247
1248        // This part is a problem: When we call parsedDate.after, we compute the time.
1249
// Take the date April 3 2004 at 2:30 am. When this is first set up, the year
1250
// will be wrong if we're parsing a 2-digit year pattern. It will be 1904.
1251
// April 3 1904 is a Sunday (unlike 2004) so it is the DST onset day. 2:30 am
1252
// is therefore an "impossible" time, since the time goes from 1:59 to 3:00 am
1253
// on that day. It is therefore parsed out to fields as 3:30 am. Then we
1254
// add 100 years, and get April 3 2004 at 3:30 am. Note that April 3 2004 is
1255
// a Saturday, so it can have a 2:30 am -- and it should. [LIU]
1256
/*
1257          Date parsedDate = cal.getTime();
1258          if( ambiguousYear[0] && !parsedDate.after(getDefaultCenturyStart()) ) {
1259          cal.add(Calendar.YEAR, 100);
1260          parsedDate = cal.getTime();
1261          }
1262        */

1263        // Because of the above condition, save off the fields in case we need to readjust.
1264
// The procedure we use here is not particularly efficient, but there is no other
1265
// way to do this given the API restrictions present in Calendar. We minimize
1266
// inefficiency by only performing this computation when it might apply, that is,
1267
// when the two-digit year is equal to the start year, and thus might fall at the
1268
// front or the back of the default century. This only works because we adjust
1269
// the year correctly to start with in other cases -- see subParse().
1270
try {
1271            if (ambiguousYear[0] || parsedTimeZone != null) {
1272                // We need a copy of the fields, and we need to avoid triggering a call to
1273
// complete(), which will recalculate the fields. Since we can't access
1274
// the fields[] array in Calendar, we clone the entire object. This will
1275
// stop working if Calendar.clone() is ever rewritten to call complete().
1276
Calendar copy = (Calendar)cal.clone();
1277                if (ambiguousYear[0]) { // the two-digit year == the default start year
1278
Date JavaDoc parsedDate = copy.getTime();
1279                    if (parsedDate.before(getDefaultCenturyStart())) {
1280                        // We can't use add here because that does a complete() first.
1281
cal.set(Calendar.YEAR, getDefaultCenturyStartYear() + 100);
1282                    }
1283                }
1284
1285                if (parsedTimeZone != null) {
1286                    TimeZone tz = parsedTimeZone;
1287
1288                    // the calendar represents the parse as gmt time
1289
// we need to turn this into local time, so we add the raw offset
1290
// then we ask the timezone to handle this local time
1291
int[] offsets = new int[2];
1292                    tz.getOffset(copy.getTimeInMillis()+tz.getRawOffset(), true, offsets);
1293
1294                    cal.set(Calendar.ZONE_OFFSET, offsets[0]);
1295                    cal.set(Calendar.DST_OFFSET, offsets[1]);
1296                    cal.setTimeZone(tz);
1297                }
1298            }
1299        }
1300        // An IllegalArgumentException will be thrown by Calendar.getTime()
1301
// if any fields are out of range, e.g., MONTH == 17.
1302
catch (IllegalArgumentException JavaDoc e) {
1303            parsePos.setErrorIndex(pos);
1304            parsePos.setIndex(start);
1305        }
1306    }
1307
1308    /**
1309     * Attempt to match the text at a given position against an array of
1310     * strings. Since multiple strings in the array may match (for
1311     * example, if the array contains "a", "ab", and "abc", all will match
1312     * the input string "abcd") the longest match is returned. As a side
1313     * effect, the given field of <code>cal</code> is set to the index
1314     * of the best match, if there is one.
1315     * @param text the time text being parsed.
1316     * @param start where to start parsing.
1317     * @param field the date field being parsed.
1318     * @param data the string array to parsed.
1319     * @return the new start position if matching succeeded; a negative
1320     * number indicating matching failure, otherwise. As a side effect,
1321     * sets the <code>cal</code> field <code>field</code> to the index
1322     * of the best match, if matching succeeded.
1323     * @stable ICU 2.0
1324     */

1325    protected int matchString(String JavaDoc text, int start, int field, String JavaDoc[] data, Calendar cal)
1326    {
1327        int i = 0;
1328        int count = data.length;
1329
1330        if (field == Calendar.DAY_OF_WEEK) i = 1;
1331
1332        // There may be multiple strings in the data[] array which begin with
1333
// the same prefix (e.g., Cerven and Cervenec (June and July) in Czech).
1334
// We keep track of the longest match, and return that. Note that this
1335
// unfortunately requires us to test all array elements.
1336
int bestMatchLength = 0, bestMatch = -1;
1337        for (; i<count; ++i)
1338            {
1339                int length = data[i].length();
1340                // Always compare if we have no match yet; otherwise only compare
1341
// against potentially better matches (longer strings).
1342
if (length > bestMatchLength &&
1343                    text.regionMatches(true, start, data[i], 0, length))
1344                    {
1345                        bestMatch = i;
1346                        bestMatchLength = length;
1347                    }
1348            }
1349        if (bestMatch >= 0)
1350            {
1351                cal.set(field, bestMatch);
1352                return start + bestMatchLength;
1353            }
1354        return -start;
1355    }
1356
1357    /**
1358     * Attempt to match the text at a given position against an array of quarter
1359     * strings. Since multiple strings in the array may match (for
1360     * example, if the array contains "a", "ab", and "abc", all will match
1361     * the input string "abcd") the longest match is returned. As a side
1362     * effect, the given field of <code>cal</code> is set to the index
1363     * of the best match, if there is one.
1364     * @param text the time text being parsed.
1365     * @param start where to start parsing.
1366     * @param field the date field being parsed.
1367     * @param data the string array to parsed.
1368     * @return the new start position if matching succeeded; a negative
1369     * number indicating matching failure, otherwise. As a side effect,
1370     * sets the <code>cal</code> field <code>field</code> to the index
1371     * of the best match, if matching succeeded.
1372     * @stable ICU 2.0
1373     */

1374    protected int matchQuarterString(String JavaDoc text, int start, int field, String JavaDoc[] data, Calendar cal)
1375    {
1376        int i = 0;
1377        int count = data.length;
1378
1379        // There may be multiple strings in the data[] array which begin with
1380
// the same prefix (e.g., Cerven and Cervenec (June and July) in Czech).
1381
// We keep track of the longest match, and return that. Note that this
1382
// unfortunately requires us to test all array elements.
1383
int bestMatchLength = 0, bestMatch = -1;
1384        for (; i<count; ++i) {
1385            int length = data[i].length();
1386            // Always compare if we have no match yet; otherwise only compare
1387
// against potentially better matches (longer strings).
1388
if (length > bestMatchLength &&
1389                text.regionMatches(true, start, data[i], 0, length)) {
1390                bestMatch = i;
1391                bestMatchLength = length;
1392            }
1393        }
1394        
1395        if (bestMatch >= 0) {
1396            cal.set(field, bestMatch * 3);
1397            return start + bestMatchLength;
1398        }
1399        
1400        return -start;
1401    }
1402    
1403    /**
1404     * find time zone 'text' matched zoneStrings and set cal
1405     */

1406    private int subParseZoneString(String JavaDoc text, int start, Calendar cal) {
1407        // At this point, check for named time zones by looking through
1408
// the locale data from the DateFormatZoneData strings.
1409
// Want to be able to parse both short and long forms.
1410

1411        // optimize for calendar's current time zone
1412
TimeZone tz = null;
1413        String JavaDoc zid = null, value = null;
1414        int type = -1;
1415
1416        DateFormatSymbols.ZoneItem item = formatData.findZoneIDTypeValue(text, start);
1417        if (item != null) {
1418            zid = item.zid;
1419            value = item.value;
1420            type = item.type;
1421        }
1422
1423        if (zid != null) {
1424            tz = TimeZone.getTimeZone(zid);
1425        }
1426        
1427        if (tz != null) { // Matched any ?
1428
// always set zone offset, needed to get correct hour in wall time
1429
// when checking daylight savings
1430
cal.set(Calendar.ZONE_OFFSET, tz.getRawOffset());
1431            if (type == DateFormatSymbols.TIMEZONE_SHORT_STANDARD
1432                    || type == DateFormatSymbols.TIMEZONE_LONG_STANDARD) {
1433                // standard time
1434
cal.set(Calendar.DST_OFFSET, 0);
1435                tz = null;
1436            } else if (type == DateFormatSymbols.TIMEZONE_SHORT_DAYLIGHT
1437                    || type == DateFormatSymbols.TIMEZONE_LONG_DAYLIGHT) {
1438                // daylight time
1439
// use the correct DST SAVINGS for the zone.
1440
// cal.set(UCAL_DST_OFFSET, tz->getDSTSavings());
1441
cal.set(Calendar.DST_OFFSET, millisPerHour);
1442                tz = null;
1443            } else {
1444                // either standard or daylight
1445
// need to finish getting the date, then compute dst offset as
1446
// appropriate
1447
parsedTimeZone = tz;
1448            }
1449            if(value != null) {
1450                return start + value.length();
1451            }
1452        }
1453        // complete failure
1454
return 0;
1455    }
1456
1457    /**
1458     * Protected method that converts one field of the input string into a
1459     * numeric field value in <code>cal</code>. Returns -start (for
1460     * ParsePosition) if failed. Subclasses may override this method to
1461     * modify or add parsing capabilities.
1462     * @param text the time text to be parsed.
1463     * @param start where to start parsing.
1464     * @param ch the pattern character for the date field text to be parsed.
1465     * @param count the count of a pattern character.
1466     * @param obeyCount if true, then the next field directly abuts this one,
1467     * and we should use the count to know when to stop parsing.
1468     * @param ambiguousYear return parameter; upon return, if ambiguousYear[0]
1469     * is true, then a two-digit year was parsed and may need to be readjusted.
1470     * @return the new start position if matching succeeded; a negative
1471     * number indicating matching failure, otherwise. As a side effect,
1472     * set the appropriate field of <code>cal</code> with the parsed
1473     * value.
1474     * @stable ICU 2.0
1475     */

1476    protected int subParse(String JavaDoc text, int start, char ch, int count,
1477                           boolean obeyCount, boolean allowNegative,
1478                           boolean[] ambiguousYear, Calendar cal)
1479    {
1480        Number JavaDoc number = null;
1481        int value = 0;
1482        int i;
1483        ParsePosition JavaDoc pos = new ParsePosition JavaDoc(0);
1484        //int patternCharIndex = DateFormatSymbols.patternChars.indexOf(ch);c
1485
int patternCharIndex = -1;
1486        if ('A' <= ch && ch <= 'z') {
1487            patternCharIndex = PATTERN_CHAR_TO_INDEX[(int)ch - PATTERN_CHAR_BASE];
1488        }
1489
1490        if (patternCharIndex == -1) {
1491            return -start;
1492        }
1493
1494        int field = PATTERN_INDEX_TO_CALENDAR_FIELD[patternCharIndex];
1495
1496        // If there are any spaces here, skip over them. If we hit the end
1497
// of the string, then fail.
1498
for (;;) {
1499            if (start >= text.length()) {
1500                return -start;
1501            }
1502            int c = UTF16.charAt(text, start);
1503            if (!UCharacter.isUWhiteSpace(c)) {
1504                break;
1505            }
1506            start += UTF16.getCharCount(c);
1507        }
1508        pos.setIndex(start);
1509
1510        // We handle a few special cases here where we need to parse
1511
// a number value. We handle further, more generic cases below. We need
1512
// to handle some of them here because some fields require extra processing on
1513
// the parsed value.
1514
if (patternCharIndex == 4 /*HOUR_OF_DAY1_FIELD*/ ||
1515            patternCharIndex == 15 /*HOUR1_FIELD*/ ||
1516            (patternCharIndex == 2 /*MONTH_FIELD*/ && count <= 2) ||
1517            patternCharIndex == 1 ||
1518            patternCharIndex == 8)
1519            {
1520                // It would be good to unify this with the obeyCount logic below,
1521
// but that's going to be difficult.
1522
if (obeyCount)
1523                    {
1524                        if ((start+count) > text.length()) return -start;
1525                        number = parseInt(text.substring(0, start+count), pos, allowNegative);
1526                    }
1527                else number = parseInt(text, pos, allowNegative);
1528                if (number == null)
1529                    return -start;
1530                value = number.intValue();
1531            }
1532
1533        switch (patternCharIndex)
1534            {
1535            case 0: // 'G' - ERA
1536
if (count == 4) {
1537                    return matchString(text, start, Calendar.ERA, formatData.eraNames, cal);
1538                } else {
1539                    return matchString(text, start, Calendar.ERA, formatData.eras, cal);
1540                }
1541            case 1: // 'y' - YEAR
1542
// If there are 3 or more YEAR pattern characters, this indicates
1543
// that the year value is to be treated literally, without any
1544
// two-digit year adjustments (e.g., from "01" to 2001). Otherwise
1545
// we made adjustments to place the 2-digit year in the proper
1546
// century, for parsed strings from "00" to "99". Any other string
1547
// is treated literally: "2250", "-1", "1", "002".
1548
/* 'yy' is the only special case, 'y' is interpreted as number. [Richard/GCL]*/
1549                if (count == 2 && (pos.getIndex() - start) == 2
1550                    && Character.isDigit(text.charAt(start))
1551                    && Character.isDigit(text.charAt(start+1)))
1552                    {
1553                        // Assume for example that the defaultCenturyStart is 6/18/1903.
1554
// This means that two-digit years will be forced into the range
1555
// 6/18/1903 to 6/17/2003. As a result, years 00, 01, and 02
1556
// correspond to 2000, 2001, and 2002. Years 04, 05, etc. correspond
1557
// to 1904, 1905, etc. If the year is 03, then it is 2003 if the
1558
// other fields specify a date before 6/18, or 1903 if they specify a
1559
// date afterwards. As a result, 03 is an ambiguous year. All other
1560
// two-digit years are unambiguous.
1561
int ambiguousTwoDigitYear = getDefaultCenturyStartYear() % 100;
1562                        ambiguousYear[0] = value == ambiguousTwoDigitYear;
1563                        value += (getDefaultCenturyStartYear()/100)*100 +
1564                            (value < ambiguousTwoDigitYear ? 100 : 0);
1565                    }
1566                cal.set(Calendar.YEAR, value);
1567                return pos.getIndex();
1568            case 2: // 'M' - MONTH
1569
if (count <= 2) // i.e., M or MM.
1570
{
1571                        // Don't want to parse the month if it is a string
1572
// while pattern uses numeric style: M or MM.
1573
// [We computed 'value' above.]
1574
cal.set(Calendar.MONTH, value - 1);
1575                        return pos.getIndex();
1576                    }
1577                else
1578                    {
1579                        // count >= 3 // i.e., MMM or MMMM
1580
// Want to be able to parse both short and long forms.
1581
// Try count == 4 first:
1582
int newStart = matchString(text, start, Calendar.MONTH,
1583                                                   formatData.months, cal);
1584                        if (newStart > 0) {
1585                            return newStart;
1586                        } else { // count == 4 failed, now try count == 3
1587
return matchString(text, start, Calendar.MONTH,
1588                                               formatData.shortMonths, cal);
1589                        }
1590                    }
1591            case 26: // 'L' - STAND_ALONE_MONTH
1592
if (count <= 2) // i.e., M or MM.
1593
{
1594                        // Don't want to parse the month if it is a string
1595
// while pattern uses numeric style: M or MM.
1596
// [We computed 'value' above.]
1597
cal.set(Calendar.MONTH, value - 1);
1598                        return pos.getIndex();
1599                    }
1600                else
1601                    {
1602                        // count >= 3 // i.e., MMM or MMMM
1603
// Want to be able to parse both short and long forms.
1604
// Try count == 4 first:
1605
int newStart = matchString(text, start, Calendar.MONTH,
1606                                                   formatData.standaloneMonths, cal);
1607                        if (newStart > 0) {
1608                            return newStart;
1609                        } else { // count == 4 failed, now try count == 3
1610
return matchString(text, start, Calendar.MONTH,
1611                                               formatData.standaloneShortMonths, cal);
1612                        }
1613                    }
1614            case 4: // 'k' - HOUR_OF_DAY (1..24)
1615
// [We computed 'value' above.]
1616
if (value == cal.getMaximum(Calendar.HOUR_OF_DAY)+1) value = 0;
1617                cal.set(Calendar.HOUR_OF_DAY, value);
1618                return pos.getIndex();
1619            case 8: // 'S' - FRACTIONAL_SECOND
1620
// Fractional seconds left-justify
1621
i = pos.getIndex() - start;
1622                if (i < 3) {
1623                    while (i < 3) {
1624                        value *= 10;
1625                        i++;
1626                    }
1627                } else {
1628                    int a = 1;
1629                    while (i > 3) {
1630                        a *= 10;
1631                        i--;
1632                    }
1633                    value = (value + (a>>1)) / a;
1634                }
1635                cal.set(Calendar.MILLISECOND, value);
1636                return pos.getIndex();
1637            case 9: { // 'E' - DAY_OF_WEEK
1638
// Want to be able to parse both short and long forms.
1639
// Try count == 4 (EEEE) first:
1640
int newStart = matchString(text, start, Calendar.DAY_OF_WEEK,
1641                                           formatData.weekdays, cal);
1642                if (newStart > 0) {
1643                    return newStart;
1644                } else { // EEEE failed, now try EEE
1645
return matchString(text, start, Calendar.DAY_OF_WEEK,
1646                                       formatData.shortWeekdays, cal);
1647                }
1648            }
1649            case 25: { // 'c' - STAND_ALONE_DAY_OF_WEEK
1650
// Want to be able to parse both short and long forms.
1651
// Try count == 4 (cccc) first:
1652
int newStart = matchString(text, start, Calendar.DAY_OF_WEEK,
1653                                           formatData.standaloneWeekdays, cal);
1654                if (newStart > 0) {
1655                    return newStart;
1656                } else { // cccc failed, now try ccc
1657
return matchString(text, start, Calendar.DAY_OF_WEEK,
1658                                       formatData.standaloneShortWeekdays, cal);
1659                }
1660            }
1661            case 14: // 'a' - AM_PM
1662
return matchString(text, start, Calendar.AM_PM, formatData.ampms, cal);
1663            case 15: // 'h' - HOUR (1..12)
1664
// [We computed 'value' above.]
1665
if (value == cal.getLeastMaximum(Calendar.HOUR)+1) value = 0;
1666                cal.set(Calendar.HOUR, value);
1667                return pos.getIndex();
1668            case 17: // 'z' - ZONE_OFFSET
1669
case 23: // 'Z' - TIMEZONE_RFC
1670
case 24: // 'v' - TIMEZONE_GENERIC
1671
// First try to parse generic forms such as GMT-07:00. Do this first
1672
// in case localized DateFormatZoneData contains the string "GMT"
1673
// for a zone; in that case, we don't want to match the first three
1674
// characters of GMT+/-HH:MM etc.
1675
{
1676                    int sign = 0;
1677                    int offset;
1678
1679                    // For time zones that have no known names, look for strings
1680
// of the form:
1681
// GMT[+-]hours:minutes or
1682
// GMT[+-]hhmm or
1683
// GMT.
1684
if ((text.length() - start) >= GMT.length() &&
1685                        text.regionMatches(true, start, GMT, 0, GMT.length()))
1686                        {
1687                            cal.set(Calendar.DST_OFFSET, 0);
1688
1689                            pos.setIndex(start + GMT.length());
1690
1691                            try { // try-catch for "GMT" only time zone string
1692
switch (text.charAt(pos.getIndex())) {
1693                                case '+':
1694                                    sign = 1;
1695                                    break;
1696                                case '-':
1697                                    sign = -1;
1698                                    break;
1699                                }
1700                            } catch(StringIndexOutOfBoundsException JavaDoc e) {
1701                            }
1702                            if (sign == 0) {
1703                                cal.set(Calendar.ZONE_OFFSET, 0 );
1704                                return pos.getIndex();
1705                            }
1706
1707                            // Look for hours:minutes or hhmm.
1708
pos.setIndex(pos.getIndex() + 1);
1709                            int st = pos.getIndex();
1710                            Number JavaDoc tzNumber = numberFormat.parse(text, pos);
1711                            if( tzNumber == null) {
1712                                return -start;
1713                            }
1714                            if( pos.getIndex() < text.length() &&
1715                                text.charAt(pos.getIndex()) == ':' ) {
1716
1717                                // This is the hours:minutes case
1718
offset = tzNumber.intValue() * 60;
1719                                pos.setIndex(pos.getIndex() + 1);
1720                                tzNumber = numberFormat.parse(text, pos);
1721                                if( tzNumber == null) {
1722                                    return -start;
1723                                }
1724                                offset += tzNumber.intValue();
1725                            }
1726                            else {
1727                                // This is the hhmm case.
1728
offset = tzNumber.intValue();
1729                                // Assume "-23".."+23" refers to hours.
1730
if( offset < 24 && (pos.getIndex() - st) <= 2)
1731                                    offset *= 60;
1732                                else
1733                                    // todo: this looks questionable, should have more error checking
1734
offset = offset % 100 + offset / 100 * 60;
1735                            }
1736
1737                            // Fall through for final processing below of 'offset' and 'sign'.
1738
}
1739                    else {
1740                        // At this point, check for named time zones by looking through
1741
// the locale data from the DateFormatZoneData strings.
1742
// Want to be able to parse both short and long forms.
1743
i = subParseZoneString(text, start, cal);
1744                        if (i != 0)
1745                            return i;
1746
1747                        // As a last resort, look for numeric timezones of the form
1748
// [+-]hhmm as specified by RFC 822. This code is actually
1749
// a little more permissive than RFC 822. It will try to do
1750
// its best with numbers that aren't strictly 4 digits long.
1751
DecimalFormat fmt = new DecimalFormat("+####;-####");
1752                        fmt.setParseIntegerOnly(true);
1753                        Number JavaDoc tzNumber = fmt.parse( text, pos );
1754                        if( tzNumber == null) {
1755                            return -start; // Wasn't actually a number.
1756
}
1757                        offset = tzNumber.intValue();
1758                        sign = 1;
1759                        if( offset < 0 ) {
1760                            sign = -1;
1761                            offset = -offset;
1762                        }
1763                        // Assume "-23".."+23" refers to hours. Length includes sign.
1764
if( offset < 24 && (pos.getIndex() - start) <= 3)
1765                            offset = offset * 60;
1766                        else
1767                            offset = offset % 100 + offset / 100 * 60;
1768
1769                        // Fall through for final processing below of 'offset' and 'sign'.
1770
}
1771
1772                    // Do the final processing for both of the above cases. We only
1773
// arrive here if the form GMT+/-... or an RFC 822 form was seen.
1774

1775                    // assert (sign != 0) : sign; // enable when guaranteed JDK >= 1.4
1776
offset *= millisPerMinute * sign;
1777
1778                    if (cal.getTimeZone().useDaylightTime())
1779                        {
1780                            cal.set(Calendar.DST_OFFSET, millisPerHour);
1781                            offset -= millisPerHour;
1782                        }
1783                    cal.set(Calendar.ZONE_OFFSET, offset);
1784
1785                    return pos.getIndex();
1786                }
1787                
1788            case 27: // 'Q' - QUARTER
1789
if (count <= 2) // i.e., Q or QQ.
1790
{
1791                    // Don't want to parse the quarter if it is a string
1792
// while pattern uses numeric style: Q or QQ.
1793
// [We computed 'value' above.]
1794
cal.set(Calendar.MONTH, (value - 1) * 3);
1795                    return pos.getIndex();
1796                }
1797            else
1798                {
1799                    // count >= 3 // i.e., QQQ or QQQQ
1800
// Want to be able to parse both short and long forms.
1801
// Try count == 4 first:
1802
int newStart = matchQuarterString(text, start, Calendar.MONTH,
1803                                               formatData.quarters, cal);
1804                    if (newStart > 0) {
1805                        return newStart;
1806                    } else { // count == 4 failed, now try count == 3
1807
return matchQuarterString(text, start, Calendar.MONTH,
1808                                           formatData.shortQuarters, cal);
1809                    }
1810                }
1811                
1812            case 28: // 'q' - STANDALONE QUARTER
1813
if (count <= 2) // i.e., q or qq.
1814
{
1815                    // Don't want to parse the quarter if it is a string
1816
// while pattern uses numeric style: q or qq.
1817
// [We computed 'value' above.]
1818
cal.set(Calendar.MONTH, (value - 1) * 3);
1819                    return pos.getIndex();
1820                }
1821            else
1822                {
1823                    // count >= 3 // i.e., qqq or qqqq
1824
// Want to be able to parse both short and long forms.
1825
// Try count == 4 first:
1826
int newStart = matchQuarterString(text, start, Calendar.MONTH,
1827                                               formatData.standaloneQuarters, cal);
1828                    if (newStart > 0) {
1829                        return newStart;
1830                    } else { // count == 4 failed, now try count == 3
1831
return matchQuarterString(text, start, Calendar.MONTH,
1832                                           formatData.standaloneShortQuarters, cal);
1833                    }
1834                }
1835
1836            default:
1837                // case 3: // 'd' - DATE
1838
// case 5: // 'H' - HOUR_OF_DAY (0..23)
1839
// case 6: // 'm' - MINUTE
1840
// case 7: // 's' - SECOND
1841
// case 10: // 'D' - DAY_OF_YEAR
1842
// case 11: // 'F' - DAY_OF_WEEK_IN_MONTH
1843
// case 12: // 'w' - WEEK_OF_YEAR
1844
// case 13: // 'W' - WEEK_OF_MONTH
1845
// case 16: // 'K' - HOUR (0..11)
1846
// case 18: // 'Y' - YEAR_WOY
1847
// case 19: // 'e' - DOW_LOCAL
1848
// case 20: // 'u' - EXTENDED_YEAR
1849
// case 21: // 'g' - JULIAN_DAY
1850
// case 22: // 'A' - MILLISECONDS_IN_DAY
1851

1852                // Handle "generic" fields
1853
if (obeyCount)
1854                    {
1855                        if ((start+count) > text.length()) return -start;
1856                        number = parseInt(text.substring(0, start+count), pos, allowNegative);
1857                    }
1858                else number = parseInt(text, pos, allowNegative);
1859                if (number != null) {
1860                    cal.set(field, number.intValue());
1861                    return pos.getIndex();
1862                }
1863                return -start;
1864            }
1865    }
1866
1867    /**
1868     * Parse an integer using fNumberFormat. This method is semantically
1869     * const, but actually may modify fNumberFormat.
1870     */

1871    private Number JavaDoc parseInt(String JavaDoc text,
1872                            ParsePosition JavaDoc pos,
1873                            boolean allowNegative) {
1874        Number JavaDoc number;
1875        if (allowNegative) {
1876            number = numberFormat.parse(text, pos);
1877        } else {
1878            // Invalidate negative numbers
1879
if (numberFormat instanceof DecimalFormat) {
1880                String JavaDoc oldPrefix = ((DecimalFormat)numberFormat).getNegativePrefix();
1881                ((DecimalFormat)numberFormat).setNegativePrefix(SUPPRESS_NEGATIVE_PREFIX);
1882                number = numberFormat.parse(text, pos);
1883                ((DecimalFormat)numberFormat).setNegativePrefix(oldPrefix);
1884            } else {
1885                boolean dateNumberFormat = (numberFormat instanceof DateNumberFormat);
1886                if (dateNumberFormat) {
1887                    ((DateNumberFormat)numberFormat).setParsePositiveOnly(true);
1888                }
1889                number = numberFormat.parse(text, pos);
1890                if (dateNumberFormat) {
1891                    ((DateNumberFormat)numberFormat).setParsePositiveOnly(false);
1892                }
1893            }
1894        }
1895        return number;
1896    }
1897
1898    /**
1899     * Translate a pattern, mapping each character in the from string to the
1900     * corresponding character in the to string.
1901     */

1902    private String JavaDoc translatePattern(String JavaDoc pattern, String JavaDoc from, String JavaDoc to) {
1903        StringBuffer JavaDoc result = new StringBuffer JavaDoc();
1904        boolean inQuote = false;
1905        for (int i = 0; i < pattern.length(); ++i) {
1906            char c = pattern.charAt(i);
1907            if (inQuote) {
1908                if (c == '\'')
1909                    inQuote = false;
1910            }
1911            else {
1912                if (c == '\'')
1913                    inQuote = true;
1914                else if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) {
1915                    int ci = from.indexOf(c);
1916                    if (ci != -1) {
1917                        c = to.charAt(ci);
1918                    }
1919                    // do not worry on translatepattern if the character is not listed
1920
// we do the validity check elsewhere
1921
}
1922            }
1923            result.append(c);
1924        }
1925        if (inQuote)
1926            throw new IllegalArgumentException JavaDoc("Unfinished quote in pattern");
1927        return result.toString();
1928    }
1929
1930    /**
1931     * Return a pattern string describing this date format.
1932     * @stable ICU 2.0
1933     */

1934    public String JavaDoc toPattern() {
1935        return pattern;
1936    }
1937
1938    /**
1939     * Return a localized pattern string describing this date format.
1940     * @stable ICU 2.0
1941     */

1942    public String JavaDoc toLocalizedPattern() {
1943        return translatePattern(pattern,
1944                                DateFormatSymbols.patternChars,
1945                                formatData.localPatternChars);
1946    }
1947
1948    /**
1949     * Apply the given unlocalized pattern string to this date format.
1950     * @stable ICU 2.0
1951     */

1952    public void applyPattern(String JavaDoc pattern)
1953    {
1954        this.pattern = pattern;
1955        setLocale(null, null);
1956        // reset parsed pattern items
1957
patternItems = null;
1958    }
1959
1960    /**
1961     * Apply the given localized pattern string to this date format.
1962     * @stable ICU 2.0
1963     */

1964    public void applyLocalizedPattern(String JavaDoc pattern) {
1965        this.pattern = translatePattern(pattern,
1966                                        formatData.localPatternChars,
1967                                        DateFormatSymbols.patternChars);
1968        setLocale(null, null);
1969    }
1970
1971    /**
1972     * Gets the date/time formatting data.
1973     * @return a copy of the date-time formatting data associated
1974     * with this date-time formatter.
1975     * @stable ICU 2.0
1976     */

1977    public DateFormatSymbols getDateFormatSymbols()
1978    {
1979        return (DateFormatSymbols)formatData.clone();
1980    }
1981
1982    /**
1983     * Allows you to set the date/time formatting data.
1984     * @param newFormatSymbols the new symbols
1985     * @stable ICU 2.0
1986     */

1987    public void setDateFormatSymbols(DateFormatSymbols newFormatSymbols)
1988    {
1989        this.formatData = (DateFormatSymbols)newFormatSymbols.clone();
1990    }
1991
1992    /**
1993     * Method for subclasses to access the DateFormatSymbols.
1994     * @stable ICU 2.0
1995     */

1996    protected DateFormatSymbols getSymbols() {
1997        return formatData;
1998    }
1999
2000    /**
2001     * Overrides Cloneable
2002     * @stable ICU 2.0
2003     */

2004    public Object JavaDoc clone() {
2005        SimpleDateFormat other = (SimpleDateFormat) super.clone();
2006        other.formatData = (DateFormatSymbols) formatData.clone();
2007        return other;
2008    }
2009
2010    /**
2011     * Override hashCode.
2012     * Generates the hash code for the SimpleDateFormat object
2013     * @stable ICU 2.0
2014     */

2015    public int hashCode()
2016    {
2017        return pattern.hashCode();
2018        // just enough fields for a reasonable distribution
2019
}
2020
2021    /**
2022     * Override equals.
2023     * @stable ICU 2.0
2024     */

2025    public boolean equals(Object JavaDoc obj)
2026    {
2027        if (!super.equals(obj)) return false; // super does class check
2028
SimpleDateFormat that = (SimpleDateFormat) obj;
2029        return (pattern.equals(that.pattern)
2030                && formatData.equals(that.formatData));
2031    }
2032
2033    /**
2034     * Override writeObject.
2035     */

2036    private void writeObject(ObjectOutputStream JavaDoc stream) throws IOException JavaDoc{
2037        if (defaultCenturyStart == null) {
2038            // if defaultCenturyStart is not yet initialized,
2039
// calculate and set value before serialization.
2040
initializeDefaultCenturyStart(defaultCenturyBase);
2041        }
2042        stream.defaultWriteObject();
2043    }
2044    
2045    /**
2046     * Override readObject.
2047     */

2048    private void readObject(ObjectInputStream JavaDoc stream)
2049        throws IOException JavaDoc, ClassNotFoundException JavaDoc {
2050        stream.defaultReadObject();
2051        ///CLOVER:OFF
2052
// don't have old serial data to test with
2053
if (serialVersionOnStream < 1) {
2054            // didn't have defaultCenturyStart field
2055
defaultCenturyBase = System.currentTimeMillis();
2056        }
2057        ///CLOVER:ON
2058
else {
2059            // fill in dependent transient field
2060
parseAmbiguousDatesAsAfter(defaultCenturyStart);
2061        }
2062        serialVersionOnStream = currentSerialVersion;
2063        locale = getLocale(ULocale.VALID_LOCALE);
2064
2065        initLocalZeroPaddingNumberFormat();
2066    }
2067}
2068
Popular Tags