]> gitweb.fperrin.net Git - Dictionary.git/blob - jars/icu4j-4_2_1-src/src/com/ibm/icu/text/SimpleDateFormat.java
icu4jsrc
[Dictionary.git] / jars / icu4j-4_2_1-src / src / com / ibm / icu / text / SimpleDateFormat.java
1 //##header\r
2 /*\r
3  *******************************************************************************\r
4  * Copyright (C) 1996-2009, International Business Machines Corporation and    *\r
5  * others. All Rights Reserved.                                                *\r
6  *******************************************************************************\r
7  */\r
8 \r
9 package com.ibm.icu.text;\r
10 \r
11 import java.io.IOException;\r
12 import java.io.ObjectInputStream;\r
13 import java.io.ObjectOutputStream;\r
14 import java.lang.ref.WeakReference;\r
15 import java.lang.Character;\r
16 import java.text.FieldPosition;\r
17 import java.text.ParsePosition;\r
18 import java.util.ArrayList;\r
19 import java.util.Date;\r
20 import java.util.HashMap;\r
21 import java.util.List;\r
22 import java.util.Locale;\r
23 import java.util.MissingResourceException;\r
24 //#if defined(FOUNDATION10) || defined(J2SE13)\r
25 //#else\r
26 import java.text.AttributedCharacterIterator;\r
27 import java.text.AttributedString;\r
28 import java.text.Format;\r
29 import java.util.LinkedList;\r
30 //#endif\r
31 \r
32 import com.ibm.icu.impl.CalendarData;\r
33 import com.ibm.icu.impl.DateNumberFormat;\r
34 import com.ibm.icu.impl.ICUCache;\r
35 import com.ibm.icu.impl.SimpleCache;\r
36 import com.ibm.icu.impl.UCharacterProperty;\r
37 import com.ibm.icu.impl.ZoneMeta;\r
38 import com.ibm.icu.impl.ZoneStringFormat.ZoneStringInfo;\r
39 import com.ibm.icu.lang.UCharacter;\r
40 import com.ibm.icu.util.BasicTimeZone;\r
41 import com.ibm.icu.util.Calendar;\r
42 import com.ibm.icu.util.GregorianCalendar;\r
43 import com.ibm.icu.util.TimeZone;\r
44 import com.ibm.icu.util.TimeZoneTransition;\r
45 import com.ibm.icu.util.ULocale;\r
46 \r
47 \r
48 /**\r
49  * <code>SimpleDateFormat</code> is a concrete class for formatting and\r
50  * parsing dates in a locale-sensitive manner. It allows for formatting\r
51  * (date -> text), parsing (text -> date), and normalization.\r
52  *\r
53  * <p>\r
54  * <code>SimpleDateFormat</code> allows you to start by choosing\r
55  * any user-defined patterns for date-time formatting. However, you\r
56  * are encouraged to create a date-time formatter with either\r
57  * <code>getTimeInstance</code>, <code>getDateInstance</code>, or\r
58  * <code>getDateTimeInstance</code> in <code>DateFormat</code>. Each\r
59  * of these class methods can return a date/time formatter initialized\r
60  * with a default format pattern. You may modify the format pattern\r
61  * using the <code>applyPattern</code> methods as desired.\r
62  * For more information on using these methods, see\r
63  * {@link DateFormat}.\r
64  *\r
65  * <p>\r
66  * <strong>Time Format Syntax:</strong>\r
67  * <p>\r
68  * To specify the time format use a <em>time pattern</em> string.\r
69  * In this pattern, all ASCII letters are reserved as pattern letters,\r
70  * which are defined as the following:\r
71  * <blockquote>\r
72  * <pre>\r
73  * Symbol   Meaning                 Presentation        Example\r
74  * ------   -------                 ------------        -------\r
75  * G        era designator          (Text)              AD\r
76  * y&#x2020;       year                    (Number)            1996\r
77  * Y*       year (week of year)     (Number)            1997\r
78  * u*       extended year           (Number)            4601\r
79  * M        month in year           (Text & Number)     July & 07\r
80  * d        day in month            (Number)            10\r
81  * h        hour in am/pm (1~12)    (Number)            12\r
82  * H        hour in day (0~23)      (Number)            0\r
83  * m        minute in hour          (Number)            30\r
84  * s        second in minute        (Number)            55\r
85  * S        fractional second       (Number)            978\r
86  * E        day of week             (Text)              Tuesday\r
87  * e*       day of week (local 1~7) (Text & Number)     Tuesday & 2\r
88  * D        day in year             (Number)            189\r
89  * F        day of week in month    (Number)            2 (2nd Wed in July)\r
90  * w        week in year            (Number)            27\r
91  * W        week in month           (Number)            2\r
92  * a        am/pm marker            (Text)              PM\r
93  * k        hour in day (1~24)      (Number)            24\r
94  * K        hour in am/pm (0~11)    (Number)            0\r
95  * z        time zone               (Text)              Pacific Standard Time\r
96  * Z        time zone (RFC 822)     (Number)            -0800\r
97  * v        time zone (generic)     (Text)              Pacific Time\r
98  * V        time zone (location)    (Text)              United States (Los Angeles)\r
99  * g*       Julian day              (Number)            2451334\r
100  * A*       milliseconds in day     (Number)            69540000\r
101  * Q*       quarter in year         (Text & Number)     Q1 & 01\r
102  * c*       stand alone day of week (Text & Number)     Tuesday & 2\r
103  * L*       stand alone month       (Text & Number)     July & 07\r
104  * q*       stand alone quarter     (Text & Number)     Q1 & 01\r
105  * '        escape for text         (Delimiter)         'Date='\r
106  * ''       single quote            (Literal)           'o''clock'\r
107  * </pre>\r
108  * </blockquote>\r
109  * <tt><b>*</b></tt> These items are not supported by Java's SimpleDateFormat.<br>\r
110  * <tt><b>&#x2020;</b></tt> ICU interprets a single 'y' differently than Java.</p>\r
111  * <p>\r
112  * The count of pattern letters determine the format.\r
113  * <p>\r
114  * <strong>(Text)</strong>: 4 or more pattern letters--use full form,\r
115  * &lt; 4--use short or abbreviated form if one exists.\r
116  * <p>\r
117  * <strong>(Number)</strong>: the minimum number of digits. Shorter\r
118  * numbers are zero-padded to this amount. Year is handled specially;\r
119  * that is, if the count of 'y' is 2, the Year will be truncated to 2 digits.\r
120  * (e.g., if "yyyy" produces "1997", "yy" produces "97".)\r
121  * Unlike other fields, fractional seconds are padded on the right with zero.\r
122  * <p>\r
123  * <strong>(Text & Number)</strong>: 3 or over, use text, otherwise use number.\r
124  * <p>\r
125  * Any characters in the pattern that are not in the ranges of ['a'..'z']\r
126  * and ['A'..'Z'] will be treated as quoted text. For instance, characters\r
127  * like ':', '.', ' ', '#' and '@' will appear in the resulting time text\r
128  * even they are not embraced within single quotes.\r
129  * <p>\r
130  * A pattern containing any invalid pattern letter will result in a thrown\r
131  * exception during formatting or parsing.\r
132  *\r
133  * <p>\r
134  * <strong>Examples Using the US Locale:</strong>\r
135  * <blockquote>\r
136  * <pre>\r
137  * Format Pattern                         Result\r
138  * --------------                         -------\r
139  * "yyyy.MM.dd G 'at' HH:mm:ss vvvv" ->>  1996.07.10 AD at 15:08:56 Pacific Time\r
140  * "EEE, MMM d, ''yy"                ->>  Wed, July 10, '96\r
141  * "h:mm a"                          ->>  12:08 PM\r
142  * "hh 'o''clock' a, zzzz"           ->>  12 o'clock PM, Pacific Daylight Time\r
143  * "K:mm a, vvv"                     ->>  0:00 PM, PT\r
144  * "yyyyy.MMMMM.dd GGG hh:mm aaa"    ->>  01996.July.10 AD 12:08 PM\r
145  * </pre>\r
146  * </blockquote>\r
147  * <strong>Code Sample:</strong>\r
148  * <blockquote>\r
149  * <pre>\r
150  * SimpleTimeZone pdt = new SimpleTimeZone(-8 * 60 * 60 * 1000, "PST");\r
151  * pdt.setStartRule(Calendar.APRIL, 1, Calendar.SUNDAY, 2*60*60*1000);\r
152  * pdt.setEndRule(Calendar.OCTOBER, -1, Calendar.SUNDAY, 2*60*60*1000);\r
153  * <br>\r
154  * // Format the current time.\r
155  * SimpleDateFormat formatter\r
156  *     = new SimpleDateFormat ("yyyy.MM.dd G 'at' hh:mm:ss a zzz");\r
157  * Date currentTime_1 = new Date();\r
158  * String dateString = formatter.format(currentTime_1);\r
159  * <br>\r
160  * // Parse the previous string back into a Date.\r
161  * ParsePosition pos = new ParsePosition(0);\r
162  * Date currentTime_2 = formatter.parse(dateString, pos);\r
163  * </pre>\r
164  * </blockquote>\r
165  * In the example, the time value <code>currentTime_2</code> obtained from\r
166  * parsing will be equal to <code>currentTime_1</code>. However, they may not be\r
167  * equal if the am/pm marker 'a' is left out from the format pattern while\r
168  * the "hour in am/pm" pattern symbol is used. This information loss can\r
169  * happen when formatting the time in PM.\r
170  *\r
171  * <p>\r
172  * When parsing a date string using the abbreviated year pattern ("yy"),\r
173  * SimpleDateFormat must interpret the abbreviated year\r
174  * relative to some century.  It does this by adjusting dates to be\r
175  * within 80 years before and 20 years after the time the SimpleDateFormat\r
176  * instance is created. For example, using a pattern of "MM/dd/yy" and a\r
177  * SimpleDateFormat instance created on Jan 1, 1997,  the string\r
178  * "01/11/12" would be interpreted as Jan 11, 2012 while the string "05/04/64"\r
179  * would be interpreted as May 4, 1964.\r
180  * During parsing, only strings consisting of exactly two digits, as defined by\r
181  * {@link com.ibm.icu.lang.UCharacter#isDigit(int)}, will be parsed into the default\r
182  * century.\r
183  * Any other numeric string, such as a one digit string, a three or more digit\r
184  * string, or a two digit string that isn't all digits (for example, "-1"), is\r
185  * interpreted literally.  So "01/02/3" or "01/02/003" are parsed, using the\r
186  * same pattern, as Jan 2, 3 AD.  Likewise, "01/02/-3" is parsed as Jan 2, 4 BC.\r
187  *\r
188  * <p>\r
189  * If the year pattern does not have exactly two 'y' characters, the year is\r
190  * interpreted literally, regardless of the number of digits.  So using the\r
191  * pattern "MM/dd/yyyy", "01/11/12" parses to Jan 11, 12 A.D.\r
192  *\r
193  * <p>\r
194  * When numeric fields abut one another directly, with no intervening delimiter\r
195  * characters, they constitute a run of abutting numeric fields.  Such runs are\r
196  * parsed specially.  For example, the format "HHmmss" parses the input text\r
197  * "123456" to 12:34:56, parses the input text "12345" to 1:23:45, and fails to\r
198  * parse "1234".  In other words, the leftmost field of the run is flexible,\r
199  * while the others keep a fixed width.  If the parse fails anywhere in the run,\r
200  * then the leftmost field is shortened by one character, and the entire run is\r
201  * parsed again. This is repeated until either the parse succeeds or the\r
202  * leftmost field is one character in length.  If the parse still fails at that\r
203  * point, the parse of the run fails.\r
204  *\r
205  * <p>\r
206  * For time zones that have no names, use strings GMT+hours:minutes or\r
207  * GMT-hours:minutes.\r
208  *\r
209  * <p>\r
210  * The calendar defines what is the first day of the week, the first week\r
211  * of the year, whether hours are zero based or not (0 vs 12 or 24), and the\r
212  * time zone. There is one common decimal format to handle all the numbers;\r
213  * the digit count is handled programmatically according to the pattern.\r
214  *\r
215  * <h4>Synchronization</h4>\r
216  *\r
217  * Date formats are not synchronized. It is recommended to create separate\r
218  * format instances for each thread. If multiple threads access a format\r
219  * concurrently, it must be synchronized externally.\r
220  *\r
221  * @see          com.ibm.icu.util.Calendar\r
222  * @see          com.ibm.icu.util.GregorianCalendar\r
223  * @see          com.ibm.icu.util.TimeZone\r
224  * @see          DateFormat\r
225  * @see          DateFormatSymbols\r
226  * @see          DecimalFormat\r
227  * @author       Mark Davis, Chen-Lieh Huang, Alan Liu\r
228  * @stable ICU 2.0\r
229  */\r
230 public class SimpleDateFormat extends DateFormat {\r
231 \r
232     // the official serial version ID which says cryptically\r
233     // which version we're compatible with\r
234     private static final long serialVersionUID = 4774881970558875024L;\r
235 \r
236     // the internal serial version which says which version was written\r
237     // - 0 (default) for version up to JDK 1.1.3\r
238     // - 1 for version from JDK 1.1.4, which includes a new field\r
239     static final int currentSerialVersion = 1;\r
240 \r
241     \r
242     /*\r
243      * From calendar field to its level.\r
244      * Used to order calendar field.\r
245      * For example, calendar fields can be defined in the following order:\r
246      * year >  month > date > am-pm > hour >  minute\r
247      * YEAR --> 10, MONTH -->20, DATE --> 30; \r
248      * AM_PM -->40, HOUR --> 50, MINUTE -->60\r
249      */\r
250     private static final int[] CALENDAR_FIELD_TO_LEVEL =\r
251     {\r
252         /*GyM*/ 0, 10, 20,\r
253         /*wW*/ 20, 30,\r
254         /*dDEF*/ 30, 20, 30, 30,\r
255         /*ahHm*/ 40, 50, 50, 60,\r
256         /*sS..*/ 70, 80, \r
257         /*z?Y*/ 0, 0, 10, \r
258         /*eug*/ 30, 10, 0,\r
259         /*A*/ 40 \r
260     };\r
261 \r
262 \r
263     \r
264     /*\r
265      * From calendar field letter to its level.\r
266      * Used to order calendar field.\r
267      * For example, calendar fields can be defined in the following order:\r
268      * year >  month > date > am-pm > hour >  minute\r
269      * 'y' --> 10, 'M' -->20, 'd' --> 30; 'a' -->40, 'h' --> 50, 'm' -->60\r
270      */\r
271     private static final int[] PATTERN_CHAR_TO_LEVEL = \r
272     {\r
273     //       A   B   C   D    E   F   G    H   I   J   K   L    M   N   O\r
274         -1, 40, -1, -1, 20,  30, 30,  0,  50, -1, -1, 50, 20,  20, -1, -1,\r
275     //   P   Q   R    S   T   U  V   W   X   Y  Z\r
276         -1, 20, -1,  80, -1, -1, 0, 30, -1, 10, 0, -1, -1, -1, -1, -1,\r
277     //       a   b   c   d    e   f  g   h   i   j    k   l    m   n   o\r
278         -1, 40, -1, 30,  30, 30, -1, 0, 50, -1, -1,  50, -1,  60, -1, -1,\r
279     //   p   q   r    s   t   u  v   w   x    y  z\r
280         -1, 20, -1,  70, -1, 10, 0, 20, -1,  10, 0, -1, -1, -1, -1, -1\r
281     };\r
282 \r
283 \r
284     /**\r
285      * The version of the serialized data on the stream.  Possible values:\r
286      * <ul>\r
287      * <li><b>0</b> or not present on stream: JDK 1.1.3.  This version\r
288      * has no <code>defaultCenturyStart</code> on stream.\r
289      * <li><b>1</b> JDK 1.1.4 or later.  This version adds\r
290      * <code>defaultCenturyStart</code>.\r
291      * </ul>\r
292      * When streaming out this class, the most recent format\r
293      * and the highest allowable <code>serialVersionOnStream</code>\r
294      * is written.\r
295      * @serial\r
296      */\r
297     private int serialVersionOnStream = currentSerialVersion;\r
298 \r
299     /**\r
300      * The pattern string of this formatter.  This is always a non-localized\r
301      * pattern.  May not be null.  See class documentation for details.\r
302      * @serial\r
303      */\r
304     private String pattern;\r
305 \r
306     /**\r
307      * The override string of this formatter.  Used to override the\r
308      * numbering system for one or more fields.\r
309      * @serial\r
310      */\r
311     private String override;\r
312 \r
313     /**\r
314      * The hash map used for number format overrides.\r
315      * @serial\r
316      */\r
317     private HashMap numberFormatters;\r
318 \r
319     /**\r
320      * The hash map used for number format overrides.\r
321      * @serial\r
322      */\r
323     private HashMap overrideMap;\r
324 \r
325     /**\r
326      * The symbols used by this formatter for week names, month names,\r
327      * etc.  May not be null.\r
328      * @serial\r
329      * @see DateFormatSymbols\r
330      */\r
331     private DateFormatSymbols formatData;\r
332 \r
333     private transient ULocale locale;\r
334 \r
335     /**\r
336      * We map dates with two-digit years into the century starting at\r
337      * <code>defaultCenturyStart</code>, which may be any date.  May\r
338      * not be null.\r
339      * @serial\r
340      * @since JDK1.1.4\r
341      */\r
342     private Date defaultCenturyStart;\r
343 \r
344     private transient int defaultCenturyStartYear;\r
345 \r
346     // defaultCenturyBase is set when an instance is created\r
347     // and may be used for calculating defaultCenturyStart when needed.\r
348     private transient long defaultCenturyBase;\r
349 \r
350     // We need to preserve time zone type when parsing specific\r
351     // time zone text (xxx Standard Time vs xxx Daylight Time)\r
352     private static final int TZTYPE_UNK = 0, TZTYPE_STD = 1, TZTYPE_DST = 2;\r
353     private transient int tztype = TZTYPE_UNK;\r
354 \r
355     private static final int millisPerHour = 60 * 60 * 1000;\r
356     private static final int millisPerMinute = 60 * 1000;\r
357     private static final int millisPerSecond = 1000;\r
358 \r
359     // This prefix is designed to NEVER MATCH real text, in order to\r
360     // suppress the parsing of negative numbers.  Adjust as needed (if\r
361     // this becomes valid Unicode).\r
362     private static final String SUPPRESS_NEGATIVE_PREFIX = "\uAB00";\r
363 \r
364     /**\r
365      * If true, this object supports fast formatting using the\r
366      * subFormat variant that takes a StringBuffer.\r
367      */\r
368     private transient boolean useFastFormat;\r
369 \r
370     /**\r
371      * Construct a SimpleDateFormat using the default pattern for the default\r
372      * locale.  <b>Note:</b> Not all locales support SimpleDateFormat; for full\r
373      * generality, use the factory methods in the DateFormat class.\r
374      *\r
375      * @see DateFormat\r
376      * @stable ICU 2.0\r
377      */\r
378     public SimpleDateFormat() {\r
379         this(getDefaultPattern(), null, null, null, null, true, null);\r
380     }\r
381 \r
382     /**\r
383      * Construct a SimpleDateFormat using the given pattern in the default\r
384      * locale.  <b>Note:</b> Not all locales support SimpleDateFormat; for full\r
385      * generality, use the factory methods in the DateFormat class.\r
386      * @stable ICU 2.0\r
387      */\r
388     public SimpleDateFormat(String pattern)\r
389     {\r
390         this(pattern, null, null, null, null, true, null);\r
391     }\r
392 \r
393     /**\r
394      * Construct a SimpleDateFormat using the given pattern and locale.\r
395      * <b>Note:</b> Not all locales support SimpleDateFormat; for full\r
396      * generality, use the factory methods in the DateFormat class.\r
397      * @stable ICU 2.0\r
398      */\r
399     public SimpleDateFormat(String pattern, Locale loc)\r
400     {\r
401         this(pattern, null, null, null, ULocale.forLocale(loc), true, null);\r
402     }\r
403 \r
404     /**\r
405      * Construct a SimpleDateFormat using the given pattern and locale.\r
406      * <b>Note:</b> Not all locales support SimpleDateFormat; for full\r
407      * generality, use the factory methods in the DateFormat class.\r
408      * @stable ICU 3.2\r
409      */\r
410     public SimpleDateFormat(String pattern, ULocale loc)\r
411     {\r
412         this(pattern, null, null, null, loc, true, null);\r
413     }\r
414 \r
415     /**\r
416      * Construct a SimpleDateFormat using the given pattern , override and locale.\r
417      * @draft ICU 4.2\r
418      * @provisional This API might change or be removed in a future release.\r
419      */\r
420     public SimpleDateFormat(String pattern, String override, ULocale loc)\r
421     {\r
422         this(pattern, null, null, null, loc, false,override);\r
423     }\r
424 \r
425     /**\r
426      * Construct a SimpleDateFormat using the given pattern and\r
427      * locale-specific symbol data.\r
428      * Warning: uses default locale for digits!\r
429      * @stable ICU 2.0\r
430      */\r
431     public SimpleDateFormat(String pattern, DateFormatSymbols formatData)\r
432     {\r
433         this(pattern, (DateFormatSymbols)formatData.clone(), null, null, null, true, null);\r
434     }\r
435 \r
436     /**\r
437      * @internal ICU 3.2\r
438      * @deprecated This API is ICU internal only.\r
439      */\r
440     public SimpleDateFormat(String pattern, DateFormatSymbols formatData, ULocale loc)\r
441     {\r
442         this(pattern, (DateFormatSymbols)formatData.clone(), null, null, loc, true,null);\r
443     }\r
444 \r
445     /**\r
446      * Package-private constructor that allows a subclass to specify\r
447      * whether it supports fast formatting.\r
448      *\r
449      * TODO make this API public.\r
450      */\r
451     SimpleDateFormat(String pattern, DateFormatSymbols formatData, Calendar calendar, ULocale locale,\r
452                      boolean useFastFormat, String override) {\r
453         this(pattern, (DateFormatSymbols)formatData.clone(), (Calendar)calendar.clone(), null, locale, useFastFormat,override);\r
454     }\r
455 \r
456     SimpleDateFormat(String pattern, DateFormatSymbols formatData, Calendar calendar, ULocale locale,\r
457                      boolean useFastFormat) {\r
458         this(pattern, (DateFormatSymbols)formatData.clone(), (Calendar)calendar.clone(), null, locale, useFastFormat,null);\r
459     }\r
460 \r
461     /*\r
462      * The constructor called from all other SimpleDateFormat constructors\r
463      */\r
464     private SimpleDateFormat(String pattern, DateFormatSymbols formatData, Calendar calendar,\r
465             NumberFormat numberFormat, ULocale locale, boolean useFastFormat,String override) {\r
466         this.pattern = pattern;\r
467         this.formatData = formatData;\r
468         this.calendar = calendar;\r
469         this.numberFormat = numberFormat;\r
470         this.locale = locale; // time zone formatting\r
471         this.useFastFormat = useFastFormat;\r
472         this.override = override;\r
473         initialize();\r
474     }\r
475 \r
476     /**\r
477      * Create an instance of SimpleDateForamt for the given format configuration\r
478      * @param formatConfig the format configuration\r
479      * @return A SimpleDateFormat instance\r
480      * @internal ICU 3.8\r
481      * @deprecated This API is ICU internal only.\r
482      */\r
483     public static SimpleDateFormat getInstance(Calendar.FormatConfiguration formatConfig) {\r
484        \r
485         String ostr = formatConfig.getOverrideString();\r
486         boolean useFast = ( ostr != null && ostr.length() > 0 );\r
487 \r
488         return new SimpleDateFormat(formatConfig.getPatternString(),\r
489                     formatConfig.getDateFormatSymbols(),\r
490                     formatConfig.getCalendar(),\r
491                     null,\r
492                     formatConfig.getLocale(),\r
493                     useFast,\r
494                     formatConfig.getOverrideString());\r
495     }\r
496 \r
497     /*\r
498      * Initialized fields\r
499      */\r
500     private void initialize() {\r
501         if (locale == null) {\r
502             locale = ULocale.getDefault();\r
503         }\r
504         if (formatData == null) {\r
505             formatData = new DateFormatSymbols(locale);\r
506         }\r
507         if (calendar == null) {\r
508             calendar = Calendar.getInstance(locale);\r
509         }\r
510         if (numberFormat == null) {\r
511             NumberingSystem ns = NumberingSystem.getInstance(locale);\r
512             if ( ns.isAlgorithmic() ) {\r
513                 numberFormat = NumberFormat.getInstance(locale);\r
514             } else {\r
515                 char digit0 = ns.getDescription().charAt(0);\r
516                 // Use a NumberFormat optimized for date formatting\r
517                 numberFormat = new DateNumberFormat(locale, digit0);\r
518             }\r
519         }\r
520         // Note: deferring calendar calculation until when we really need it.\r
521         // Instead, we just record time of construction for backward compatibility.\r
522         defaultCenturyBase = System.currentTimeMillis();\r
523 \r
524         setLocale(calendar.getLocale(ULocale.VALID_LOCALE ), calendar.getLocale(ULocale.ACTUAL_LOCALE));\r
525         initLocalZeroPaddingNumberFormat();\r
526 \r
527         if (override != null) {\r
528            initNumberFormatters(locale);\r
529         }\r
530 \r
531     }\r
532 \r
533     // privates for the default pattern\r
534     private static ULocale cachedDefaultLocale = null;\r
535     private static String cachedDefaultPattern = null;\r
536     private static final String FALLBACKPATTERN = "yy/MM/dd HH:mm";\r
537 \r
538     /*\r
539      * Returns the default date and time pattern (SHORT) for the default locale.\r
540      * This method is only used by the default SimpleDateFormat constructor.\r
541      */\r
542     private static synchronized String getDefaultPattern() {\r
543         ULocale defaultLocale = ULocale.getDefault();\r
544         if (!defaultLocale.equals(cachedDefaultLocale)) {\r
545             cachedDefaultLocale = defaultLocale;\r
546             Calendar cal = Calendar.getInstance(cachedDefaultLocale);\r
547             try {\r
548                 CalendarData calData = new CalendarData(cachedDefaultLocale, cal.getType());\r
549                 String[] dateTimePatterns = calData.getDateTimePatterns();\r
550                 int glueIndex = 8;\r
551                 if (dateTimePatterns.length >= 13)\r
552                 {\r
553                     glueIndex += (SHORT + 1);\r
554                 }\r
555                 cachedDefaultPattern = MessageFormat.format(dateTimePatterns[glueIndex],\r
556                         new Object[] {dateTimePatterns[SHORT], dateTimePatterns[SHORT + 4]});\r
557             } catch (MissingResourceException e) {\r
558                 cachedDefaultPattern = FALLBACKPATTERN;\r
559             }\r
560         }\r
561         return cachedDefaultPattern;\r
562     }\r
563 \r
564     /* Define one-century window into which to disambiguate dates using\r
565      * two-digit years.\r
566      */\r
567     private void parseAmbiguousDatesAsAfter(Date startDate) {\r
568         defaultCenturyStart = startDate;\r
569         calendar.setTime(startDate);\r
570         defaultCenturyStartYear = calendar.get(Calendar.YEAR);\r
571     }\r
572 \r
573     /* Initialize defaultCenturyStart and defaultCenturyStartYear by base time.\r
574      * The default start time is 80 years before the creation time of this object.\r
575      */\r
576     private void initializeDefaultCenturyStart(long baseTime) {\r
577         defaultCenturyBase = baseTime;\r
578         // clone to avoid messing up date stored in calendar object\r
579         // when this method is called while parsing\r
580         Calendar tmpCal = (Calendar)calendar.clone();\r
581         tmpCal.setTimeInMillis(baseTime);\r
582         tmpCal.add(Calendar.YEAR, -80);\r
583         defaultCenturyStart = tmpCal.getTime();\r
584         defaultCenturyStartYear = tmpCal.get(Calendar.YEAR);\r
585     }\r
586 \r
587     /* Gets the default century start date for this object */\r
588     private Date getDefaultCenturyStart() {\r
589         if (defaultCenturyStart == null) {\r
590             // not yet initialized\r
591             initializeDefaultCenturyStart(defaultCenturyBase);\r
592         }\r
593         return defaultCenturyStart;\r
594     }\r
595 \r
596     /* Gets the default century start year for this object */\r
597     private int getDefaultCenturyStartYear() {\r
598         if (defaultCenturyStart == null) {\r
599             // not yet initialized\r
600             initializeDefaultCenturyStart(defaultCenturyBase);\r
601         }\r
602         return defaultCenturyStartYear;\r
603     }\r
604 \r
605     /**\r
606      * Sets the 100-year period 2-digit years will be interpreted as being in\r
607      * to begin on the date the user specifies.\r
608      * @param startDate During parsing, two digit years will be placed in the range\r
609      * <code>startDate</code> to <code>startDate + 100 years</code>.\r
610      * @stable ICU 2.0\r
611      */\r
612     public void set2DigitYearStart(Date startDate) {\r
613         parseAmbiguousDatesAsAfter(startDate);\r
614     }\r
615 \r
616     /**\r
617      * Returns the beginning date of the 100-year period 2-digit years are interpreted\r
618      * as being within.\r
619      * @return the start of the 100-year period into which two digit years are\r
620      * parsed\r
621      * @stable ICU 2.0\r
622      */\r
623     public Date get2DigitYearStart() {\r
624         return getDefaultCenturyStart();\r
625     }\r
626 \r
627     /**\r
628      * Overrides DateFormat.\r
629      * <p>Formats a date or time, which is the standard millis\r
630      * since January 1, 1970, 00:00:00 GMT.\r
631      * <p>Example: using the US locale:\r
632      * "yyyy.MM.dd G 'at' HH:mm:ss zzz" ->> 1996.07.10 AD at 15:08:56 PDT\r
633      * @param cal the calendar whose date-time value is to be formatted into a date-time string\r
634      * @param toAppendTo where the new date-time text is to be appended\r
635      * @param pos the formatting position. On input: an alignment field,\r
636      * if desired. On output: the offsets of the alignment field.\r
637      * @return the formatted date-time string.\r
638      * @see DateFormat\r
639      * @stable ICU 2.0\r
640      */\r
641     public StringBuffer format(Calendar cal, StringBuffer toAppendTo,\r
642                                FieldPosition pos) {\r
643         TimeZone backupTZ = null;\r
644         if (cal != calendar && !cal.getType().equals(calendar.getType())) {\r
645             // Different calendar type\r
646             // We use the time and time zone from the input calendar, but\r
647             // do not use the input calendar for field calculation.\r
648             calendar.setTimeInMillis(cal.getTimeInMillis());\r
649             backupTZ = calendar.getTimeZone();\r
650             calendar.setTimeZone(cal.getTimeZone());\r
651             cal = calendar;\r
652         }\r
653         StringBuffer result = format(cal, toAppendTo, pos, null);\r
654         if (backupTZ != null) {\r
655             // Restore the original time zone\r
656             calendar.setTimeZone(backupTZ);\r
657         }\r
658         return result;\r
659     }\r
660 \r
661     // The actual method to format date. If List attributes is not null,\r
662     // then attribute information will be recorded.\r
663     private StringBuffer format(Calendar cal, StringBuffer toAppendTo,\r
664             FieldPosition pos, List attributes) {\r
665         // Initialize\r
666         pos.setBeginIndex(0);\r
667         pos.setEndIndex(0);\r
668 \r
669         // Careful: For best performance, minimize the number of calls\r
670         // to StringBuffer.append() by consolidating appends when\r
671         // possible.\r
672 \r
673         Object[] items = getPatternItems();\r
674         for (int i = 0; i < items.length; i++) {\r
675             if (items[i] instanceof String) {\r
676                 toAppendTo.append((String)items[i]);\r
677             } else {\r
678                 PatternItem item = (PatternItem)items[i];\r
679 //#if defined(FOUNDATION10) || defined(J2SE13)\r
680 //#else\r
681                 int start = 0;\r
682                 if (attributes != null) {\r
683                     // Save the current length\r
684                     start = toAppendTo.length();\r
685                 }\r
686 //#endif\r
687                 if (useFastFormat) {\r
688                     subFormat(toAppendTo, item.type, item.length, toAppendTo.length(), pos, cal);\r
689                 } else {\r
690                     toAppendTo.append(subFormat(item.type, item.length, toAppendTo.length(), pos, formatData, cal));\r
691                 }\r
692 //#if defined(FOUNDATION10) || defined(J2SE13)\r
693 //#else\r
694                 if (attributes != null) {\r
695                     // Check the sub format length\r
696                     int end = toAppendTo.length();\r
697                     if (end - start > 0) {\r
698                         // Append the attribute to the list\r
699                         DateFormat.Field attr = patternCharToDateFormatField(item.type);\r
700                         FieldPosition fp = new FieldPosition(attr);\r
701                         fp.setBeginIndex(start);\r
702                         fp.setEndIndex(end);\r
703                         attributes.add(fp);\r
704                     }\r
705                 }\r
706 //#endif\r
707             }\r
708         }\r
709         return toAppendTo;\r
710         \r
711     }\r
712 \r
713     // Map pattern character to index\r
714     private static final int PATTERN_CHAR_BASE = 0x40;\r
715     private static final int[] PATTERN_CHAR_TO_INDEX =\r
716     {\r
717     //       A   B   C   D   E   F   G   H   I   J   K   L   M   N   O\r
718         -1, 22, -1, -1, 10,  9, 11,  0,  5, -1, -1, 16, 26,  2, -1, -1,\r
719     //   P   Q   R   S   T   U   V   W   X   Y   Z\r
720         -1, 27, -1,  8, -1, -1, 29, 13, -1, 18, 23, -1, -1, -1, -1, -1,\r
721     //       a   b   c   d   e   f   g   h   i   j   k   l   m   n   o\r
722         -1, 14, -1, 25,  3, 19, -1, 21, 15, -1, -1,  4, -1,  6, -1, -1,\r
723     //   p   q   r   s   t   u   v   w   x   y   z\r
724         -1, 28, -1,  7, -1, 20, 24, 12, -1,  1, 17, -1, -1, -1, -1, -1\r
725     };\r
726     \r
727     // Map pattern character index to Calendar field number\r
728     private static final int[] PATTERN_INDEX_TO_CALENDAR_FIELD =\r
729     {\r
730         /*GyM*/ Calendar.ERA, Calendar.YEAR, Calendar.MONTH,\r
731         /*dkH*/ Calendar.DATE, Calendar.HOUR_OF_DAY, Calendar.HOUR_OF_DAY,\r
732         /*msS*/ Calendar.MINUTE, Calendar.SECOND, Calendar.MILLISECOND,\r
733         /*EDF*/ Calendar.DAY_OF_WEEK, Calendar.DAY_OF_YEAR, Calendar.DAY_OF_WEEK_IN_MONTH,\r
734         /*wWa*/ Calendar.WEEK_OF_YEAR, Calendar.WEEK_OF_MONTH, Calendar.AM_PM,\r
735         /*hKz*/ Calendar.HOUR, Calendar.HOUR, Calendar.ZONE_OFFSET,\r
736         /*Yeu*/ Calendar.YEAR_WOY, Calendar.DOW_LOCAL, Calendar.EXTENDED_YEAR,\r
737         /*gAZ*/ Calendar.JULIAN_DAY, Calendar.MILLISECONDS_IN_DAY, Calendar.ZONE_OFFSET,\r
738         /*v*/   Calendar.ZONE_OFFSET,\r
739         /*c*/   Calendar.DOW_LOCAL,\r
740         /*L*/   Calendar.MONTH,\r
741         /*Qq*/  Calendar.MONTH, Calendar.MONTH,\r
742         /*V*/   Calendar.ZONE_OFFSET,\r
743     };\r
744 \r
745     // Map pattern character index to DateFormat field number\r
746     private static final int[] PATTERN_INDEX_TO_DATE_FORMAT_FIELD = {\r
747         /*GyM*/ DateFormat.ERA_FIELD, DateFormat.YEAR_FIELD, DateFormat.MONTH_FIELD,\r
748         /*dkH*/ DateFormat.DATE_FIELD, DateFormat.HOUR_OF_DAY1_FIELD, DateFormat.HOUR_OF_DAY0_FIELD,\r
749         /*msS*/ DateFormat.MINUTE_FIELD, DateFormat.SECOND_FIELD, DateFormat.FRACTIONAL_SECOND_FIELD,\r
750         /*EDF*/ DateFormat.DAY_OF_WEEK_FIELD, DateFormat.DAY_OF_YEAR_FIELD, DateFormat.DAY_OF_WEEK_IN_MONTH_FIELD,\r
751         /*wWa*/ DateFormat.WEEK_OF_YEAR_FIELD, DateFormat.WEEK_OF_MONTH_FIELD, DateFormat.AM_PM_FIELD,\r
752         /*hKz*/ DateFormat.HOUR1_FIELD, DateFormat.HOUR0_FIELD, DateFormat.TIMEZONE_FIELD,\r
753         /*Yeu*/ DateFormat.YEAR_WOY_FIELD, DateFormat.DOW_LOCAL_FIELD, DateFormat.EXTENDED_YEAR_FIELD,\r
754         /*gAZ*/ DateFormat.JULIAN_DAY_FIELD, DateFormat.MILLISECONDS_IN_DAY_FIELD, DateFormat.TIMEZONE_RFC_FIELD,\r
755         /*v*/   DateFormat.TIMEZONE_GENERIC_FIELD, \r
756         /*c*/   DateFormat.STANDALONE_DAY_FIELD,\r
757         /*L*/   DateFormat.STANDALONE_MONTH_FIELD,\r
758         /*Qq*/  DateFormat.QUARTER_FIELD, DateFormat.STANDALONE_QUARTER_FIELD,\r
759         /*V*/   DateFormat.TIMEZONE_SPECIAL_FIELD, \r
760     };\r
761 \r
762 //#if defined(FOUNDATION10) || defined(J2SE13)\r
763 //#else\r
764     // Map pattern character index to DateFormat.Field\r
765     private static final DateFormat.Field[] PATTERN_INDEX_TO_DATE_FORMAT_ATTRIBUTE = {\r
766         /*GyM*/ DateFormat.Field.ERA, DateFormat.Field.YEAR, DateFormat.Field.MONTH,\r
767         /*dkH*/ DateFormat.Field.DAY_OF_MONTH, DateFormat.Field.HOUR_OF_DAY1, DateFormat.Field.HOUR_OF_DAY0,\r
768         /*msS*/ DateFormat.Field.MINUTE, DateFormat.Field.SECOND, DateFormat.Field.MILLISECOND,\r
769         /*EDF*/ DateFormat.Field.DAY_OF_WEEK, DateFormat.Field.DAY_OF_YEAR, DateFormat.Field.DAY_OF_WEEK_IN_MONTH,\r
770         /*wWa*/ DateFormat.Field.WEEK_OF_YEAR, DateFormat.Field.WEEK_OF_MONTH, DateFormat.Field.AM_PM,\r
771         /*hKz*/ DateFormat.Field.HOUR1, DateFormat.Field.HOUR0, DateFormat.Field.TIME_ZONE,\r
772         /*Yeu*/ DateFormat.Field.YEAR_WOY, DateFormat.Field.DOW_LOCAL, DateFormat.Field.EXTENDED_YEAR,\r
773         /*gAZ*/ DateFormat.Field.JULIAN_DAY, DateFormat.Field.MILLISECONDS_IN_DAY, DateFormat.Field.TIME_ZONE,\r
774         /*v*/   DateFormat.Field.TIME_ZONE,\r
775         /*c*/   DateFormat.Field.DAY_OF_WEEK,\r
776         /*L*/   DateFormat.Field.MONTH,\r
777         /*Qq*/  DateFormat.Field.QUARTER, DateFormat.Field.QUARTER,\r
778         /*V*/   DateFormat.Field.TIME_ZONE,\r
779     };\r
780 \r
781     /**\r
782      * Return a DateFormat.Field constant associated with the specified format pattern\r
783      * character.\r
784      * \r
785      * @param ch The pattern character\r
786      * @return DateFormat.Field associated with the pattern character\r
787      * \r
788      * @stable ICU 3.8\r
789      */\r
790     protected DateFormat.Field patternCharToDateFormatField(char ch) {\r
791         int patternCharIndex = -1;\r
792         if ('A' <= ch && ch <= 'z') {\r
793             patternCharIndex = PATTERN_CHAR_TO_INDEX[(int)ch - PATTERN_CHAR_BASE];\r
794         }\r
795         if (patternCharIndex != -1) {\r
796             return PATTERN_INDEX_TO_DATE_FORMAT_ATTRIBUTE[patternCharIndex];\r
797         }\r
798         return null;\r
799     }\r
800 //#endif\r
801 \r
802     /**\r
803      * Format a single field, given its pattern character.  Subclasses may\r
804      * override this method in order to modify or add formatting\r
805      * capabilities.\r
806      * @param ch the pattern character\r
807      * @param count the number of times ch is repeated in the pattern\r
808      * @param beginOffset the offset of the output string at the start of\r
809      * this field; used to set pos when appropriate\r
810      * @param pos receives the position of a field, when appropriate\r
811      * @param fmtData the symbols for this formatter\r
812      * @stable ICU 2.0\r
813      */\r
814     protected String subFormat(char ch, int count, int beginOffset,\r
815                                FieldPosition pos, DateFormatSymbols fmtData,\r
816                                Calendar cal)\r
817         throws IllegalArgumentException\r
818     {\r
819         // Note: formatData is ignored\r
820         StringBuffer buf = new StringBuffer();\r
821         subFormat(buf, ch, count, beginOffset, pos, cal);\r
822         return buf.toString();\r
823     }\r
824 \r
825     /**\r
826      * Format a single field; useFastFormat variant.  Reuses a\r
827      * StringBuffer for results instead of creating a String on the\r
828      * heap for each call.\r
829      *\r
830      * NOTE We don't really need the beginOffset parameter, EXCEPT for\r
831      * the need to support the slow subFormat variant (above) which\r
832      * has to pass it in to us.\r
833      *\r
834      * TODO make this API public\r
835      *\r
836      * @internal\r
837      * @deprecated This API is ICU internal only.\r
838      */\r
839     protected void subFormat(StringBuffer buf,\r
840                              char ch, int count, int beginOffset,\r
841                              FieldPosition pos,\r
842                              Calendar cal) {\r
843         final int maxIntCount = Integer.MAX_VALUE;\r
844         final int bufstart = buf.length();\r
845 \r
846         // final int patternCharIndex = DateFormatSymbols.patternChars.indexOf(ch);\r
847         int patternCharIndex = -1;\r
848         if ('A' <= ch && ch <= 'z') {\r
849             patternCharIndex = PATTERN_CHAR_TO_INDEX[(int)ch - PATTERN_CHAR_BASE];\r
850         }\r
851 \r
852         if (patternCharIndex == -1) {\r
853             throw new IllegalArgumentException("Illegal pattern character " +\r
854                                                "'" + ch + "' in \"" +\r
855                                                new String(pattern) + '"');\r
856         }\r
857 \r
858         final int field = PATTERN_INDEX_TO_CALENDAR_FIELD[patternCharIndex];\r
859         int value = cal.get(field);\r
860 \r
861         String zoneString = null;\r
862 \r
863         NumberFormat currentNumberFormat = getNumberFormat(ch);\r
864 \r
865         switch (patternCharIndex) {\r
866         case 0: // 'G' - ERA\r
867             if (count == 5) {\r
868                 safeAppend(formatData.narrowEras, value, buf);\r
869             } else if (count == 4) {\r
870                 safeAppend(formatData.eraNames, value, buf);\r
871             } else {\r
872                 safeAppend(formatData.eras, value, buf);\r
873             }\r
874             break;\r
875         case 1: // 'y' - YEAR\r
876             /* According to the specification, if the number of pattern letters ('y') is 2,\r
877              * the year is truncated to 2 digits; otherwise it is interpreted as a number.\r
878              * But the original code process 'y', 'yy', 'yyy' in the same way. and process\r
879              * patterns with 4 or more than 4 'y' characters in the same way.\r
880              * So I change the codes to meet the specification. [Richard/GCl]\r
881              */\r
882             if (count == 2)\r
883                 zeroPaddingNumber(currentNumberFormat,buf, value, 2, 2); // clip 1996 to 96\r
884             else //count = 1 or count > 2\r
885                 zeroPaddingNumber(currentNumberFormat,buf, value, count, maxIntCount);\r
886             break;\r
887         case 2: // 'M' - MONTH\r
888             if (count == 5) {\r
889                 safeAppend(formatData.narrowMonths, value, buf);\r
890             } else if (count == 4) {\r
891                 safeAppend(formatData.months, value, buf);\r
892             } else if (count == 3) {\r
893                 safeAppend(formatData.shortMonths, value, buf);\r
894             } else {\r
895                 zeroPaddingNumber(currentNumberFormat,buf, value+1, count, maxIntCount);\r
896             }\r
897             break;\r
898         case 4: // 'k' - HOUR_OF_DAY (1..24)\r
899             if (value == 0)\r
900                 zeroPaddingNumber(currentNumberFormat,buf,\r
901                                   cal.getMaximum(Calendar.HOUR_OF_DAY)+1,\r
902                                   count, maxIntCount);\r
903             else\r
904                 zeroPaddingNumber(currentNumberFormat,buf, value, count, maxIntCount);\r
905             break;\r
906         case 8: // 'S' - FRACTIONAL_SECOND\r
907             // Fractional seconds left-justify\r
908             {\r
909                 numberFormat.setMinimumIntegerDigits(Math.min(3, count));\r
910                 numberFormat.setMaximumIntegerDigits(maxIntCount);\r
911                 if (count == 1) {\r
912                     value = (value + 50) / 100;\r
913                 } else if (count == 2) {\r
914                     value = (value + 5) / 10;\r
915                 }\r
916                 FieldPosition p = new FieldPosition(-1);\r
917                 numberFormat.format((long) value, buf, p);\r
918                 if (count > 3) {\r
919                     numberFormat.setMinimumIntegerDigits(count - 3);\r
920                     numberFormat.format(0L, buf, p);\r
921                 }\r
922             }\r
923             break;\r
924         case 19: // 'e' - DOW_LOCAL (use DOW_LOCAL for numeric, DAY_OF_WEEK for format names)\r
925             if (count < 3) {\r
926                 zeroPaddingNumber(currentNumberFormat,buf, value, count, maxIntCount);\r
927                 break;\r
928             }\r
929             // For alpha day-of-week, we don't want DOW_LOCAL,\r
930             // we need the standard DAY_OF_WEEK.\r
931             value = cal.get(Calendar.DAY_OF_WEEK);\r
932             // fall through, do not break here\r
933         case 9: // 'E' - DAY_OF_WEEK\r
934             if (count == 5) {\r
935                 safeAppend(formatData.narrowWeekdays, value, buf);\r
936             } else if (count == 4) {\r
937                 safeAppend(formatData.weekdays, value, buf);\r
938             } else {// count <= 3, use abbreviated form if exists\r
939                 safeAppend(formatData.shortWeekdays, value, buf);\r
940             }\r
941             break;\r
942         case 14: // 'a' - AM_PM\r
943             safeAppend(formatData.ampms, value, buf);\r
944             break;\r
945         case 15: // 'h' - HOUR (1..12)\r
946             if (value == 0)\r
947                 zeroPaddingNumber(currentNumberFormat,buf,\r
948                                   cal.getLeastMaximum(Calendar.HOUR)+1,\r
949                                   count, maxIntCount);\r
950             else\r
951                 zeroPaddingNumber(currentNumberFormat,buf, value, count, maxIntCount);\r
952             break;\r
953         case 17: // 'z' - ZONE_OFFSET\r
954             if (count < 4) {\r
955                 // "z", "zz", "zzz"\r
956                 zoneString = formatData.getZoneStringFormat().getSpecificShortString(cal, true /* commonly used only */);\r
957             } else {\r
958                 zoneString = formatData.getZoneStringFormat().getSpecificLongString(cal);\r
959             }\r
960             if (zoneString != null && zoneString.length() != 0) {\r
961                 buf.append(zoneString);\r
962             } else {\r
963                 // Use localized GMT format as fallback\r
964                 appendGMT(currentNumberFormat,buf, cal);\r
965             }\r
966             break;\r
967         case 23: // 'Z' - TIMEZONE_RFC\r
968             if (count < 4) {\r
969                 // RFC822 format, must use ASCII digits\r
970                 int val = (cal.get(Calendar.ZONE_OFFSET) + cal.get(Calendar.DST_OFFSET));\r
971                 char sign = '+';\r
972                 if (val < 0) {\r
973                     val = -val;\r
974                     sign = '-';\r
975                 }\r
976                 buf.append(sign);\r
977 \r
978                 int offsetH = val / millisPerHour;\r
979                 val = val % millisPerHour;\r
980                 int offsetM = val / millisPerMinute;\r
981                 val = val % millisPerMinute;\r
982                 int offsetS = val / millisPerSecond;\r
983 \r
984                 int num = 0, denom = 0;\r
985                 if (offsetS == 0) {\r
986                     val = offsetH*100 + offsetM; // HHmm\r
987                     num = val % 10000;\r
988                     denom = 1000;\r
989                 } else {\r
990                     val = offsetH*10000 + offsetM*100 + offsetS; // HHmmss\r
991                     num = val % 1000000;\r
992                     denom = 100000;\r
993                 }\r
994                 while (denom >= 1) {\r
995                     char digit = (char)((num / denom) + '0');\r
996                     buf.append(digit);\r
997                     num = num % denom;\r
998                     denom /= 10;\r
999                 }\r
1000             } else {\r
1001                 // long form, localized GMT pattern\r
1002                 appendGMT(currentNumberFormat,buf, cal);\r
1003             }\r
1004             break;\r
1005         case 24: // 'v' - TIMEZONE_GENERIC\r
1006             if (count == 1) {\r
1007                 // "v"\r
1008                 zoneString = formatData.getZoneStringFormat().getGenericShortString(cal, true /* commonly used only */);\r
1009             } else if (count == 4) {\r
1010                 // "vvvv"\r
1011                 zoneString = formatData.getZoneStringFormat().getGenericLongString(cal);\r
1012             }\r
1013             if (zoneString != null && zoneString.length() != 0) {\r
1014                 buf.append(zoneString);\r
1015             } else {\r
1016                 // Use localized GMT format as fallback\r
1017                 appendGMT(currentNumberFormat,buf, cal);\r
1018             }\r
1019             break;\r
1020         case 25: // 'c' - STANDALONE DAY (use DOW_LOCAL for numeric, DAY_OF_WEEK for standalone names)\r
1021             if (count < 3) {\r
1022                 zeroPaddingNumber(currentNumberFormat,buf, value, 1, maxIntCount);\r
1023                 break;\r
1024             }\r
1025             // For alpha day-of-week, we don't want DOW_LOCAL,\r
1026             // we need the standard DAY_OF_WEEK.\r
1027             value = cal.get(Calendar.DAY_OF_WEEK);\r
1028             if (count == 5) {\r
1029                 safeAppend(formatData.standaloneNarrowWeekdays, value, buf);\r
1030             } else if (count == 4) {\r
1031                 safeAppend(formatData.standaloneWeekdays, value, buf);\r
1032             } else { // count == 3\r
1033                 safeAppend(formatData.standaloneShortWeekdays, value, buf);\r
1034             }\r
1035             break;\r
1036         case 26: // 'L' - STANDALONE MONTH\r
1037             if (count == 5) {\r
1038                 safeAppend(formatData.standaloneNarrowMonths, value, buf);\r
1039             } else if (count == 4) {\r
1040                 safeAppend(formatData.standaloneMonths, value, buf);\r
1041             } else if (count == 3) {\r
1042                 safeAppend(formatData.standaloneShortMonths, value, buf);\r
1043             } else {\r
1044                 zeroPaddingNumber(currentNumberFormat,buf, value+1, count, maxIntCount);\r
1045             }\r
1046             break;\r
1047         case 27: // 'Q' - QUARTER\r
1048             if (count >= 4) {\r
1049                 safeAppend(formatData.quarters, value/3, buf);\r
1050             } else if (count == 3) {\r
1051                 safeAppend(formatData.shortQuarters, value/3, buf);\r
1052             } else {\r
1053                 zeroPaddingNumber(currentNumberFormat,buf, (value/3)+1, count, maxIntCount);\r
1054             }\r
1055             break;\r
1056         case 28: // 'q' - STANDALONE QUARTER\r
1057             if (count >= 4) {\r
1058                 safeAppend(formatData.standaloneQuarters, value/3, buf);\r
1059             } else if (count == 3) {\r
1060                 safeAppend(formatData.standaloneShortQuarters, value/3, buf);\r
1061             } else {\r
1062                 zeroPaddingNumber(currentNumberFormat,buf, (value/3)+1, count, maxIntCount);\r
1063             }\r
1064             break;\r
1065         case 29: // 'V' - TIMEZONE_SPECIAL\r
1066             if (count == 1) {\r
1067                 // "V"\r
1068                 zoneString = formatData.getZoneStringFormat().getSpecificShortString(cal, false /* ignoring commonly used */);\r
1069             } else if (count == 4) {\r
1070                 // "VVVV"\r
1071                 zoneString = formatData.getZoneStringFormat().getGenericLocationString(cal);\r
1072             }\r
1073             if (zoneString != null && zoneString.length() != 0) {\r
1074                 buf.append(zoneString);\r
1075             } else {\r
1076                 // Use localized GMT format as fallback\r
1077                 appendGMT(currentNumberFormat,buf, cal);\r
1078             }\r
1079             break;\r
1080         default:\r
1081             // case 3: // 'd' - DATE\r
1082             // case 5: // 'H' - HOUR_OF_DAY (0..23)\r
1083             // case 6: // 'm' - MINUTE\r
1084             // case 7: // 's' - SECOND\r
1085             // case 10: // 'D' - DAY_OF_YEAR\r
1086             // case 11: // 'F' - DAY_OF_WEEK_IN_MONTH\r
1087             // case 12: // 'w' - WEEK_OF_YEAR\r
1088             // case 13: // 'W' - WEEK_OF_MONTH\r
1089             // case 16: // 'K' - HOUR (0..11)\r
1090             // case 18: // 'Y' - YEAR_WOY\r
1091             // case 20: // 'u' - EXTENDED_YEAR\r
1092             // case 21: // 'g' - JULIAN_DAY\r
1093             // case 22: // 'A' - MILLISECONDS_IN_DAY\r
1094 \r
1095             zeroPaddingNumber(currentNumberFormat,buf, value, count, maxIntCount);\r
1096             break;\r
1097         } // switch (patternCharIndex)\r
1098 \r
1099         // Set the FieldPosition (for the first occurrence only)\r
1100         if (pos.getBeginIndex() == pos.getEndIndex()) {\r
1101             if (pos.getField() == PATTERN_INDEX_TO_DATE_FORMAT_FIELD[patternCharIndex]) {\r
1102                 pos.setBeginIndex(beginOffset);\r
1103                 pos.setEndIndex(beginOffset + buf.length() - bufstart);\r
1104             }\r
1105 //#if defined(FOUNDATION10) || defined(J2SE13)\r
1106 //#else\r
1107             else if (pos.getFieldAttribute() == PATTERN_INDEX_TO_DATE_FORMAT_ATTRIBUTE[patternCharIndex]) {\r
1108                 pos.setBeginIndex(beginOffset);\r
1109                 pos.setEndIndex(beginOffset + buf.length() - bufstart);\r
1110             }\r
1111 //#endif\r
1112         }\r
1113     }\r
1114 \r
1115     private static void safeAppend(String[] array, int value, StringBuffer appendTo) {\r
1116         if (array != null && value >= 0 && value < array.length) {\r
1117             appendTo.append(array[value]);\r
1118         }\r
1119     }\r
1120 \r
1121     /*\r
1122      * PatternItem store parsed date/time field pattern information.\r
1123      */\r
1124     private static class PatternItem {\r
1125         final char type;\r
1126         final int length;\r
1127         final boolean isNumeric;\r
1128 \r
1129         PatternItem(char type, int length) {\r
1130             this.type = type;\r
1131             this.length = length;\r
1132             isNumeric = isNumeric(type, length);\r
1133         }\r
1134     }\r
1135 \r
1136     private static ICUCache PARSED_PATTERN_CACHE = new SimpleCache();\r
1137     private transient Object[] patternItems;\r
1138 \r
1139     /*\r
1140      * Returns parsed pattern items.  Each item is either String or\r
1141      * PatternItem.\r
1142      */\r
1143     private Object[] getPatternItems() {\r
1144         if (patternItems != null) {\r
1145             return patternItems;\r
1146         }\r
1147 \r
1148         patternItems = (Object[])PARSED_PATTERN_CACHE.get(pattern);\r
1149         if (patternItems != null) {\r
1150             return patternItems;\r
1151         }\r
1152 \r
1153         boolean isPrevQuote = false;\r
1154         boolean inQuote = false;\r
1155         StringBuffer text = new StringBuffer();\r
1156         char itemType = 0;  // 0 for string literal, otherwise date/time pattern character\r
1157         int itemLength = 1;\r
1158 \r
1159         List items = new ArrayList();\r
1160 \r
1161         for (int i = 0; i < pattern.length(); i++) {\r
1162             char ch = pattern.charAt(i);\r
1163             if (ch == '\'') {\r
1164                 if (isPrevQuote) {\r
1165                     text.append('\'');\r
1166                     isPrevQuote = false;\r
1167                 } else {\r
1168                     isPrevQuote = true;\r
1169                     if (itemType != 0) {\r
1170                         items.add(new PatternItem(itemType, itemLength));\r
1171                         itemType = 0;\r
1172                     }\r
1173                 }\r
1174                 inQuote = !inQuote;\r
1175             } else {\r
1176                 isPrevQuote = false;\r
1177                 if (inQuote) {\r
1178                     text.append(ch);\r
1179                 } else {\r
1180                     if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')) {\r
1181                         // a date/time pattern character\r
1182                         if (ch == itemType) {\r
1183                             itemLength++;\r
1184                         } else {\r
1185                             if (itemType == 0) {\r
1186                                 if (text.length() > 0) {\r
1187                                     items.add(text.toString());\r
1188                                     text.setLength(0);\r
1189                                 }\r
1190                             } else {\r
1191                                 items.add(new PatternItem(itemType, itemLength));\r
1192                             }\r
1193                             itemType = ch;\r
1194                             itemLength = 1;\r
1195                         }\r
1196                     } else {\r
1197                         // a string literal\r
1198                         if (itemType != 0) {\r
1199                             items.add(new PatternItem(itemType, itemLength));\r
1200                             itemType = 0;\r
1201                         }\r
1202                         text.append(ch);\r
1203                     }\r
1204                 }\r
1205             }\r
1206         }\r
1207         // handle last item\r
1208         if (itemType == 0) {\r
1209             if (text.length() > 0) {\r
1210                 items.add(text.toString());\r
1211                 text.setLength(0);\r
1212             }\r
1213         } else {\r
1214             items.add(new PatternItem(itemType, itemLength));\r
1215         }\r
1216 \r
1217         patternItems = new Object[items.size()];\r
1218         items.toArray(patternItems);\r
1219 \r
1220         PARSED_PATTERN_CACHE.put(pattern, patternItems);\r
1221         \r
1222         return patternItems;\r
1223     }\r
1224 \r
1225     /*\r
1226      * Time zone localized GMT format stuffs\r
1227      */\r
1228     private static final String STR_GMT = "GMT";\r
1229     private static final String STR_UT = "UT";\r
1230     private static final String STR_UTC = "UTC";\r
1231     private static final int STR_GMT_LEN = 3;\r
1232     private static final int STR_UT_LEN = 2;\r
1233     private static final int STR_UTC_LEN = 3;\r
1234     private static final char PLUS = '+';\r
1235     private static final char MINUS = '-';\r
1236     private static final char COLON = ':';\r
1237 \r
1238     private void appendGMT(NumberFormat currentNumberFormat,StringBuffer buf, Calendar cal) {\r
1239         int offset = cal.get(Calendar.ZONE_OFFSET) + cal.get(Calendar.DST_OFFSET);\r
1240 \r
1241         if (isDefaultGMTFormat()) {\r
1242             formatGMTDefault(currentNumberFormat,buf, offset);\r
1243         } else {\r
1244             int sign = DateFormatSymbols.OFFSET_POSITIVE;\r
1245             if (offset < 0) {\r
1246                 offset = -offset;\r
1247                 sign = DateFormatSymbols.OFFSET_NEGATIVE;\r
1248             }\r
1249             int width = offset%(60*1000) == 0 ? DateFormatSymbols.OFFSET_HM : DateFormatSymbols.OFFSET_HMS;\r
1250 \r
1251             MessageFormat fmt = getGMTFormatter(sign, width);\r
1252             fmt.format(new Object[] {new Long(offset)}, buf, null);\r
1253         }\r
1254     }\r
1255 \r
1256     private void formatGMTDefault(NumberFormat currentNumberFormat,StringBuffer buf, int offset) {\r
1257         buf.append(STR_GMT);\r
1258         if (offset >= 0) {\r
1259             buf.append(PLUS);\r
1260         } else {\r
1261             buf.append(MINUS);\r
1262             offset = -offset;\r
1263         }\r
1264         offset /= 1000; // now in seconds\r
1265         int sec = offset % 60;\r
1266         offset /= 60;\r
1267         int min = offset % 60;\r
1268         int hour = offset / 60;\r
1269 \r
1270         zeroPaddingNumber(currentNumberFormat,buf, hour, 2, 2);\r
1271         buf.append(COLON);\r
1272         zeroPaddingNumber(currentNumberFormat,buf, min, 2, 2);\r
1273         if (sec != 0) {\r
1274             buf.append(COLON);\r
1275             zeroPaddingNumber(currentNumberFormat,buf, sec, 2, 2);\r
1276         }\r
1277     }\r
1278 \r
1279     private Integer parseGMT(String text, ParsePosition pos, NumberFormat currentNumberFormat) {\r
1280         if (!isDefaultGMTFormat()) {\r
1281             int start = pos.getIndex();\r
1282             String gmtPattern = formatData.gmtFormat;\r
1283 \r
1284             // Quick check\r
1285             boolean prefixMatch = false;\r
1286             int prefixLen = gmtPattern.indexOf('{');\r
1287             if (prefixLen > 0 && text.regionMatches(start, gmtPattern, 0, prefixLen)) {\r
1288                 prefixMatch = true;\r
1289             }\r
1290 \r
1291             if (prefixMatch) {\r
1292                 // Prefix matched\r
1293                 MessageFormat fmt;\r
1294                 Object[] parsedObjects;\r
1295                 int offset;\r
1296 \r
1297                 // Try negative Hms\r
1298                 fmt = getGMTFormatter(DateFormatSymbols.OFFSET_NEGATIVE, DateFormatSymbols.OFFSET_HMS);\r
1299                 parsedObjects = fmt.parse(text, pos);\r
1300                 if ((parsedObjects != null) && (parsedObjects[0] instanceof Date)\r
1301                         && (pos.getIndex() - start) >= getGMTFormatMinHMSLen(DateFormatSymbols.OFFSET_NEGATIVE)) {\r
1302                     offset = (int)((Date)parsedObjects[0]).getTime();\r
1303                     return new Integer(-offset /* negative */);\r
1304                 }\r
1305 \r
1306                 // Reset ParsePosition\r
1307                 pos.setIndex(start);\r
1308                 pos.setErrorIndex(-1);\r
1309 \r
1310                 // Try positive Hms\r
1311                 fmt = getGMTFormatter(DateFormatSymbols.OFFSET_POSITIVE, DateFormatSymbols.OFFSET_HMS);\r
1312                 parsedObjects = fmt.parse(text, pos);\r
1313                 if ((parsedObjects != null) && (parsedObjects[0] instanceof Date)\r
1314                         && (pos.getIndex() - start) >= getGMTFormatMinHMSLen(DateFormatSymbols.OFFSET_POSITIVE)) {\r
1315                     offset = (int)((Date)parsedObjects[0]).getTime();\r
1316                     return new Integer(offset);\r
1317                 }\r
1318 \r
1319                 // Reset ParsePosition\r
1320                 pos.setIndex(start);\r
1321                 pos.setErrorIndex(-1);\r
1322 \r
1323                 // Try negative Hm\r
1324                 fmt = getGMTFormatter(DateFormatSymbols.OFFSET_NEGATIVE, DateFormatSymbols.OFFSET_HM);\r
1325                 parsedObjects = fmt.parse(text, pos);\r
1326                 if ((parsedObjects != null) && (parsedObjects[0] instanceof Date)) {\r
1327                     offset = (int)((Date)parsedObjects[0]).getTime();\r
1328                     return new Integer(-offset /* negative */);\r
1329                 }\r
1330 \r
1331                 // Reset ParsePosition\r
1332                 pos.setIndex(start);\r
1333                 pos.setErrorIndex(-1);\r
1334 \r
1335                 // Try positive Hm\r
1336                 fmt = getGMTFormatter(DateFormatSymbols.OFFSET_POSITIVE, DateFormatSymbols.OFFSET_HM);\r
1337                 parsedObjects = fmt.parse(text, pos);\r
1338                 if ((parsedObjects != null) && (parsedObjects[0] instanceof Date)) {\r
1339                     offset = (int)((Date)parsedObjects[0]).getTime();\r
1340                     return new Integer(offset);\r
1341                 }\r
1342 \r
1343                 // Reset ParsePosition\r
1344                 pos.setIndex(start);\r
1345                 pos.setErrorIndex(-1);\r
1346             }\r
1347         }\r
1348 \r
1349         return parseGMTDefault(text, pos, currentNumberFormat);\r
1350     }\r
1351 \r
1352     private Integer parseGMTDefault(String text, ParsePosition pos, NumberFormat currentNumberFormat) {\r
1353         int start = pos.getIndex();\r
1354 \r
1355         if (start + STR_UT_LEN + 1 >= text.length()) {\r
1356             pos.setErrorIndex(start);\r
1357             return null;\r
1358         }\r
1359 \r
1360         int cur = start;\r
1361         // "GMT"\r
1362         if (text.regionMatches(true, start, STR_GMT, 0, STR_GMT_LEN)) {\r
1363             cur += STR_GMT_LEN;\r
1364         } else if (text.regionMatches(true, start, STR_UT, 0, STR_UT_LEN)) {\r
1365             cur += STR_UT_LEN;\r
1366         } else {\r
1367             pos.setErrorIndex(start);\r
1368             return null;\r
1369         }\r
1370         // Sign\r
1371         boolean negative = false;\r
1372         if (text.charAt(cur) == MINUS) {\r
1373             negative = true;\r
1374         } else if (text.charAt(cur) != PLUS) {\r
1375             pos.setErrorIndex(cur);\r
1376             return null;\r
1377         }\r
1378         cur++;\r
1379 \r
1380         // Numbers\r
1381         int numLen;\r
1382         pos.setIndex(cur);\r
1383 \r
1384         Number n = parseInt(text, 6, pos, false,currentNumberFormat);\r
1385         numLen = pos.getIndex() - cur;\r
1386 \r
1387         if (n == null || numLen <= 0 || numLen > 6) {\r
1388             pos.setIndex(start);\r
1389             pos.setErrorIndex(cur);\r
1390             return null;\r
1391         }\r
1392 \r
1393         int numVal = n.intValue();\r
1394 \r
1395         int hour = 0;\r
1396         int min = 0;\r
1397         int sec = 0;\r
1398 \r
1399         if (numLen <= 2) {\r
1400             // H[H][:mm[:ss]]\r
1401             hour = numVal;\r
1402             cur += numLen;\r
1403             if (cur + 2 < text.length() && text.charAt(cur) == COLON) {\r
1404                 cur++;\r
1405                 pos.setIndex(cur);\r
1406                 n = parseInt(text, 2, pos, false,currentNumberFormat);\r
1407                 numLen = pos.getIndex() - cur;\r
1408                 if (n != null && numLen == 2) {\r
1409                     // got minute field\r
1410                     min = n.intValue();\r
1411                     cur += numLen;\r
1412                     if (cur + 2 < text.length() && text.charAt(cur) == COLON) {\r
1413                         cur++;\r
1414                         pos.setIndex(cur);\r
1415                         n = parseInt(text, 2, pos, false,currentNumberFormat);\r
1416                         numLen = pos.getIndex() - cur;\r
1417                         if (n != null && numLen == 2) {\r
1418                             // got second field\r
1419                             sec = n.intValue();\r
1420                         } else {\r
1421                             // reset position\r
1422                             pos.setIndex(cur - 1);\r
1423                             pos.setErrorIndex(-1);\r
1424                         }\r
1425                     }\r
1426                 } else {\r
1427                     // reset postion\r
1428                     pos.setIndex(cur - 1);\r
1429                     pos.setErrorIndex(-1);\r
1430                 }\r
1431             }\r
1432         } else if (numLen == 3 || numLen == 4) {\r
1433             // Hmm or HHmm\r
1434             hour = numVal / 100;\r
1435             min = numVal % 100;\r
1436         } else { // numLen == 5 || numLen == 6\r
1437             // Hmmss or HHmmss\r
1438             hour = numVal / 10000;\r
1439             min = (numVal % 10000) / 100;\r
1440             sec = numVal % 100;\r
1441         }\r
1442 \r
1443         int offset = ((hour*60 + min)*60 + sec)*1000;\r
1444         if (negative) {\r
1445             offset = -offset;\r
1446         }\r
1447         return new Integer(offset);\r
1448     }\r
1449 \r
1450     transient private WeakReference[] gmtfmtCache;\r
1451 \r
1452     private MessageFormat getGMTFormatter(int sign, int width) {\r
1453         MessageFormat fmt = null;\r
1454         if (gmtfmtCache == null) {\r
1455             gmtfmtCache = new WeakReference[4];\r
1456         }\r
1457         int cacheIdx = sign*2 + width;\r
1458         if (gmtfmtCache[cacheIdx] != null) {\r
1459             fmt = (MessageFormat)gmtfmtCache[cacheIdx].get();\r
1460         }\r
1461         if (fmt == null) {\r
1462             fmt = new MessageFormat(formatData.gmtFormat);\r
1463             GregorianCalendar gcal = new GregorianCalendar(TimeZone.getTimeZone("Etc/UTC"));\r
1464             SimpleDateFormat sdf = (SimpleDateFormat)this.clone();\r
1465             sdf.setCalendar(gcal);\r
1466             sdf.applyPattern(formatData.getGmtHourFormat(sign, width));\r
1467             fmt.setFormat(0, sdf);\r
1468             gmtfmtCache[cacheIdx] = new WeakReference(fmt);\r
1469         }\r
1470         return fmt;\r
1471     }\r
1472 \r
1473     transient private int[] gmtFormatHmsMinLen = null;\r
1474 \r
1475     private int getGMTFormatMinHMSLen(int sign) {\r
1476         if (gmtFormatHmsMinLen == null) {\r
1477             gmtFormatHmsMinLen = new int[2];\r
1478             Long offset = new Long(60*60*1000); // 1 hour\r
1479 \r
1480             StringBuffer buf = new StringBuffer();\r
1481             MessageFormat fmtNeg = getGMTFormatter(DateFormatSymbols.OFFSET_NEGATIVE, DateFormatSymbols.OFFSET_HMS);\r
1482             fmtNeg.format(new Object[] {offset}, buf, null);\r
1483             gmtFormatHmsMinLen[0] = buf.length();\r
1484 \r
1485             buf.setLength(0);\r
1486             MessageFormat fmtPos = getGMTFormatter(DateFormatSymbols.OFFSET_POSITIVE, DateFormatSymbols.OFFSET_HMS);\r
1487             fmtPos.format(new Object[] {offset}, buf, null);\r
1488             gmtFormatHmsMinLen[1] = buf.length();\r
1489         }\r
1490         return gmtFormatHmsMinLen[(sign < 0 ? 0 : 1)];\r
1491     }\r
1492 \r
1493     private boolean isDefaultGMTFormat() {\r
1494         // GMT pattern\r
1495         if (!DateFormatSymbols.DEFAULT_GMT_PATTERN.equals(formatData.getGmtFormat())) {\r
1496             return false;\r
1497         }\r
1498         // GMT offset hour patters\r
1499         boolean res = true;\r
1500         for (int sign = 0; sign < 2 && res; sign++) {\r
1501             for (int width = 0; width < 2; width++) {\r
1502                 if (!DateFormatSymbols.DEFAULT_GMT_HOUR_PATTERNS[sign][width].equals(formatData.getGmtHourFormat(sign, width))) {\r
1503                     res = false;\r
1504                     break;\r
1505                 }\r
1506             }\r
1507         }\r
1508         return res;\r
1509     }\r
1510 \r
1511     /*\r
1512      * Internal method. Returns null if the value of an array is empty, or if the\r
1513      * index is out of bounds\r
1514      */\r
1515 /*    private String getZoneArrayValue(String[] zs, int ix) {\r
1516         if (ix >= 0 && ix < zs.length) {\r
1517             String result = zs[ix];\r
1518             if (result != null && result.length() != 0) {\r
1519                 return result;\r
1520             }\r
1521         }\r
1522         return null;\r
1523     }*/\r
1524 \r
1525     /**\r
1526      * Internal high-speed method.  Reuses a StringBuffer for results\r
1527      * instead of creating a String on the heap for each call.\r
1528      * @internal\r
1529      * @deprecated This API is ICU internal only.\r
1530      */\r
1531     protected void zeroPaddingNumber(NumberFormat nf,StringBuffer buf, int value,\r
1532                                      int minDigits, int maxDigits) {\r
1533         if (useLocalZeroPaddingNumberFormat) {\r
1534             fastZeroPaddingNumber(buf, value, minDigits, maxDigits);\r
1535         } else {\r
1536             nf.setMinimumIntegerDigits(minDigits);\r
1537             nf.setMaximumIntegerDigits(maxDigits);\r
1538             nf.format(value, buf, new FieldPosition(-1));\r
1539         }\r
1540     }\r
1541 \r
1542     /**\r
1543      * Overrides superclass method\r
1544      * @stable ICU 2.0\r
1545      */\r
1546     public void setNumberFormat(NumberFormat newNumberFormat) {\r
1547         // Override this method to update local zero padding number formatter\r
1548         super.setNumberFormat(newNumberFormat);\r
1549         initLocalZeroPaddingNumberFormat();\r
1550     }\r
1551 \r
1552     private void initLocalZeroPaddingNumberFormat() {\r
1553         if (numberFormat instanceof DecimalFormat) {\r
1554             zeroDigit = ((DecimalFormat)numberFormat).getDecimalFormatSymbols().getZeroDigit();\r
1555             useLocalZeroPaddingNumberFormat = true;\r
1556         } else if (numberFormat instanceof DateNumberFormat) {\r
1557             zeroDigit = ((DateNumberFormat)numberFormat).getZeroDigit();\r
1558             useLocalZeroPaddingNumberFormat = true;\r
1559         } else {\r
1560             useLocalZeroPaddingNumberFormat = false;\r
1561         }\r
1562 \r
1563         if (useLocalZeroPaddingNumberFormat) {\r
1564             decimalBuf = new char[10];  // sufficient for int numbers\r
1565         }\r
1566     }\r
1567 \r
1568     // If true, use local version of zero padding number format\r
1569     private transient boolean useLocalZeroPaddingNumberFormat;\r
1570     private transient char zeroDigit;\r
1571     private transient char[] decimalBuf;\r
1572 \r
1573     /*\r
1574      * Lightweight zero padding integer number format function.\r
1575      * \r
1576      * Note: This implementation is almost equivalent to format method in DateNumberFormat.\r
1577      * In the method zeroPaddingNumber above should be able to use the one in DateNumberFormat,\r
1578      * but, it does not help IBM J9's JIT to optimize the performance much.  In simple repeative\r
1579      * date format test case, having local implementation is ~10% faster than using one in\r
1580      * DateNumberFormat on IBM J9 VM.  On Sun Hotspot VM, I do not see such difference.\r
1581      * \r
1582      * -Yoshito\r
1583      */\r
1584     private void fastZeroPaddingNumber(StringBuffer buf, int value, int minDigits, int maxDigits) {\r
1585         int limit = decimalBuf.length < maxDigits ? decimalBuf.length : maxDigits;\r
1586         int index = limit - 1;\r
1587         while (true) {\r
1588             decimalBuf[index] = (char)((value % 10) + zeroDigit);\r
1589             value /= 10;\r
1590             if (index == 0 || value == 0) {\r
1591                 break;\r
1592             }\r
1593             index--;\r
1594         }\r
1595         int padding = minDigits - (limit - index);\r
1596         for (; padding > 0; padding--) {\r
1597             decimalBuf[--index] = zeroDigit;\r
1598         }\r
1599         int length = limit - index;\r
1600         buf.append(decimalBuf, index, length);        \r
1601     }\r
1602 \r
1603     /**\r
1604      * Formats a number with the specified minimum and maximum number of digits.\r
1605      * @stable ICU 2.0\r
1606      */\r
1607     protected String zeroPaddingNumber(long value, int minDigits, int maxDigits)\r
1608     {\r
1609         numberFormat.setMinimumIntegerDigits(minDigits);\r
1610         numberFormat.setMaximumIntegerDigits(maxDigits);\r
1611         return numberFormat.format(value);\r
1612     }\r
1613 \r
1614     /**\r
1615      * Format characters that indicate numeric fields.  The character\r
1616      * at index 0 is treated specially.\r
1617      */\r
1618     private static final String NUMERIC_FORMAT_CHARS = "MYyudehHmsSDFwWkK";\r
1619 \r
1620     /**\r
1621      * Return true if the given format character, occuring count\r
1622      * times, represents a numeric field.\r
1623      */\r
1624     private static final boolean isNumeric(char formatChar, int count) {\r
1625         int i = NUMERIC_FORMAT_CHARS.indexOf(formatChar);\r
1626         return (i > 0 || (i == 0 && count < 3));\r
1627     }\r
1628 \r
1629     /**\r
1630      * Overrides DateFormat\r
1631      * @see DateFormat\r
1632      * @stable ICU 2.0\r
1633      */\r
1634     public void parse(String text, Calendar cal, ParsePosition parsePos)\r
1635     {\r
1636         TimeZone backupTZ = null;\r
1637         Calendar resultCal = null;\r
1638         if (cal != calendar && !cal.getType().equals(calendar.getType())) {\r
1639             // Different calendar type\r
1640             // We use the time/zone from the input calendar, but\r
1641             // do not use the input calendar for field calculation.\r
1642             calendar.setTimeInMillis(cal.getTimeInMillis());\r
1643             backupTZ = calendar.getTimeZone();\r
1644             calendar.setTimeZone(cal.getTimeZone());\r
1645             resultCal = cal;\r
1646             cal = calendar;\r
1647         }\r
1648 \r
1649         int pos = parsePos.getIndex();\r
1650         int start = pos;\r
1651 \r
1652         // Reset tztype\r
1653         tztype = TZTYPE_UNK;\r
1654         boolean[] ambiguousYear = { false };\r
1655 \r
1656         // item index for the first numeric field within a contiguous numeric run\r
1657         int numericFieldStart = -1;\r
1658         // item length for the first numeric field within a contiguous numeric run\r
1659         int numericFieldLength = 0;\r
1660         // start index of numeric text run in the input text\r
1661         int numericStartPos = 0;\r
1662 \r
1663         Object[] items = getPatternItems();\r
1664         int i = 0;\r
1665         while (i < items.length) {\r
1666             if (items[i] instanceof PatternItem) {\r
1667                 // Handle pattern field\r
1668                 PatternItem field = (PatternItem)items[i];\r
1669                 if (field.isNumeric) {\r
1670                     // Handle fields within a run of abutting numeric fields.  Take\r
1671                     // the pattern "HHmmss" as an example. We will try to parse\r
1672                     // 2/2/2 characters of the input text, then if that fails,\r
1673                     // 1/2/2.  We only adjust the width of the leftmost field; the\r
1674                     // others remain fixed.  This allows "123456" => 12:34:56, but\r
1675                     // "12345" => 1:23:45.  Likewise, for the pattern "yyyyMMdd" we\r
1676                     // try 4/2/2, 3/2/2, 2/2/2, and finally 1/2/2.\r
1677                     if (numericFieldStart == -1) {\r
1678                         // check if this field is followed by abutting another numeric field\r
1679                         if ((i + 1) < items.length \r
1680                                 && (items[i + 1] instanceof PatternItem)\r
1681                                 && ((PatternItem)items[i + 1]).isNumeric) {\r
1682                             // record the first numeric field within a numeric text run\r
1683                             numericFieldStart = i;\r
1684                             numericFieldLength = field.length;\r
1685                             numericStartPos = pos; \r
1686                         }\r
1687                     }\r
1688                 }\r
1689                 if (numericFieldStart != -1) {\r
1690                     // Handle a numeric field within abutting numeric fields\r
1691                     int len = field.length;\r
1692                     if (numericFieldStart == i) {\r
1693                         len = numericFieldLength;\r
1694                     }\r
1695 \r
1696                     // Parse a numeric field\r
1697                     pos = subParse(text, pos, field.type, len,\r
1698                             true, false, ambiguousYear, cal);\r
1699 \r
1700                     if (pos < 0) {\r
1701                         // If the parse fails anywhere in the numeric run, back up to the\r
1702                         // start of the run and use shorter pattern length for the first\r
1703                         // numeric field.\r
1704                         --numericFieldLength;\r
1705                         if (numericFieldLength == 0) {\r
1706                             // can not make shorter any more\r
1707                             parsePos.setIndex(start);\r
1708                             parsePos.setErrorIndex(pos);\r
1709                             if (backupTZ != null) {\r
1710                                 calendar.setTimeZone(backupTZ);\r
1711                             }\r
1712                             return;\r
1713                         }\r
1714                         i = numericFieldStart;\r
1715                         pos = numericStartPos;\r
1716                         continue;\r
1717                     }\r
1718 \r
1719                 } else {\r
1720                     // Handle a non-numeric field or a non-abutting numeric field\r
1721                     numericFieldStart = -1;\r
1722 \r
1723                     int s = pos;\r
1724                     pos = subParse(text, pos, field.type, field.length,\r
1725                             false, true, ambiguousYear, cal);\r
1726                     if (pos < 0) {\r
1727                         parsePos.setIndex(start);\r
1728                         parsePos.setErrorIndex(s);\r
1729                         if (backupTZ != null) {\r
1730                             calendar.setTimeZone(backupTZ);\r
1731                         }\r
1732                         return;\r
1733                     }\r
1734                 }\r
1735             } else {\r
1736                 // Handle literal pattern text literal\r
1737                 numericFieldStart = -1;\r
1738 \r
1739                 String patl = (String)items[i];\r
1740                 int plen = patl.length();\r
1741                 int tlen = text.length();\r
1742                 int idx = 0;\r
1743                 while (idx < plen && pos < tlen) {\r
1744                     char pch = patl.charAt(idx);\r
1745                     char ich = text.charAt(pos);\r
1746                     if (UCharacterProperty.isRuleWhiteSpace(pch) && UCharacterProperty.isRuleWhiteSpace(ich)) {\r
1747                         // White space characters found in both patten and input.\r
1748                         // Skip contiguous white spaces.\r
1749                         while ((idx + 1) < plen &&\r
1750                                 UCharacterProperty.isRuleWhiteSpace(patl.charAt(idx + 1))) {\r
1751                              ++idx;\r
1752                         }\r
1753                         while ((pos + 1) < tlen &&\r
1754                                 UCharacterProperty.isRuleWhiteSpace(text.charAt(pos + 1))) {\r
1755                              ++pos;\r
1756                         }\r
1757                     } else if (pch != ich) {\r
1758                         break;\r
1759                     }\r
1760                     ++idx;\r
1761                     ++pos;\r
1762                 }\r
1763                 if (idx != plen) {\r
1764                     // Set the position of mismatch\r
1765                     parsePos.setIndex(start);\r
1766                     parsePos.setErrorIndex(pos);\r
1767                     if (backupTZ != null) {\r
1768                         calendar.setTimeZone(backupTZ);\r
1769                     }\r
1770                     return;\r
1771                 }\r
1772             }\r
1773             ++i;\r
1774         }\r
1775 \r
1776         // At this point the fields of Calendar have been set.  Calendar\r
1777         // will fill in default values for missing fields when the time\r
1778         // is computed.\r
1779 \r
1780         parsePos.setIndex(pos);\r
1781 \r
1782         // This part is a problem:  When we call parsedDate.after, we compute the time.\r
1783         // Take the date April 3 2004 at 2:30 am.  When this is first set up, the year\r
1784         // will be wrong if we're parsing a 2-digit year pattern.  It will be 1904.\r
1785         // April 3 1904 is a Sunday (unlike 2004) so it is the DST onset day.  2:30 am\r
1786         // is therefore an "impossible" time, since the time goes from 1:59 to 3:00 am\r
1787         // on that day.  It is therefore parsed out to fields as 3:30 am.  Then we\r
1788         // add 100 years, and get April 3 2004 at 3:30 am.  Note that April 3 2004 is\r
1789         // a Saturday, so it can have a 2:30 am -- and it should. [LIU]\r
1790         /*\r
1791           Date parsedDate = cal.getTime();\r
1792           if( ambiguousYear[0] && !parsedDate.after(getDefaultCenturyStart()) ) {\r
1793           cal.add(Calendar.YEAR, 100);\r
1794           parsedDate = cal.getTime();\r
1795           }\r
1796         */\r
1797         // Because of the above condition, save off the fields in case we need to readjust.\r
1798         // The procedure we use here is not particularly efficient, but there is no other\r
1799         // way to do this given the API restrictions present in Calendar.  We minimize\r
1800         // inefficiency by only performing this computation when it might apply, that is,\r
1801         // when the two-digit year is equal to the start year, and thus might fall at the\r
1802         // front or the back of the default century.  This only works because we adjust\r
1803         // the year correctly to start with in other cases -- see subParse().\r
1804         try {\r
1805             if (ambiguousYear[0] || tztype != TZTYPE_UNK) {\r
1806                 // We need a copy of the fields, and we need to avoid triggering a call to\r
1807                 // complete(), which will recalculate the fields.  Since we can't access\r
1808                 // the fields[] array in Calendar, we clone the entire object.  This will\r
1809                 // stop working if Calendar.clone() is ever rewritten to call complete().\r
1810                 Calendar copy;\r
1811                 if (ambiguousYear[0]) { // the two-digit year == the default start year\r
1812                     copy = (Calendar)cal.clone();\r
1813                     Date parsedDate = copy.getTime();\r
1814                     if (parsedDate.before(getDefaultCenturyStart())) {\r
1815                         // We can't use add here because that does a complete() first.\r
1816                         cal.set(Calendar.YEAR, getDefaultCenturyStartYear() + 100);\r
1817                     }\r
1818                 }\r
1819                 if (tztype != TZTYPE_UNK) {\r
1820                     copy = (Calendar)cal.clone();\r
1821                     TimeZone tz = copy.getTimeZone();\r
1822                     BasicTimeZone btz = null;\r
1823                     if (tz instanceof BasicTimeZone) {\r
1824                         btz = (BasicTimeZone)tz;\r
1825                     }\r
1826 \r
1827                     // Get local millis\r
1828                     copy.set(Calendar.ZONE_OFFSET, 0);\r
1829                     copy.set(Calendar.DST_OFFSET, 0);\r
1830                     long localMillis = copy.getTimeInMillis();\r
1831 \r
1832                     // Make sure parsed time zone type (Standard or Daylight)\r
1833                     // matches the rule used by the parsed time zone.\r
1834                     int[] offsets = new int[2];\r
1835                     if (btz != null) {\r
1836                         if (tztype == TZTYPE_STD) {\r
1837                             btz.getOffsetFromLocal(localMillis,\r
1838                                     BasicTimeZone.LOCAL_STD, BasicTimeZone.LOCAL_STD, offsets);\r
1839                         } else {\r
1840                             btz.getOffsetFromLocal(localMillis,\r
1841                                     BasicTimeZone.LOCAL_DST, BasicTimeZone.LOCAL_DST, offsets);\r
1842                         }\r
1843                     } else {\r
1844                         // No good way to resolve ambiguous time at transition,\r
1845                         // but following code work in most case.\r
1846                         tz.getOffset(localMillis, true, offsets);\r
1847 \r
1848                         if (tztype == TZTYPE_STD && offsets[1] != 0 || tztype == TZTYPE_DST && offsets[1] == 0) {\r
1849                             // Roll back one day and try it again.\r
1850                             // Note: This code assumes 1. timezone transition only happens once within 24 hours at max\r
1851                             // 2. the difference of local offsets at the transition is less than 24 hours.\r
1852                             tz.getOffset(localMillis - (24*60*60*1000), true, offsets);\r
1853                         }\r
1854                     }\r
1855 \r
1856                     // Now, compare the results with parsed type, either standard or daylight saving time\r
1857                     int resolvedSavings = offsets[1];\r
1858                     if (tztype == TZTYPE_STD) {\r
1859                         if (offsets[1] != 0) {\r
1860                             // Override DST_OFFSET = 0 in the result calendar\r
1861                             resolvedSavings = 0;\r
1862                         }\r
1863                     } else { // tztype == TZTYPE_DST\r
1864                         if (offsets[1] == 0) {\r
1865                             if (btz != null) {\r
1866                                 long time = localMillis + offsets[0];\r
1867                                 // We use the nearest daylight saving time rule.\r
1868                                 TimeZoneTransition beforeTrs, afterTrs;\r
1869                                 long beforeT = time, afterT = time;\r
1870                                 int beforeSav = 0, afterSav = 0;\r
1871 \r
1872                                 // Search for DST rule before or on the time\r
1873                                 while (true) {\r
1874                                     beforeTrs = btz.getPreviousTransition(beforeT, true);\r
1875                                     if (beforeTrs == null) {\r
1876                                         break;\r
1877                                     }\r
1878                                     beforeT = beforeTrs.getTime() - 1;\r
1879                                     beforeSav = beforeTrs.getFrom().getDSTSavings();\r
1880                                     if (beforeSav != 0) {\r
1881                                         break;\r
1882                                     }\r
1883                                 }\r
1884 \r
1885                                 // Search for DST rule after the time\r
1886                                 while (true) {\r
1887                                     afterTrs = btz.getNextTransition(afterT, false);\r
1888                                     if (afterTrs == null) {\r
1889                                         break;\r
1890                                     }\r
1891                                     afterT = afterTrs.getTime();\r
1892                                     afterSav = afterTrs.getTo().getDSTSavings();\r
1893                                     if (afterSav != 0) {\r
1894                                         break;\r
1895                                     }\r
1896                                 }\r
1897 \r
1898                                 if (beforeTrs != null && afterTrs != null) {\r
1899                                     if (time - beforeT > afterT - time) {\r
1900                                         resolvedSavings = afterSav;\r
1901                                     } else {\r
1902                                         resolvedSavings = beforeSav;\r
1903                                     }\r
1904                                 } else if (beforeTrs != null && beforeSav != 0) {\r
1905                                     resolvedSavings = beforeSav;\r
1906                                 } else if (afterTrs != null && afterSav != 0) {\r
1907                                     resolvedSavings = afterSav;\r
1908                                 } else {\r
1909                                     resolvedSavings = btz.getDSTSavings();\r
1910                                 }\r
1911                             } else {\r
1912                                 resolvedSavings = tz.getDSTSavings();\r
1913                             }\r
1914                             if (resolvedSavings == 0) {\r
1915                                 // Final fallback\r
1916                                 resolvedSavings = millisPerHour;\r
1917                             }\r
1918                         }\r
1919                     }\r
1920                     cal.set(Calendar.ZONE_OFFSET, offsets[0]);\r
1921                     cal.set(Calendar.DST_OFFSET, resolvedSavings);\r
1922                 }\r
1923             }\r
1924         }\r
1925         // An IllegalArgumentException will be thrown by Calendar.getTime()\r
1926         // if any fields are out of range, e.g., MONTH == 17.\r
1927         catch (IllegalArgumentException e) {\r
1928             parsePos.setErrorIndex(pos);\r
1929             parsePos.setIndex(start);\r
1930             if (backupTZ != null) {\r
1931                 calendar.setTimeZone(backupTZ);\r
1932             }\r
1933             return;\r
1934         }\r
1935         // Set the parsed result if local calendar is used\r
1936         // instead of the input calendar\r
1937         if (resultCal != null) {\r
1938             resultCal.setTimeZone(cal.getTimeZone());\r
1939             resultCal.setTimeInMillis(cal.getTimeInMillis());\r
1940         }\r
1941         // Restore the original time zone if required\r
1942         if (backupTZ != null) {\r
1943             calendar.setTimeZone(backupTZ);\r
1944         }\r
1945     }\r
1946 \r
1947     /**\r
1948      * Attempt to match the text at a given position against an array of\r
1949      * strings.  Since multiple strings in the array may match (for\r
1950      * example, if the array contains "a", "ab", and "abc", all will match\r
1951      * the input string "abcd") the longest match is returned.  As a side\r
1952      * effect, the given field of <code>cal</code> is set to the index\r
1953      * of the best match, if there is one.\r
1954      * @param text the time text being parsed.\r
1955      * @param start where to start parsing.\r
1956      * @param field the date field being parsed.\r
1957      * @param data the string array to parsed.\r
1958      * @return the new start position if matching succeeded; a negative\r
1959      * number indicating matching failure, otherwise.  As a side effect,\r
1960      * sets the <code>cal</code> field <code>field</code> to the index\r
1961      * of the best match, if matching succeeded.\r
1962      * @stable ICU 2.0\r
1963      */\r
1964     protected int matchString(String text, int start, int field, String[] data, Calendar cal)\r
1965     {\r
1966         int i = 0;\r
1967         int count = data.length;\r
1968 \r
1969         if (field == Calendar.DAY_OF_WEEK) i = 1;\r
1970 \r
1971         // There may be multiple strings in the data[] array which begin with\r
1972         // the same prefix (e.g., Cerven and Cervenec (June and July) in Czech).\r
1973         // We keep track of the longest match, and return that.  Note that this\r
1974         // unfortunately requires us to test all array elements.\r
1975         int bestMatchLength = 0, bestMatch = -1;\r
1976         for (; i<count; ++i)\r
1977             {\r
1978                 int length = data[i].length();\r
1979                 // Always compare if we have no match yet; otherwise only compare\r
1980                 // against potentially better matches (longer strings).\r
1981                 if (length > bestMatchLength &&\r
1982                     text.regionMatches(true, start, data[i], 0, length))\r
1983                     {\r
1984                         bestMatch = i;\r
1985                         bestMatchLength = length;\r
1986                     }\r
1987             }\r
1988         if (bestMatch >= 0)\r
1989             {\r
1990                 cal.set(field, bestMatch);\r
1991                 return start + bestMatchLength;\r
1992             }\r
1993         return -start;\r
1994     }\r
1995 \r
1996     /**\r
1997      * Attempt to match the text at a given position against an array of quarter\r
1998      * strings.  Since multiple strings in the array may match (for\r
1999      * example, if the array contains "a", "ab", and "abc", all will match\r
2000      * the input string "abcd") the longest match is returned.  As a side\r
2001      * effect, the given field of <code>cal</code> is set to the index\r
2002      * of the best match, if there is one.\r
2003      * @param text the time text being parsed.\r
2004      * @param start where to start parsing.\r
2005      * @param field the date field being parsed.\r
2006      * @param data the string array to parsed.\r
2007      * @return the new start position if matching succeeded; a negative\r
2008      * number indicating matching failure, otherwise.  As a side effect,\r
2009      * sets the <code>cal</code> field <code>field</code> to the index\r
2010      * of the best match, if matching succeeded.\r
2011      * @stable ICU 2.0\r
2012      */\r
2013     protected int matchQuarterString(String text, int start, int field, String[] data, Calendar cal)\r
2014     {\r
2015         int i = 0;\r
2016         int count = data.length;\r
2017 \r
2018         // There may be multiple strings in the data[] array which begin with\r
2019         // the same prefix (e.g., Cerven and Cervenec (June and July) in Czech).\r
2020         // We keep track of the longest match, and return that.  Note that this\r
2021         // unfortunately requires us to test all array elements.\r
2022         int bestMatchLength = 0, bestMatch = -1;\r
2023         for (; i<count; ++i) {\r
2024             int length = data[i].length();\r
2025             // Always compare if we have no match yet; otherwise only compare\r
2026             // against potentially better matches (longer strings).\r
2027             if (length > bestMatchLength &&\r
2028                 text.regionMatches(true, start, data[i], 0, length)) {\r
2029                 bestMatch = i;\r
2030                 bestMatchLength = length;\r
2031             }\r
2032         }\r
2033         \r
2034         if (bestMatch >= 0) {\r
2035             cal.set(field, bestMatch * 3);\r
2036             return start + bestMatchLength;\r
2037         }\r
2038         \r
2039         return -start;\r
2040     }\r
2041     \r
2042     /**\r
2043      * Protected method that converts one field of the input string into a\r
2044      * numeric field value in <code>cal</code>.  Returns -start (for\r
2045      * ParsePosition) if failed.  Subclasses may override this method to\r
2046      * modify or add parsing capabilities.\r
2047      * @param text the time text to be parsed.\r
2048      * @param start where to start parsing.\r
2049      * @param ch the pattern character for the date field text to be parsed.\r
2050      * @param count the count of a pattern character.\r
2051      * @param obeyCount if true, then the next field directly abuts this one,\r
2052      * and we should use the count to know when to stop parsing.\r
2053      * @param ambiguousYear return parameter; upon return, if ambiguousYear[0]\r
2054      * is true, then a two-digit year was parsed and may need to be readjusted.\r
2055      * @return the new start position if matching succeeded; a negative\r
2056      * number indicating matching failure, otherwise.  As a side effect,\r
2057      * set the appropriate field of <code>cal</code> with the parsed\r
2058      * value.\r
2059      * @stable ICU 2.0\r
2060      */\r
2061     protected int subParse(String text, int start, char ch, int count,\r
2062                            boolean obeyCount, boolean allowNegative,\r
2063                            boolean[] ambiguousYear, Calendar cal)\r
2064     {\r
2065         Number number = null;\r
2066         NumberFormat currentNumberFormat = null;\r
2067         int value = 0;\r
2068         int i;\r
2069         ParsePosition pos = new ParsePosition(0);\r
2070         //int patternCharIndex = DateFormatSymbols.patternChars.indexOf(ch);c\r
2071         int patternCharIndex = -1;\r
2072         if ('A' <= ch && ch <= 'z') {\r
2073             patternCharIndex = PATTERN_CHAR_TO_INDEX[(int)ch - PATTERN_CHAR_BASE];\r
2074         }\r
2075 \r
2076         if (patternCharIndex == -1) {\r
2077             return -start;\r
2078         }\r
2079 \r
2080         currentNumberFormat = getNumberFormat(ch);\r
2081  \r
2082         int field = PATTERN_INDEX_TO_CALENDAR_FIELD[patternCharIndex];\r
2083 \r
2084         // If there are any spaces here, skip over them.  If we hit the end\r
2085         // of the string, then fail.\r
2086         for (;;) {\r
2087             if (start >= text.length()) {\r
2088                 return -start;\r
2089             }\r
2090             int c = UTF16.charAt(text, start);\r
2091             if (!UCharacter.isUWhiteSpace(c)) {\r
2092                 break;\r
2093             }\r
2094             start += UTF16.getCharCount(c);\r
2095         }\r
2096         pos.setIndex(start);\r
2097 \r
2098         // We handle a few special cases here where we need to parse\r
2099         // a number value.  We handle further, more generic cases below.  We need\r
2100         // to handle some of them here because some fields require extra processing on\r
2101         // the parsed value.\r
2102         if (patternCharIndex == 4 /*HOUR_OF_DAY1_FIELD*/ ||\r
2103             patternCharIndex == 15 /*HOUR1_FIELD*/ ||\r
2104             (patternCharIndex == 2 /*MONTH_FIELD*/ && count <= 2) ||\r
2105             patternCharIndex == 1 ||\r
2106             patternCharIndex == 8)\r
2107             {\r
2108                 // It would be good to unify this with the obeyCount logic below,\r
2109                 // but that's going to be difficult.\r
2110                 if (obeyCount)\r
2111                     {\r
2112                         if ((start+count) > text.length()) return -start;\r
2113                         number = parseInt(text, count, pos, allowNegative,currentNumberFormat);\r
2114                     }\r
2115                 else number = parseInt(text, pos, allowNegative,currentNumberFormat);\r
2116                 if (number == null)\r
2117                     return -start;\r
2118                 value = number.intValue();\r
2119             }\r
2120 \r
2121         switch (patternCharIndex)\r
2122             {\r
2123             case 0: // 'G' - ERA\r
2124                 if (count == 4) {\r
2125                     return matchString(text, start, Calendar.ERA, formatData.eraNames, cal);\r
2126                 } else {\r
2127                     return matchString(text, start, Calendar.ERA, formatData.eras, cal);\r
2128                 }\r
2129             case 1: // 'y' - YEAR\r
2130                 // If there are 3 or more YEAR pattern characters, this indicates\r
2131                 // that the year value is to be treated literally, without any\r
2132                 // two-digit year adjustments (e.g., from "01" to 2001).  Otherwise\r
2133                 // we made adjustments to place the 2-digit year in the proper\r
2134                 // century, for parsed strings from "00" to "99".  Any other string\r
2135                 // is treated literally:  "2250", "-1", "1", "002".\r
2136                 /* 'yy' is the only special case, 'y' is interpreted as number. [Richard/GCL]*/\r
2137                 if (count == 2 && (pos.getIndex() - start) == 2\r
2138                     && UCharacter.isDigit(text.charAt(start))\r
2139                     && UCharacter.isDigit(text.charAt(start+1)))\r
2140                     {\r
2141                         // Assume for example that the defaultCenturyStart is 6/18/1903.\r
2142                         // This means that two-digit years will be forced into the range\r
2143                         // 6/18/1903 to 6/17/2003.  As a result, years 00, 01, and 02\r
2144                         // correspond to 2000, 2001, and 2002.  Years 04, 05, etc. correspond\r
2145                         // to 1904, 1905, etc.  If the year is 03, then it is 2003 if the\r
2146                         // other fields specify a date before 6/18, or 1903 if they specify a\r
2147                         // date afterwards.  As a result, 03 is an ambiguous year.  All other\r
2148                         // two-digit years are unambiguous.\r
2149                         int ambiguousTwoDigitYear = getDefaultCenturyStartYear() % 100;\r
2150                         ambiguousYear[0] = value == ambiguousTwoDigitYear;\r
2151                         value += (getDefaultCenturyStartYear()/100)*100 +\r
2152                             (value < ambiguousTwoDigitYear ? 100 : 0);\r
2153                     }\r
2154                 cal.set(Calendar.YEAR, value);\r
2155                 return pos.getIndex();\r
2156             case 2: // 'M' - MONTH\r
2157                 if (count <= 2) // i.e., M or MM.\r
2158                     {\r
2159                         // Don't want to parse the month if it is a string\r
2160                         // while pattern uses numeric style: M or MM.\r
2161                         // [We computed 'value' above.]\r
2162                         cal.set(Calendar.MONTH, value - 1);\r
2163                         return pos.getIndex();\r
2164                     }\r
2165                 else\r
2166                     {\r
2167                         // count >= 3 // i.e., MMM or MMMM\r
2168                         // Want to be able to parse both short and long forms.\r
2169                         // Try count == 4 first:\r
2170                         int newStart = matchString(text, start, Calendar.MONTH,\r
2171                                                    formatData.months, cal);\r
2172                         if (newStart > 0) {\r
2173                             return newStart;\r
2174                         } else { // count == 4 failed, now try count == 3\r
2175                             return matchString(text, start, Calendar.MONTH,\r
2176                                                formatData.shortMonths, cal);\r
2177                         }\r
2178                     }\r
2179             case 26: // 'L' - STAND_ALONE_MONTH\r
2180                 if (count <= 2) // i.e., M or MM.\r
2181                     {\r
2182                         // Don't want to parse the month if it is a string\r
2183                         // while pattern uses numeric style: M or MM.\r
2184                         // [We computed 'value' above.]\r
2185                         cal.set(Calendar.MONTH, value - 1);\r
2186                         return pos.getIndex();\r
2187                     }\r
2188                 else\r
2189                     {\r
2190                         // count >= 3 // i.e., MMM or MMMM\r
2191                         // Want to be able to parse both short and long forms.\r
2192                         // Try count == 4 first:\r
2193                         int newStart = matchString(text, start, Calendar.MONTH,\r
2194                                                    formatData.standaloneMonths, cal);\r
2195                         if (newStart > 0) {\r
2196                             return newStart;\r
2197                         } else { // count == 4 failed, now try count == 3\r
2198                             return matchString(text, start, Calendar.MONTH,\r
2199                                                formatData.standaloneShortMonths, cal);\r
2200                         }\r
2201                     }\r
2202             case 4: // 'k' - HOUR_OF_DAY (1..24)\r
2203                 // [We computed 'value' above.]\r
2204                 if (value == cal.getMaximum(Calendar.HOUR_OF_DAY)+1) value = 0;\r
2205                 cal.set(Calendar.HOUR_OF_DAY, value);\r
2206                 return pos.getIndex();\r
2207             case 8: // 'S' - FRACTIONAL_SECOND\r
2208                 // Fractional seconds left-justify\r
2209                 i = pos.getIndex() - start;\r
2210                 if (i < 3) {\r
2211                     while (i < 3) {\r
2212                         value *= 10;\r
2213                         i++;\r
2214                     }\r
2215                 } else {\r
2216                     int a = 1;\r
2217                     while (i > 3) {\r
2218                         a *= 10;\r
2219                         i--;\r
2220                     }\r
2221                     value = (value + (a>>1)) / a;\r
2222                 }\r
2223                 cal.set(Calendar.MILLISECOND, value);\r
2224                 return pos.getIndex();\r
2225             case 9: { // 'E' - DAY_OF_WEEK\r
2226                 // Want to be able to parse both short and long forms.\r
2227                 // Try count == 4 (EEEE) first:\r
2228                 int newStart = matchString(text, start, Calendar.DAY_OF_WEEK,\r
2229                                            formatData.weekdays, cal);\r
2230                 if (newStart > 0) {\r
2231                     return newStart;\r
2232                 } else { // EEEE failed, now try EEE\r
2233                     return matchString(text, start, Calendar.DAY_OF_WEEK,\r
2234                                        formatData.shortWeekdays, cal);\r
2235                 }\r
2236             }\r
2237             case 25: { // 'c' - STAND_ALONE_DAY_OF_WEEK\r
2238                 // Want to be able to parse both short and long forms.\r
2239                 // Try count == 4 (cccc) first:\r
2240                 int newStart = matchString(text, start, Calendar.DAY_OF_WEEK,\r
2241                                            formatData.standaloneWeekdays, cal);\r
2242                 if (newStart > 0) {\r
2243                     return newStart;\r
2244                 } else { // cccc failed, now try ccc\r
2245                     return matchString(text, start, Calendar.DAY_OF_WEEK,\r
2246                                        formatData.standaloneShortWeekdays, cal);\r
2247                 }\r
2248             }\r
2249             case 14: // 'a' - AM_PM\r
2250                 return matchString(text, start, Calendar.AM_PM, formatData.ampms, cal);\r
2251             case 15: // 'h' - HOUR (1..12)\r
2252                 // [We computed 'value' above.]\r
2253                 if (value == cal.getLeastMaximum(Calendar.HOUR)+1) value = 0;\r
2254                 cal.set(Calendar.HOUR, value);\r
2255                 return pos.getIndex();\r
2256             case 17: // 'z' - ZONE_OFFSET\r
2257             case 23: // 'Z' - TIMEZONE_RFC\r
2258             case 24: // 'v' - TIMEZONE_GENERIC\r
2259             case 29: // 'V' - TIMEZONE_SPECIAL\r
2260                 {\r
2261                     TimeZone tz = null;\r
2262                     int offset = 0;\r
2263                     boolean parsed = false;\r
2264 \r
2265                     // Step 1\r
2266                     // Check if this is a long GMT offset string (either localized or default)\r
2267                     Integer gmtoff = parseGMT(text, pos, currentNumberFormat);\r
2268                     if (gmtoff != null) {\r
2269                         offset = gmtoff.intValue();\r
2270                         parsed = true;\r
2271                     }\r
2272 \r
2273                     if (!parsed) {\r
2274                         // Step 2\r
2275                         // Check if this is an RFC822 time zone offset.\r
2276                         // ICU supports the standard RFC822 format [+|-]HHmm\r
2277                         // and its extended form [+|-]HHmmSS.\r
2278                         \r
2279                         do {\r
2280                             int sign = 0;\r
2281                             char signChar = text.charAt(start);\r
2282                             if (signChar == '+') {\r
2283                                 sign = 1;\r
2284                             } else if (signChar == '-') {\r
2285                                 sign = -1;\r
2286                             } else {\r
2287                                 // Not an RFC822 offset string\r
2288                                 break;\r
2289                             }\r
2290 \r
2291                             // Parse digits\r
2292                             int orgPos = start + 1;\r
2293                             pos.setIndex(orgPos);\r
2294                             number = parseInt(text, 6, pos, false,currentNumberFormat);\r
2295                             int numLen = pos.getIndex() - orgPos;\r
2296                             if (numLen <= 0) {\r
2297                                 break;\r
2298                             }\r
2299 \r
2300                             // Followings are possible format (excluding sign char)\r
2301                             // HHmmSS\r
2302                             // HmmSS\r
2303                             // HHmm\r
2304                             // Hmm\r
2305                             // HH\r
2306                             // H\r
2307                             int val = number.intValue();\r
2308                             int hour = 0, min = 0, sec = 0;\r
2309                             switch(numLen) {\r
2310                             case 1: // H\r
2311                             case 2: // HH\r
2312                                 hour = val;\r
2313                                 break;\r
2314                             case 3: // Hmm\r
2315                             case 4: // HHmm\r
2316                                 hour = val / 100;\r
2317                                 min = val % 100;\r
2318                                 break;\r
2319                             case 5: // Hmmss\r
2320                             case 6: // HHmmss\r
2321                                 hour = val / 10000;\r
2322                                 min = (val % 10000) / 100;\r
2323                                 sec = val % 100;\r
2324                                 break;\r
2325                             }\r
2326                             if (hour > 23 || min > 59 || sec > 59) {\r
2327                                 // Invalid value range\r
2328                                 break;\r
2329                             }\r
2330                             offset = (((hour * 60) + min) * 60 + sec) * 1000 * sign;\r
2331                             parsed = true;\r
2332                         } while (false);\r
2333 \r
2334                         if (!parsed) {\r
2335                             // Failed to parse.  Reset the position.\r
2336                             pos.setIndex(start);\r
2337                         }\r
2338                     }\r
2339 \r
2340                     if (parsed) {\r
2341                         // offset was successfully parsed as either a long GMT string or RFC822 zone offset\r
2342                         // string.  Create normalized zone ID for the offset.\r
2343                         tz = ZoneMeta.getCustomTimeZone(offset);\r
2344                         cal.setTimeZone(tz);\r
2345                         return pos.getIndex();\r
2346                     }\r
2347 \r
2348                     // Step 3\r
2349                     // At this point, check for named time zones by looking through\r
2350                     // the locale data from the DateFormatZoneData strings.\r
2351                     // Want to be able to parse both short and long forms.\r
2352                     // optimize for calendar's current time zone\r
2353                     ZoneStringInfo zsinfo = null;\r
2354                     switch (patternCharIndex) {\r
2355                     case 17: // 'z' - ZONE_OFFSET\r
2356                         if (count < 4) {\r
2357                             zsinfo = formatData.getZoneStringFormat().findSpecificShort(text, start);\r
2358                         } else {\r
2359                             zsinfo = formatData.getZoneStringFormat().findSpecificLong(text, start);\r
2360                         }\r
2361                         break;\r
2362                     case 24: // 'v' - TIMEZONE_GENERIC\r
2363                         if (count == 1) {\r
2364                             zsinfo = formatData.getZoneStringFormat().findGenericShort(text, start);\r
2365                         } else if (count == 4) {\r
2366                             zsinfo = formatData.getZoneStringFormat().findGenericLong(text, start);\r
2367                         }\r
2368                         break;\r
2369                     case 29: // 'V' - TIMEZONE_SPECIAL\r
2370                         if (count == 1) {\r
2371                             zsinfo = formatData.getZoneStringFormat().findSpecificShort(text, start);\r
2372                         } else if (count == 4) {\r
2373                             zsinfo = formatData.getZoneStringFormat().findGenericLocation(text, start);\r
2374                         }\r
2375                         break;\r
2376                     }\r
2377                     if (zsinfo != null) {\r
2378                         if (zsinfo.isStandard()) {\r
2379                             tztype = TZTYPE_STD;\r
2380                         } else if (zsinfo.isDaylight()) {\r
2381                             tztype = TZTYPE_DST;\r
2382                         }\r
2383                         tz = TimeZone.getTimeZone(zsinfo.getID());\r
2384                         cal.setTimeZone(tz);\r
2385                         return start + zsinfo.getString().length();\r
2386                     }\r
2387                     // Step 4\r
2388                     // Final attempt - is this standalone GMT/UT/UTC?\r
2389                     int gmtLen = 0;\r
2390                     if (text.regionMatches(true, start, STR_GMT, 0, STR_GMT_LEN)) {\r
2391                         gmtLen = STR_GMT_LEN;\r
2392                     } else if (text.regionMatches(true, start, STR_UTC, 0, STR_UTC_LEN)) {\r
2393                         gmtLen = STR_UTC_LEN;\r
2394                     } else if (text.regionMatches(true, start, STR_UT, 0, STR_UT_LEN)) {\r
2395                         gmtLen = STR_UT_LEN;\r
2396                     }\r
2397                     if (gmtLen > 0) {\r
2398                         tz = TimeZone.getTimeZone("Etc/GMT");\r
2399                         cal.setTimeZone(tz);\r
2400                         return start + gmtLen;\r
2401                     }\r
2402 \r
2403                     // complete failure\r
2404                     return -start;\r
2405                 }\r
2406 \r
2407             case 27: // 'Q' - QUARTER\r
2408                 if (count <= 2) // i.e., Q or QQ.\r
2409                 {\r
2410                     // Don't want to parse the quarter if it is a string\r
2411                     // while pattern uses numeric style: Q or QQ.\r
2412                     // [We computed 'value' above.]\r
2413                     cal.set(Calendar.MONTH, (value - 1) * 3);\r
2414                     return pos.getIndex();\r
2415                 }\r
2416             else\r
2417                 {\r
2418                     // count >= 3 // i.e., QQQ or QQQQ\r
2419                     // Want to be able to parse both short and long forms.\r
2420                     // Try count == 4 first:\r
2421                     int newStart = matchQuarterString(text, start, Calendar.MONTH,\r
2422                                                formatData.quarters, cal);\r
2423                     if (newStart > 0) {\r
2424                         return newStart;\r
2425                     } else { // count == 4 failed, now try count == 3\r
2426                         return matchQuarterString(text, start, Calendar.MONTH,\r
2427                                            formatData.shortQuarters, cal);\r
2428                     }\r
2429                 }\r
2430                 \r
2431             case 28: // 'q' - STANDALONE QUARTER\r
2432                 if (count <= 2) // i.e., q or qq.\r
2433                 {\r
2434                     // Don't want to parse the quarter if it is a string\r
2435                     // while pattern uses numeric style: q or qq.\r
2436                     // [We computed 'value' above.]\r
2437                     cal.set(Calendar.MONTH, (value - 1) * 3);\r
2438                     return pos.getIndex();\r
2439                 }\r
2440             else\r
2441                 {\r
2442                     // count >= 3 // i.e., qqq or qqqq\r
2443                     // Want to be able to parse both short and long forms.\r
2444                     // Try count == 4 first:\r
2445                     int newStart = matchQuarterString(text, start, Calendar.MONTH,\r
2446                                                formatData.standaloneQuarters, cal);\r
2447                     if (newStart > 0) {\r
2448                         return newStart;\r
2449                     } else { // count == 4 failed, now try count == 3\r
2450                         return matchQuarterString(text, start, Calendar.MONTH,\r
2451                                            formatData.standaloneShortQuarters, cal);\r
2452                     }\r
2453                 }\r
2454 \r
2455             default:\r
2456                 // case 3: // 'd' - DATE\r
2457                 // case 5: // 'H' - HOUR_OF_DAY (0..23)\r
2458                 // case 6: // 'm' - MINUTE\r
2459                 // case 7: // 's' - SECOND\r
2460                 // case 10: // 'D' - DAY_OF_YEAR\r
2461                 // case 11: // 'F' - DAY_OF_WEEK_IN_MONTH\r
2462                 // case 12: // 'w' - WEEK_OF_YEAR\r
2463                 // case 13: // 'W' - WEEK_OF_MONTH\r
2464                 // case 16: // 'K' - HOUR (0..11)\r
2465                 // case 18: // 'Y' - YEAR_WOY\r
2466                 // case 19: // 'e' - DOW_LOCAL\r
2467                 // case 20: // 'u' - EXTENDED_YEAR\r
2468                 // case 21: // 'g' - JULIAN_DAY\r
2469                 // case 22: // 'A' - MILLISECONDS_IN_DAY\r
2470 \r
2471                 // Handle "generic" fields\r
2472                 if (obeyCount)\r
2473                     {\r
2474                         if ((start+count) > text.length()) return -start;\r
2475                         number = parseInt(text, count, pos, allowNegative,currentNumberFormat);\r
2476                     }\r
2477                 else number = parseInt(text, pos, allowNegative,currentNumberFormat);\r
2478                 if (number != null) {\r
2479                     cal.set(field, number.intValue());\r
2480                     return pos.getIndex();\r
2481                 }\r
2482                 return -start;\r
2483             }\r
2484     }\r
2485 \r
2486     /**\r
2487      * Parse an integer using numberFormat.  This method is semantically\r
2488      * const, but actually may modify fNumberFormat.\r
2489      */\r
2490     private Number parseInt(String text,\r
2491                             ParsePosition pos,\r
2492                             boolean allowNegative,\r
2493                             NumberFormat fmt) {\r
2494         return parseInt(text, -1, pos, allowNegative, fmt);\r
2495     }\r
2496     \r
2497     /**\r
2498      * Parse an integer using numberFormat up to maxDigits.\r
2499      */\r
2500     private Number parseInt(String text,\r
2501                             int maxDigits,\r
2502                             ParsePosition pos,\r
2503                             boolean allowNegative,\r
2504                             NumberFormat fmt) {\r
2505         Number number;\r
2506         int oldPos = pos.getIndex();\r
2507         if (allowNegative) {\r
2508             number = fmt.parse(text, pos);\r
2509         } else {\r
2510             // Invalidate negative numbers\r
2511             if (fmt instanceof DecimalFormat) {\r
2512                 String oldPrefix = ((DecimalFormat)fmt).getNegativePrefix();\r
2513                 ((DecimalFormat)fmt).setNegativePrefix(SUPPRESS_NEGATIVE_PREFIX);\r
2514                 number = fmt.parse(text, pos);\r
2515                 ((DecimalFormat)fmt).setNegativePrefix(oldPrefix);\r
2516             } else {\r
2517                 boolean dateNumberFormat = (fmt instanceof DateNumberFormat);\r
2518                 if (dateNumberFormat) {\r
2519                     ((DateNumberFormat)fmt).setParsePositiveOnly(true);\r
2520                 }\r
2521                 number = fmt.parse(text, pos);                \r
2522                 if (dateNumberFormat) {\r
2523                     ((DateNumberFormat)fmt).setParsePositiveOnly(false);\r
2524                 }\r
2525             }\r
2526         }\r
2527         if (maxDigits > 0) {\r
2528             // adjust the result to fit into\r
2529             // the maxDigits and move the position back\r
2530             int nDigits = pos.getIndex() - oldPos;\r
2531             if (nDigits > maxDigits) {\r
2532                 double val = number.doubleValue();\r
2533                 nDigits -= maxDigits;\r
2534                 while (nDigits > 0) {\r
2535                     val /= 10;\r
2536                     nDigits--;\r
2537                 }\r
2538                 pos.setIndex(oldPos + maxDigits);\r
2539                 number = new Integer((int)val);\r
2540             }\r
2541         }\r
2542         return number;\r
2543     }\r
2544 \r
2545     \r
2546     /**\r
2547      * Translate a pattern, mapping each character in the from string to the\r
2548      * corresponding character in the to string.\r
2549      */\r
2550     private String translatePattern(String pat, String from, String to) {\r
2551         StringBuffer result = new StringBuffer();\r
2552         boolean inQuote = false;\r
2553         for (int i = 0; i < pat.length(); ++i) {\r
2554             char c = pat.charAt(i);\r
2555             if (inQuote) {\r
2556                 if (c == '\'')\r
2557                     inQuote = false;\r
2558             }\r
2559             else {\r
2560                 if (c == '\'')\r
2561                     inQuote = true;\r
2562                 else if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) {\r
2563                     int ci = from.indexOf(c);\r
2564                     if (ci != -1) {\r
2565                         c = to.charAt(ci);\r
2566                     }\r
2567                     // do not worry on translatepattern if the character is not listed\r
2568                     // we do the validity check elsewhere\r
2569                 }\r
2570             }\r
2571             result.append(c);\r
2572         }\r
2573         if (inQuote)\r
2574             throw new IllegalArgumentException("Unfinished quote in pattern");\r
2575         return result.toString();\r
2576     }\r
2577 \r
2578     /**\r
2579      * Return a pattern string describing this date format.\r
2580      * @stable ICU 2.0\r
2581      */\r
2582     public String toPattern() {\r
2583         return pattern;\r
2584     }\r
2585 \r
2586     /**\r
2587      * Return a localized pattern string describing this date format.\r
2588      * @stable ICU 2.0\r
2589      */\r
2590     public String toLocalizedPattern() {\r
2591         return translatePattern(pattern,\r
2592                                 DateFormatSymbols.patternChars,\r
2593                                 formatData.localPatternChars);\r
2594     }\r
2595 \r
2596     /**\r
2597      * Apply the given unlocalized pattern string to this date format.\r
2598      * @stable ICU 2.0\r
2599      */\r
2600     public void applyPattern(String pat)\r
2601     {\r
2602         this.pattern = pat;\r
2603         setLocale(null, null);\r
2604         // reset parsed pattern items\r
2605         patternItems = null;\r
2606     }\r
2607 \r
2608     /**\r
2609      * Apply the given localized pattern string to this date format.\r
2610      * @stable ICU 2.0\r
2611      */\r
2612     public void applyLocalizedPattern(String pat) {\r
2613         this.pattern = translatePattern(pat,\r
2614                                         formatData.localPatternChars,\r
2615                                         DateFormatSymbols.patternChars);\r
2616         setLocale(null, null);\r
2617     }\r
2618 \r
2619     /**\r
2620      * Gets the date/time formatting data.\r
2621      * @return a copy of the date-time formatting data associated\r
2622      * with this date-time formatter.\r
2623      * @stable ICU 2.0\r
2624      */\r
2625     public DateFormatSymbols getDateFormatSymbols()\r
2626     {\r
2627         return (DateFormatSymbols)formatData.clone();\r
2628     }\r
2629 \r
2630     /**\r
2631      * Allows you to set the date/time formatting data.\r
2632      * @param newFormatSymbols the new symbols\r
2633      * @stable ICU 2.0\r
2634      */\r
2635     public void setDateFormatSymbols(DateFormatSymbols newFormatSymbols)\r
2636     {\r
2637         this.formatData = (DateFormatSymbols)newFormatSymbols.clone();\r
2638         gmtfmtCache = null;\r
2639     }\r
2640 \r
2641     /**\r
2642      * Method for subclasses to access the DateFormatSymbols.\r
2643      * @stable ICU 2.0\r
2644      */\r
2645     protected DateFormatSymbols getSymbols() {\r
2646         return formatData;\r
2647     }\r
2648 \r
2649     /**\r
2650      * Overrides Cloneable\r
2651      * @stable ICU 2.0\r
2652      */\r
2653     public Object clone() {\r
2654         SimpleDateFormat other = (SimpleDateFormat) super.clone();\r
2655         other.formatData = (DateFormatSymbols) formatData.clone();\r
2656         return other;\r
2657     }\r
2658 \r
2659     /**\r
2660      * Override hashCode.\r
2661      * Generates the hash code for the SimpleDateFormat object\r
2662      * @stable ICU 2.0\r
2663      */\r
2664     public int hashCode()\r
2665     {\r
2666         return pattern.hashCode();\r
2667         // just enough fields for a reasonable distribution\r
2668     }\r
2669 \r
2670     /**\r
2671      * Override equals.\r
2672      * @stable ICU 2.0\r
2673      */\r
2674     public boolean equals(Object obj)\r
2675     {\r
2676         if (!super.equals(obj)) return false; // super does class check\r
2677         SimpleDateFormat that = (SimpleDateFormat) obj;\r
2678         return (pattern.equals(that.pattern)\r
2679                 && formatData.equals(that.formatData));\r
2680     }\r
2681 \r
2682     /**\r
2683      * Override writeObject.\r
2684      */\r
2685     private void writeObject(ObjectOutputStream stream) throws IOException{\r
2686         if (defaultCenturyStart == null) {\r
2687             // if defaultCenturyStart is not yet initialized,\r
2688             // calculate and set value before serialization.\r
2689             initializeDefaultCenturyStart(defaultCenturyBase);\r
2690         }\r
2691         stream.defaultWriteObject();\r
2692     }\r
2693     \r
2694     /**\r
2695      * Override readObject.\r
2696      */\r
2697     private void readObject(ObjectInputStream stream)\r
2698         throws IOException, ClassNotFoundException {\r
2699         stream.defaultReadObject();\r
2700         ///CLOVER:OFF\r
2701         // don't have old serial data to test with\r
2702         if (serialVersionOnStream < 1) {\r
2703             // didn't have defaultCenturyStart field\r
2704             defaultCenturyBase = System.currentTimeMillis();\r
2705         }\r
2706         ///CLOVER:ON\r
2707         else {\r
2708             // fill in dependent transient field\r
2709             parseAmbiguousDatesAsAfter(defaultCenturyStart);\r
2710         }\r
2711         serialVersionOnStream = currentSerialVersion;\r
2712         locale = getLocale(ULocale.VALID_LOCALE);\r
2713 \r
2714         initLocalZeroPaddingNumberFormat();\r
2715     }\r
2716 \r
2717 //#if defined(FOUNDATION10) || defined(J2SE13)\r
2718 //#else\r
2719     /**\r
2720      * Format the object to an attributed string, and return the corresponding iterator\r
2721      * Overrides superclass method.\r
2722      * \r
2723      * @param obj The object to format\r
2724      * @return <code>AttributedCharacterIterator</code> describing the formatted value.\r
2725      * \r
2726      * @stable ICU 3.8\r
2727      */\r
2728     public AttributedCharacterIterator formatToCharacterIterator(Object obj) {\r
2729         Calendar cal = calendar;\r
2730         if (obj instanceof Calendar) {\r
2731             cal = (Calendar)obj;\r
2732         } else if (obj instanceof Date) {\r
2733             calendar.setTime((Date)obj);\r
2734         } else if (obj instanceof Number) {\r
2735             calendar.setTimeInMillis(((Number)obj).longValue());\r
2736         } else { \r
2737             throw new IllegalArgumentException("Cannot format given Object as a Date");\r
2738         }\r
2739         StringBuffer toAppendTo = new StringBuffer();\r
2740         FieldPosition pos = new FieldPosition(0);\r
2741         List attributes = new LinkedList();\r
2742         format(cal, toAppendTo, pos, attributes);\r
2743 \r
2744         AttributedString as = new AttributedString(toAppendTo.toString());\r
2745         \r
2746         // add DateFormat field attributes to the AttributedString\r
2747         for (int i = 0; i < attributes.size(); i++) {\r
2748             FieldPosition fp = (FieldPosition) attributes.get(i);\r
2749             Format.Field attribute = fp.getFieldAttribute();\r
2750             as.addAttribute(attribute, attribute, fp.getBeginIndex(), fp.getEndIndex());\r
2751         }\r
2752         // return the CharacterIterator from AttributedString\r
2753         return as.getIterator();\r
2754     }\r
2755 //#endif\r
2756 \r
2757 \r
2758     /**\r
2759      * Get the locale of this simple date formatter.\r
2760      * It is package accessible. also used in DateIntervalFormat.\r
2761      *\r
2762      * @return   locale in this simple date formatter\r
2763      */\r
2764     ULocale getLocale() \r
2765     {\r
2766         return locale;\r
2767     }\r
2768 \r
2769 \r
2770     \r
2771     /**\r
2772      * Check whether the 'field' is smaller than all the fields covered in\r
2773      * pattern, return true if it is.\r
2774      * The sequence of calendar field,\r
2775      * from large to small is: ERA, YEAR, MONTH, DATE, AM_PM, HOUR, MINUTE,...\r
2776      * @param field    the calendar field need to check against\r
2777      * @return         true if the 'field' is smaller than all the fields \r
2778      *                 covered in pattern. false otherwise.\r
2779      */\r
2780 \r
2781     boolean isFieldUnitIgnored(int field) {\r
2782         return isFieldUnitIgnored(pattern, field);\r
2783     }\r
2784 \r
2785 \r
2786     /*\r
2787      * Check whether the 'field' is smaller than all the fields covered in\r
2788      * pattern, return true if it is.\r
2789      * The sequence of calendar field,\r
2790      * from large to small is: ERA, YEAR, MONTH, DATE, AM_PM, HOUR, MINUTE,...\r
2791      * @param pattern  the pattern to check against\r
2792      * @param field    the calendar field need to check against\r
2793      * @return         true if the 'field' is smaller than all the fields \r
2794      *                 covered in pattern. false otherwise.\r
2795      * @internal ICU 4.0\r
2796      */\r
2797     static boolean isFieldUnitIgnored(String pattern, int field) {\r
2798         int fieldLevel = CALENDAR_FIELD_TO_LEVEL[field];\r
2799         int level;\r
2800         char ch;\r
2801         boolean inQuote = false;\r
2802         char prevCh = 0;\r
2803         int count = 0;\r
2804     \r
2805         for (int i = 0; i < pattern.length(); ++i) {\r
2806             ch = pattern.charAt(i);\r
2807             if (ch != prevCh && count > 0) {\r
2808                 level = PATTERN_CHAR_TO_LEVEL[prevCh - PATTERN_CHAR_BASE];\r
2809                 if ( fieldLevel <= level ) {\r
2810                     return false;\r
2811                 }\r
2812                 count = 0;\r
2813             }\r
2814             if (ch == '\'') {\r
2815                 if ((i+1) < pattern.length() && pattern.charAt(i+1) == '\'') {\r
2816                     ++i;\r
2817                 } else {\r
2818                     inQuote = ! inQuote;\r
2819                 }\r
2820             } \r
2821             else if ( ! inQuote && ((ch >= 0x0061 /*'a'*/ && ch <= 0x007A /*'z'*/) \r
2822                         || (ch >= 0x0041 /*'A'*/ && ch <= 0x005A /*'Z'*/))) {\r
2823                 prevCh = ch;\r
2824                 ++count;\r
2825             }\r
2826         }\r
2827         if ( count > 0 ) {\r
2828             // last item\r
2829             level = PATTERN_CHAR_TO_LEVEL[prevCh - PATTERN_CHAR_BASE];\r
2830                 if ( fieldLevel <= level ) {\r
2831                     return false;\r
2832                 }\r
2833         }\r
2834         return true;\r
2835     }\r
2836 \r
2837 \r
2838     /**\r
2839      * Format date interval by algorithm. \r
2840      * It is supposed to be used only by CLDR survey tool.\r
2841      *\r
2842      * @param fromCalendar      calendar set to the from date in date interval\r
2843      *                          to be formatted into date interval stirng\r
2844      * @param toCalendar        calendar set to the to date in date interval\r
2845      *                          to be formatted into date interval stirng\r
2846      * @param appendTo          Output parameter to receive result.\r
2847      *                          Result is appended to existing contents.\r
2848      * @param pos               On input: an alignment field, if desired.\r
2849      *                          On output: the offsets of the alignment field.\r
2850      * @exception IllegalArgumentException when there is non-recognized\r
2851      *                                     pattern letter\r
2852      * @return                  Reference to 'appendTo' parameter.\r
2853      * @internal ICU 4.0\r
2854      */\r
2855     public final StringBuffer intervalFormatByAlgorithm(Calendar fromCalendar,\r
2856                                                         Calendar toCalendar,\r
2857                                                         StringBuffer appendTo,\r
2858                                                         FieldPosition pos)\r
2859                               throws IllegalArgumentException\r
2860     {\r
2861         // not support different calendar types and time zones\r
2862         if ( !fromCalendar.isEquivalentTo(toCalendar) ) {\r
2863             throw new IllegalArgumentException("can not format on two different calendars");\r
2864         }\r
2865      \r
2866         Object[] items = getPatternItems();\r
2867         int diffBegin = -1;\r
2868         int diffEnd = -1;\r
2869 \r
2870         /* look for different formatting string range */\r
2871         // look for start of difference\r
2872         try {\r
2873             for (int i = 0; i < items.length; i++) {\r
2874                 if ( diffCalFieldValue(fromCalendar, toCalendar, items, i) ) {\r
2875                     diffBegin = i;\r
2876                     break;\r
2877                 }\r
2878             } \r
2879         \r
2880             if ( diffBegin == -1 ) { \r
2881                 // no difference, single date format\r
2882                 return format(fromCalendar, appendTo, pos);\r
2883             }\r
2884     \r
2885             // look for end of difference\r
2886             for (int i = items.length-1; i >= diffBegin; i--) {\r
2887                 if ( diffCalFieldValue(fromCalendar, toCalendar, items, i) ) {\r
2888                     diffEnd = i;\r
2889                     break;\r
2890                 }\r
2891             }\r
2892         } catch ( IllegalArgumentException e ) {\r
2893             throw new IllegalArgumentException(e.toString());\r
2894         }\r
2895 \r
2896         // full range is different\r
2897         if ( diffBegin == 0 && diffEnd == items.length-1 ) {\r
2898             format(fromCalendar, appendTo, pos);\r
2899             appendTo.append(" \u2013 "); // default separator\r
2900             format(toCalendar, appendTo, pos);\r
2901             return appendTo;\r
2902         }\r
2903 \r
2904 \r
2905         /* search for largest calendar field within the different range */\r
2906         int highestLevel = 1000;\r
2907         for (int i = diffBegin; i <= diffEnd; i++) {\r
2908             if ( items[i] instanceof String) {\r
2909                 continue;\r
2910             } \r
2911             PatternItem item = (PatternItem)items[i];\r
2912             char ch = item.type; \r
2913             int patternCharIndex = -1;\r
2914             if ('A' <= ch && ch <= 'z') {\r
2915                 patternCharIndex = PATTERN_CHAR_TO_LEVEL[(int)ch - PATTERN_CHAR_BASE];\r
2916             }\r
2917     \r
2918             if (patternCharIndex == -1) {\r
2919                 throw new IllegalArgumentException("Illegal pattern character " +\r
2920                                                    "'" + ch + "' in \"" +\r
2921                                                    new String(pattern) + '"');\r
2922             }\r
2923     \r
2924             if ( patternCharIndex < highestLevel ) {\r
2925                 highestLevel = patternCharIndex;\r
2926             }\r
2927         }\r
2928 \r
2929         /* re-calculate diff range, including those calendar field which\r
2930            is in lower level than the largest calendar field covered\r
2931            in diff range calculated. */\r
2932         try {\r
2933             for (int i = 0; i < diffBegin; i++) {\r
2934                 if ( lowerLevel(items, i, highestLevel) ) {\r
2935                     diffBegin = i;\r
2936                     break;\r
2937                 }\r
2938             }\r
2939     \r
2940     \r
2941             for (int i = items.length-1; i > diffEnd; i--) {\r
2942                 if ( lowerLevel(items, i, highestLevel) ) {\r
2943                     diffEnd = i;\r
2944                     break;\r
2945                 }\r
2946             }\r
2947         } catch ( IllegalArgumentException e ) {\r
2948             throw new IllegalArgumentException(e.toString());\r
2949         }\r
2950 \r
2951 \r
2952         // full range is different\r
2953         if ( diffBegin == 0 && diffEnd == items.length-1 ) {\r
2954             format(fromCalendar, appendTo, pos);\r
2955             appendTo.append(" \u2013 "); // default separator\r
2956             format(toCalendar, appendTo, pos);\r
2957             return appendTo;\r
2958         }\r
2959 \r
2960 \r
2961         // formatting\r
2962         // Initialize\r
2963         pos.setBeginIndex(0);\r
2964         pos.setEndIndex(0);\r
2965 \r
2966         // formatting date 1\r
2967         for (int i = 0; i <= diffEnd; i++) {\r
2968             if (items[i] instanceof String) {\r
2969                 appendTo.append((String)items[i]);\r
2970             } else {\r
2971                 PatternItem item = (PatternItem)items[i];\r
2972                 if (useFastFormat) {\r
2973                     subFormat(appendTo, item.type, item.length, appendTo.length(), pos, fromCalendar);\r
2974                 } else {\r
2975                     appendTo.append(subFormat(item.type, item.length, appendTo.length(), pos, formatData, fromCalendar));\r
2976                 }\r
2977             }\r
2978         }\r
2979 \r
2980         appendTo.append(" \u2013 "); // default separator\r
2981 \r
2982         // formatting date 2\r
2983         for (int i = diffBegin; i < items.length; i++) {\r
2984             if (items[i] instanceof String) {\r
2985                 appendTo.append((String)items[i]);\r
2986             } else {\r
2987                 PatternItem item = (PatternItem)items[i];\r
2988                 if (useFastFormat) {\r
2989                     subFormat(appendTo, item.type, item.length, appendTo.length(), pos, toCalendar);\r
2990                 } else {\r
2991                     appendTo.append(subFormat(item.type, item.length, appendTo.length(), pos, formatData, toCalendar));\r
2992                 }\r
2993             }\r
2994         }\r
2995         return appendTo;\r
2996     }\r
2997 \r
2998 \r
2999     /**\r
3000      * check whether the i-th item in 2 calendar is in different value.\r
3001      *\r
3002      * It is supposed to be used only by CLDR survey tool.\r
3003      * It is used by intervalFormatByAlgorithm().\r
3004      *\r
3005      * @param fromCalendar   one calendar\r
3006      * @param toCalendar     the other calendar\r
3007      * @param items          pattern items\r
3008      * @param i              the i-th item in pattern items\r
3009      * @exception IllegalArgumentException when there is non-recognized\r
3010      *                                     pattern letter\r
3011      * @return               true is i-th item in 2 calendar is in different \r
3012      *                       value, false otherwise.\r
3013      */\r
3014     private boolean diffCalFieldValue(Calendar fromCalendar,\r
3015                                       Calendar toCalendar,\r
3016                                       Object[] items,\r
3017                                       int i) throws IllegalArgumentException {\r
3018         if ( items[i] instanceof String) {\r
3019             return false;\r
3020         } \r
3021         PatternItem item = (PatternItem)items[i];\r
3022         char ch = item.type; \r
3023         int patternCharIndex = -1;\r
3024         if ('A' <= ch && ch <= 'z') {\r
3025             patternCharIndex = PATTERN_CHAR_TO_INDEX[(int)ch - PATTERN_CHAR_BASE];\r
3026         }\r
3027    \r
3028         if (patternCharIndex == -1) {\r
3029             throw new IllegalArgumentException("Illegal pattern character " +\r
3030                                                "'" + ch + "' in \"" +\r
3031                                                new String(pattern) + '"');\r
3032         }\r
3033   \r
3034         final int field = PATTERN_INDEX_TO_CALENDAR_FIELD[patternCharIndex];\r
3035         int value = fromCalendar.get(field);\r
3036         int value_2 = toCalendar.get(field);\r
3037         if ( value != value_2 ) {\r
3038             return true;\r
3039         }\r
3040         return false;\r
3041     }\r
3042 \r
3043 \r
3044     /**\r
3045      * check whether the i-th item's level is lower than the input 'level'\r
3046      *\r
3047      * It is supposed to be used only by CLDR survey tool.\r
3048      * It is used by intervalFormatByAlgorithm().\r
3049      *\r
3050      * @param items  the pattern items\r
3051      * @param i      the i-th item in pattern items\r
3052      * @param level  the level with which the i-th pattern item compared to\r
3053      * @exception IllegalArgumentException when there is non-recognized\r
3054      *                                     pattern letter\r
3055      * @return       true if i-th pattern item is lower than 'level',\r
3056      *               false otherwise\r
3057      */\r
3058     private boolean lowerLevel(Object[] items, int i, int level) \r
3059                     throws IllegalArgumentException {\r
3060         if ( items[i] instanceof String) {\r
3061             return false;\r
3062         } \r
3063         PatternItem item = (PatternItem)items[i];\r
3064         char ch = item.type; \r
3065         int patternCharIndex = -1;\r
3066         if ('A' <= ch && ch <= 'z') {\r
3067             patternCharIndex = PATTERN_CHAR_TO_LEVEL[(int)ch - PATTERN_CHAR_BASE];\r
3068         }\r
3069     \r
3070         if (patternCharIndex == -1) {\r
3071             throw new IllegalArgumentException("Illegal pattern character " +\r
3072                                                "'" + ch + "' in \"" +\r
3073                                                new String(pattern) + '"');\r
3074         }\r
3075     \r
3076         if ( patternCharIndex >= level ) {\r
3077             return true;\r
3078         }    \r
3079         return false;\r
3080     }\r
3081 \r
3082     protected NumberFormat getNumberFormat(char ch) {\r
3083 \r
3084        Character ovrField;\r
3085        ovrField = new Character(ch);\r
3086        if (overrideMap != null && overrideMap.containsKey(ovrField)) {\r
3087            String nsName = overrideMap.get(ovrField).toString();\r
3088            NumberFormat nf = (NumberFormat)numberFormatters.get(nsName);\r
3089            return nf;\r
3090        } else {\r
3091            return numberFormat;\r
3092        }\r
3093     }\r
3094 \r
3095     private void initNumberFormatters(ULocale loc) {\r
3096 \r
3097        numberFormatters = new HashMap();\r
3098        overrideMap = new HashMap();\r
3099        processOverrideString(loc,override); \r
3100 \r
3101     }\r
3102 \r
3103     private void processOverrideString(ULocale loc, String str) {\r
3104 \r
3105         if ( str == null || str.length() == 0 )\r
3106             return;\r
3107 \r
3108         int start = 0;\r
3109         int end;\r
3110         String nsName;\r
3111         Character ovrField;\r
3112         boolean moreToProcess = true;\r
3113         boolean fullOverride;\r
3114 \r
3115         while (moreToProcess) {\r
3116             int delimiterPosition = str.indexOf(";",start);\r
3117             if (delimiterPosition == -1) {\r
3118                 moreToProcess = false;\r
3119                 end = str.length();\r
3120             } else {\r
3121                 end = delimiterPosition;\r
3122             }\r
3123 \r
3124             String currentString = str.substring(start,end);\r
3125             int equalSignPosition = currentString.indexOf("=");\r
3126             if (equalSignPosition == -1) { // Simple override string such as "hebrew"\r
3127                nsName = currentString;\r
3128                fullOverride = true;\r
3129             } else { // Field specific override string such as "y=hebrew"\r
3130                nsName = currentString.substring(equalSignPosition+1);\r
3131                ovrField = new Character(currentString.charAt(0));\r
3132                overrideMap.put(ovrField,nsName);\r
3133                fullOverride = false;\r
3134             }\r
3135 \r
3136             ULocale ovrLoc = new ULocale(loc.getBaseName()+"@numbers="+nsName);\r
3137             NumberFormat nf = NumberFormat.createInstance(ovrLoc,NumberFormat.NUMBERSTYLE);\r
3138             \r
3139             if (fullOverride) {\r
3140                 setNumberFormat(nf);\r
3141             } else {\r
3142                 // Since one or more of the override number formatters might be complex,\r
3143                 // we can't rely on the fast numfmt where we have a partial field override.\r
3144                 useLocalZeroPaddingNumberFormat = false;\r
3145             }\r
3146 \r
3147             if (!numberFormatters.containsKey(nsName)) {\r
3148                   numberFormatters.put(nsName,nf);\r
3149             }\r
3150 \r
3151             start = delimiterPosition + 1;\r
3152 \r
3153         }    \r
3154     }\r
3155 }\r