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