]> gitweb.fperrin.net Git - Dictionary.git/blobdiff - jars/icu4j-4_2_1-src/src/com/ibm/icu/text/SimpleDateFormat.java
go
[Dictionary.git] / jars / icu4j-4_2_1-src / src / com / ibm / icu / text / SimpleDateFormat.java
old mode 100755 (executable)
new mode 100644 (file)
index 0656719..9ac82d8
-//##header\r
-/*\r
- *******************************************************************************\r
- * Copyright (C) 1996-2009, International Business Machines Corporation and    *\r
- * others. All Rights Reserved.                                                *\r
- *******************************************************************************\r
- */\r
-\r
-package com.ibm.icu.text;\r
-\r
-import java.io.IOException;\r
-import java.io.ObjectInputStream;\r
-import java.io.ObjectOutputStream;\r
-import java.lang.ref.WeakReference;\r
-import java.lang.Character;\r
-import java.text.FieldPosition;\r
-import java.text.ParsePosition;\r
-import java.util.ArrayList;\r
-import java.util.Date;\r
-import java.util.HashMap;\r
-import java.util.List;\r
-import java.util.Locale;\r
-import java.util.MissingResourceException;\r
-//#if defined(FOUNDATION10) || defined(J2SE13)\r
-//#else\r
-import java.text.AttributedCharacterIterator;\r
-import java.text.AttributedString;\r
-import java.text.Format;\r
-import java.util.LinkedList;\r
-//#endif\r
-\r
-import com.ibm.icu.impl.CalendarData;\r
-import com.ibm.icu.impl.DateNumberFormat;\r
-import com.ibm.icu.impl.ICUCache;\r
-import com.ibm.icu.impl.SimpleCache;\r
-import com.ibm.icu.impl.UCharacterProperty;\r
-import com.ibm.icu.impl.ZoneMeta;\r
-import com.ibm.icu.impl.ZoneStringFormat.ZoneStringInfo;\r
-import com.ibm.icu.lang.UCharacter;\r
-import com.ibm.icu.util.BasicTimeZone;\r
-import com.ibm.icu.util.Calendar;\r
-import com.ibm.icu.util.GregorianCalendar;\r
-import com.ibm.icu.util.TimeZone;\r
-import com.ibm.icu.util.TimeZoneTransition;\r
-import com.ibm.icu.util.ULocale;\r
-\r
-\r
-/**\r
- * <code>SimpleDateFormat</code> is a concrete class for formatting and\r
- * parsing dates in a locale-sensitive manner. It allows for formatting\r
- * (date -> text), parsing (text -> date), and normalization.\r
- *\r
- * <p>\r
- * <code>SimpleDateFormat</code> allows you to start by choosing\r
- * any user-defined patterns for date-time formatting. However, you\r
- * are encouraged to create a date-time formatter with either\r
- * <code>getTimeInstance</code>, <code>getDateInstance</code>, or\r
- * <code>getDateTimeInstance</code> in <code>DateFormat</code>. Each\r
- * of these class methods can return a date/time formatter initialized\r
- * with a default format pattern. You may modify the format pattern\r
- * using the <code>applyPattern</code> methods as desired.\r
- * For more information on using these methods, see\r
- * {@link DateFormat}.\r
- *\r
- * <p>\r
- * <strong>Time Format Syntax:</strong>\r
- * <p>\r
- * To specify the time format use a <em>time pattern</em> string.\r
- * In this pattern, all ASCII letters are reserved as pattern letters,\r
- * which are defined as the following:\r
- * <blockquote>\r
- * <pre>\r
- * Symbol   Meaning                 Presentation        Example\r
- * ------   -------                 ------------        -------\r
- * G        era designator          (Text)              AD\r
- * y&#x2020;       year                    (Number)            1996\r
- * Y*       year (week of year)     (Number)            1997\r
- * u*       extended year           (Number)            4601\r
- * M        month in year           (Text & Number)     July & 07\r
- * d        day in month            (Number)            10\r
- * h        hour in am/pm (1~12)    (Number)            12\r
- * H        hour in day (0~23)      (Number)            0\r
- * m        minute in hour          (Number)            30\r
- * s        second in minute        (Number)            55\r
- * S        fractional second       (Number)            978\r
- * E        day of week             (Text)              Tuesday\r
- * e*       day of week (local 1~7) (Text & Number)     Tuesday & 2\r
- * D        day in year             (Number)            189\r
- * F        day of week in month    (Number)            2 (2nd Wed in July)\r
- * w        week in year            (Number)            27\r
- * W        week in month           (Number)            2\r
- * a        am/pm marker            (Text)              PM\r
- * k        hour in day (1~24)      (Number)            24\r
- * K        hour in am/pm (0~11)    (Number)            0\r
- * z        time zone               (Text)              Pacific Standard Time\r
- * Z        time zone (RFC 822)     (Number)            -0800\r
- * v        time zone (generic)     (Text)              Pacific Time\r
- * V        time zone (location)    (Text)              United States (Los Angeles)\r
- * g*       Julian day              (Number)            2451334\r
- * A*       milliseconds in day     (Number)            69540000\r
- * Q*       quarter in year         (Text & Number)     Q1 & 01\r
- * c*       stand alone day of week (Text & Number)     Tuesday & 2\r
- * L*       stand alone month       (Text & Number)     July & 07\r
- * q*       stand alone quarter     (Text & Number)     Q1 & 01\r
- * '        escape for text         (Delimiter)         'Date='\r
- * ''       single quote            (Literal)           'o''clock'\r
- * </pre>\r
- * </blockquote>\r
- * <tt><b>*</b></tt> These items are not supported by Java's SimpleDateFormat.<br>\r
- * <tt><b>&#x2020;</b></tt> ICU interprets a single 'y' differently than Java.</p>\r
- * <p>\r
- * The count of pattern letters determine the format.\r
- * <p>\r
- * <strong>(Text)</strong>: 4 or more pattern letters--use full form,\r
- * &lt; 4--use short or abbreviated form if one exists.\r
- * <p>\r
- * <strong>(Number)</strong>: the minimum number of digits. Shorter\r
- * numbers are zero-padded to this amount. Year is handled specially;\r
- * that is, if the count of 'y' is 2, the Year will be truncated to 2 digits.\r
- * (e.g., if "yyyy" produces "1997", "yy" produces "97".)\r
- * Unlike other fields, fractional seconds are padded on the right with zero.\r
- * <p>\r
- * <strong>(Text & Number)</strong>: 3 or over, use text, otherwise use number.\r
- * <p>\r
- * Any characters in the pattern that are not in the ranges of ['a'..'z']\r
- * and ['A'..'Z'] will be treated as quoted text. For instance, characters\r
- * like ':', '.', ' ', '#' and '@' will appear in the resulting time text\r
- * even they are not embraced within single quotes.\r
- * <p>\r
- * A pattern containing any invalid pattern letter will result in a thrown\r
- * exception during formatting or parsing.\r
- *\r
- * <p>\r
- * <strong>Examples Using the US Locale:</strong>\r
- * <blockquote>\r
- * <pre>\r
- * Format Pattern                         Result\r
- * --------------                         -------\r
- * "yyyy.MM.dd G 'at' HH:mm:ss vvvv" ->>  1996.07.10 AD at 15:08:56 Pacific Time\r
- * "EEE, MMM d, ''yy"                ->>  Wed, July 10, '96\r
- * "h:mm a"                          ->>  12:08 PM\r
- * "hh 'o''clock' a, zzzz"           ->>  12 o'clock PM, Pacific Daylight Time\r
- * "K:mm a, vvv"                     ->>  0:00 PM, PT\r
- * "yyyyy.MMMMM.dd GGG hh:mm aaa"    ->>  01996.July.10 AD 12:08 PM\r
- * </pre>\r
- * </blockquote>\r
- * <strong>Code Sample:</strong>\r
- * <blockquote>\r
- * <pre>\r
- * SimpleTimeZone pdt = new SimpleTimeZone(-8 * 60 * 60 * 1000, "PST");\r
- * pdt.setStartRule(Calendar.APRIL, 1, Calendar.SUNDAY, 2*60*60*1000);\r
- * pdt.setEndRule(Calendar.OCTOBER, -1, Calendar.SUNDAY, 2*60*60*1000);\r
- * <br>\r
- * // Format the current time.\r
- * SimpleDateFormat formatter\r
- *     = new SimpleDateFormat ("yyyy.MM.dd G 'at' hh:mm:ss a zzz");\r
- * Date currentTime_1 = new Date();\r
- * String dateString = formatter.format(currentTime_1);\r
- * <br>\r
- * // Parse the previous string back into a Date.\r
- * ParsePosition pos = new ParsePosition(0);\r
- * Date currentTime_2 = formatter.parse(dateString, pos);\r
- * </pre>\r
- * </blockquote>\r
- * In the example, the time value <code>currentTime_2</code> obtained from\r
- * parsing will be equal to <code>currentTime_1</code>. However, they may not be\r
- * equal if the am/pm marker 'a' is left out from the format pattern while\r
- * the "hour in am/pm" pattern symbol is used. This information loss can\r
- * happen when formatting the time in PM.\r
- *\r
- * <p>\r
- * When parsing a date string using the abbreviated year pattern ("yy"),\r
- * SimpleDateFormat must interpret the abbreviated year\r
- * relative to some century.  It does this by adjusting dates to be\r
- * within 80 years before and 20 years after the time the SimpleDateFormat\r
- * instance is created. For example, using a pattern of "MM/dd/yy" and a\r
- * SimpleDateFormat instance created on Jan 1, 1997,  the string\r
- * "01/11/12" would be interpreted as Jan 11, 2012 while the string "05/04/64"\r
- * would be interpreted as May 4, 1964.\r
- * During parsing, only strings consisting of exactly two digits, as defined by\r
- * {@link com.ibm.icu.lang.UCharacter#isDigit(int)}, will be parsed into the default\r
- * century.\r
- * Any other numeric string, such as a one digit string, a three or more digit\r
- * string, or a two digit string that isn't all digits (for example, "-1"), is\r
- * interpreted literally.  So "01/02/3" or "01/02/003" are parsed, using the\r
- * same pattern, as Jan 2, 3 AD.  Likewise, "01/02/-3" is parsed as Jan 2, 4 BC.\r
- *\r
- * <p>\r
- * If the year pattern does not have exactly two 'y' characters, the year is\r
- * interpreted literally, regardless of the number of digits.  So using the\r
- * pattern "MM/dd/yyyy", "01/11/12" parses to Jan 11, 12 A.D.\r
- *\r
- * <p>\r
- * When numeric fields abut one another directly, with no intervening delimiter\r
- * characters, they constitute a run of abutting numeric fields.  Such runs are\r
- * parsed specially.  For example, the format "HHmmss" parses the input text\r
- * "123456" to 12:34:56, parses the input text "12345" to 1:23:45, and fails to\r
- * parse "1234".  In other words, the leftmost field of the run is flexible,\r
- * while the others keep a fixed width.  If the parse fails anywhere in the run,\r
- * then the leftmost field is shortened by one character, and the entire run is\r
- * parsed again. This is repeated until either the parse succeeds or the\r
- * leftmost field is one character in length.  If the parse still fails at that\r
- * point, the parse of the run fails.\r
- *\r
- * <p>\r
- * For time zones that have no names, use strings GMT+hours:minutes or\r
- * GMT-hours:minutes.\r
- *\r
- * <p>\r
- * The calendar defines what is the first day of the week, the first week\r
- * of the year, whether hours are zero based or not (0 vs 12 or 24), and the\r
- * time zone. There is one common decimal format to handle all the numbers;\r
- * the digit count is handled programmatically according to the pattern.\r
- *\r
- * <h4>Synchronization</h4>\r
- *\r
- * Date formats are not synchronized. It is recommended to create separate\r
- * format instances for each thread. If multiple threads access a format\r
- * concurrently, it must be synchronized externally.\r
- *\r
- * @see          com.ibm.icu.util.Calendar\r
- * @see          com.ibm.icu.util.GregorianCalendar\r
- * @see          com.ibm.icu.util.TimeZone\r
- * @see          DateFormat\r
- * @see          DateFormatSymbols\r
- * @see          DecimalFormat\r
- * @author       Mark Davis, Chen-Lieh Huang, Alan Liu\r
- * @stable ICU 2.0\r
- */\r
-public class SimpleDateFormat extends DateFormat {\r
-\r
-    // the official serial version ID which says cryptically\r
-    // which version we're compatible with\r
-    private static final long serialVersionUID = 4774881970558875024L;\r
-\r
-    // the internal serial version which says which version was written\r
-    // - 0 (default) for version up to JDK 1.1.3\r
-    // - 1 for version from JDK 1.1.4, which includes a new field\r
-    static final int currentSerialVersion = 1;\r
-\r
-    \r
-    /*\r
-     * From calendar field to its level.\r
-     * Used to order calendar field.\r
-     * For example, calendar fields can be defined in the following order:\r
-     * year >  month > date > am-pm > hour >  minute\r
-     * YEAR --> 10, MONTH -->20, DATE --> 30; \r
-     * AM_PM -->40, HOUR --> 50, MINUTE -->60\r
-     */\r
-    private static final int[] CALENDAR_FIELD_TO_LEVEL =\r
-    {\r
-        /*GyM*/ 0, 10, 20,\r
-        /*wW*/ 20, 30,\r
-        /*dDEF*/ 30, 20, 30, 30,\r
-        /*ahHm*/ 40, 50, 50, 60,\r
-        /*sS..*/ 70, 80, \r
-        /*z?Y*/ 0, 0, 10, \r
-        /*eug*/ 30, 10, 0,\r
-        /*A*/ 40 \r
-    };\r
-\r
-\r
-    \r
-    /*\r
-     * From calendar field letter to its level.\r
-     * Used to order calendar field.\r
-     * For example, calendar fields can be defined in the following order:\r
-     * year >  month > date > am-pm > hour >  minute\r
-     * 'y' --> 10, 'M' -->20, 'd' --> 30; 'a' -->40, 'h' --> 50, 'm' -->60\r
-     */\r
-    private static final int[] PATTERN_CHAR_TO_LEVEL = \r
-    {\r
-    //       A   B   C   D    E   F   G    H   I   J   K   L    M   N   O\r
-        -1, 40, -1, -1, 20,  30, 30,  0,  50, -1, -1, 50, 20,  20, -1, -1,\r
-    //   P   Q   R    S   T   U  V   W   X   Y  Z\r
-        -1, 20, -1,  80, -1, -1, 0, 30, -1, 10, 0, -1, -1, -1, -1, -1,\r
-    //       a   b   c   d    e   f  g   h   i   j    k   l    m   n   o\r
-        -1, 40, -1, 30,  30, 30, -1, 0, 50, -1, -1,  50, -1,  60, -1, -1,\r
-    //   p   q   r    s   t   u  v   w   x    y  z\r
-        -1, 20, -1,  70, -1, 10, 0, 20, -1,  10, 0, -1, -1, -1, -1, -1\r
-    };\r
-\r
-\r
-    /**\r
-     * The version of the serialized data on the stream.  Possible values:\r
-     * <ul>\r
-     * <li><b>0</b> or not present on stream: JDK 1.1.3.  This version\r
-     * has no <code>defaultCenturyStart</code> on stream.\r
-     * <li><b>1</b> JDK 1.1.4 or later.  This version adds\r
-     * <code>defaultCenturyStart</code>.\r
-     * </ul>\r
-     * When streaming out this class, the most recent format\r
-     * and the highest allowable <code>serialVersionOnStream</code>\r
-     * is written.\r
-     * @serial\r
-     */\r
-    private int serialVersionOnStream = currentSerialVersion;\r
-\r
-    /**\r
-     * The pattern string of this formatter.  This is always a non-localized\r
-     * pattern.  May not be null.  See class documentation for details.\r
-     * @serial\r
-     */\r
-    private String pattern;\r
-\r
-    /**\r
-     * The override string of this formatter.  Used to override the\r
-     * numbering system for one or more fields.\r
-     * @serial\r
-     */\r
-    private String override;\r
-\r
-    /**\r
-     * The hash map used for number format overrides.\r
-     * @serial\r
-     */\r
-    private HashMap numberFormatters;\r
-\r
-    /**\r
-     * The hash map used for number format overrides.\r
-     * @serial\r
-     */\r
-    private HashMap overrideMap;\r
-\r
-    /**\r
-     * The symbols used by this formatter for week names, month names,\r
-     * etc.  May not be null.\r
-     * @serial\r
-     * @see DateFormatSymbols\r
-     */\r
-    private DateFormatSymbols formatData;\r
-\r
-    private transient ULocale locale;\r
-\r
-    /**\r
-     * We map dates with two-digit years into the century starting at\r
-     * <code>defaultCenturyStart</code>, which may be any date.  May\r
-     * not be null.\r
-     * @serial\r
-     * @since JDK1.1.4\r
-     */\r
-    private Date defaultCenturyStart;\r
-\r
-    private transient int defaultCenturyStartYear;\r
-\r
-    // defaultCenturyBase is set when an instance is created\r
-    // and may be used for calculating defaultCenturyStart when needed.\r
-    private transient long defaultCenturyBase;\r
-\r
-    // We need to preserve time zone type when parsing specific\r
-    // time zone text (xxx Standard Time vs xxx Daylight Time)\r
-    private static final int TZTYPE_UNK = 0, TZTYPE_STD = 1, TZTYPE_DST = 2;\r
-    private transient int tztype = TZTYPE_UNK;\r
-\r
-    private static final int millisPerHour = 60 * 60 * 1000;\r
-    private static final int millisPerMinute = 60 * 1000;\r
-    private static final int millisPerSecond = 1000;\r
-\r
-    // This prefix is designed to NEVER MATCH real text, in order to\r
-    // suppress the parsing of negative numbers.  Adjust as needed (if\r
-    // this becomes valid Unicode).\r
-    private static final String SUPPRESS_NEGATIVE_PREFIX = "\uAB00";\r
-\r
-    /**\r
-     * If true, this object supports fast formatting using the\r
-     * subFormat variant that takes a StringBuffer.\r
-     */\r
-    private transient boolean useFastFormat;\r
-\r
-    /**\r
-     * Construct a SimpleDateFormat using the default pattern for the default\r
-     * locale.  <b>Note:</b> Not all locales support SimpleDateFormat; for full\r
-     * generality, use the factory methods in the DateFormat class.\r
-     *\r
-     * @see DateFormat\r
-     * @stable ICU 2.0\r
-     */\r
-    public SimpleDateFormat() {\r
-        this(getDefaultPattern(), null, null, null, null, true, null);\r
-    }\r
-\r
-    /**\r
-     * Construct a SimpleDateFormat using the given pattern in the default\r
-     * locale.  <b>Note:</b> Not all locales support SimpleDateFormat; for full\r
-     * generality, use the factory methods in the DateFormat class.\r
-     * @stable ICU 2.0\r
-     */\r
-    public SimpleDateFormat(String pattern)\r
-    {\r
-        this(pattern, null, null, null, null, true, null);\r
-    }\r
-\r
-    /**\r
-     * Construct a SimpleDateFormat using the given pattern and locale.\r
-     * <b>Note:</b> Not all locales support SimpleDateFormat; for full\r
-     * generality, use the factory methods in the DateFormat class.\r
-     * @stable ICU 2.0\r
-     */\r
-    public SimpleDateFormat(String pattern, Locale loc)\r
-    {\r
-        this(pattern, null, null, null, ULocale.forLocale(loc), true, null);\r
-    }\r
-\r
-    /**\r
-     * Construct a SimpleDateFormat using the given pattern and locale.\r
-     * <b>Note:</b> Not all locales support SimpleDateFormat; for full\r
-     * generality, use the factory methods in the DateFormat class.\r
-     * @stable ICU 3.2\r
-     */\r
-    public SimpleDateFormat(String pattern, ULocale loc)\r
-    {\r
-        this(pattern, null, null, null, loc, true, null);\r
-    }\r
-\r
-    /**\r
-     * Construct a SimpleDateFormat using the given pattern , override and locale.\r
-     * @draft ICU 4.2\r
-     * @provisional This API might change or be removed in a future release.\r
-     */\r
-    public SimpleDateFormat(String pattern, String override, ULocale loc)\r
-    {\r
-        this(pattern, null, null, null, loc, false,override);\r
-    }\r
-\r
-    /**\r
-     * Construct a SimpleDateFormat using the given pattern and\r
-     * locale-specific symbol data.\r
-     * Warning: uses default locale for digits!\r
-     * @stable ICU 2.0\r
-     */\r
-    public SimpleDateFormat(String pattern, DateFormatSymbols formatData)\r
-    {\r
-        this(pattern, (DateFormatSymbols)formatData.clone(), null, null, null, true, null);\r
-    }\r
-\r
-    /**\r
-     * @internal ICU 3.2\r
-     * @deprecated This API is ICU internal only.\r
-     */\r
-    public SimpleDateFormat(String pattern, DateFormatSymbols formatData, ULocale loc)\r
-    {\r
-        this(pattern, (DateFormatSymbols)formatData.clone(), null, null, loc, true,null);\r
-    }\r
-\r
-    /**\r
-     * Package-private constructor that allows a subclass to specify\r
-     * whether it supports fast formatting.\r
-     *\r
-     * TODO make this API public.\r
-     */\r
-    SimpleDateFormat(String pattern, DateFormatSymbols formatData, Calendar calendar, ULocale locale,\r
-                     boolean useFastFormat, String override) {\r
-        this(pattern, (DateFormatSymbols)formatData.clone(), (Calendar)calendar.clone(), null, locale, useFastFormat,override);\r
-    }\r
-\r
-    SimpleDateFormat(String pattern, DateFormatSymbols formatData, Calendar calendar, ULocale locale,\r
-                     boolean useFastFormat) {\r
-        this(pattern, (DateFormatSymbols)formatData.clone(), (Calendar)calendar.clone(), null, locale, useFastFormat,null);\r
-    }\r
-\r
-    /*\r
-     * The constructor called from all other SimpleDateFormat constructors\r
-     */\r
-    private SimpleDateFormat(String pattern, DateFormatSymbols formatData, Calendar calendar,\r
-            NumberFormat numberFormat, ULocale locale, boolean useFastFormat,String override) {\r
-        this.pattern = pattern;\r
-        this.formatData = formatData;\r
-        this.calendar = calendar;\r
-        this.numberFormat = numberFormat;\r
-        this.locale = locale; // time zone formatting\r
-        this.useFastFormat = useFastFormat;\r
-        this.override = override;\r
-        initialize();\r
-    }\r
-\r
-    /**\r
-     * Create an instance of SimpleDateForamt for the given format configuration\r
-     * @param formatConfig the format configuration\r
-     * @return A SimpleDateFormat instance\r
-     * @internal ICU 3.8\r
-     * @deprecated This API is ICU internal only.\r
-     */\r
-    public static SimpleDateFormat getInstance(Calendar.FormatConfiguration formatConfig) {\r
-       \r
-        String ostr = formatConfig.getOverrideString();\r
-        boolean useFast = ( ostr != null && ostr.length() > 0 );\r
-\r
-        return new SimpleDateFormat(formatConfig.getPatternString(),\r
-                    formatConfig.getDateFormatSymbols(),\r
-                    formatConfig.getCalendar(),\r
-                    null,\r
-                    formatConfig.getLocale(),\r
-                    useFast,\r
-                    formatConfig.getOverrideString());\r
-    }\r
-\r
-    /*\r
-     * Initialized fields\r
-     */\r
-    private void initialize() {\r
-        if (locale == null) {\r
-            locale = ULocale.getDefault();\r
-        }\r
-        if (formatData == null) {\r
-            formatData = new DateFormatSymbols(locale);\r
-        }\r
-        if (calendar == null) {\r
-            calendar = Calendar.getInstance(locale);\r
-        }\r
-        if (numberFormat == null) {\r
-            NumberingSystem ns = NumberingSystem.getInstance(locale);\r
-            if ( ns.isAlgorithmic() ) {\r
-                numberFormat = NumberFormat.getInstance(locale);\r
-            } else {\r
-                char digit0 = ns.getDescription().charAt(0);\r
-                // Use a NumberFormat optimized for date formatting\r
-                numberFormat = new DateNumberFormat(locale, digit0);\r
-            }\r
-        }\r
-        // Note: deferring calendar calculation until when we really need it.\r
-        // Instead, we just record time of construction for backward compatibility.\r
-        defaultCenturyBase = System.currentTimeMillis();\r
-\r
-        setLocale(calendar.getLocale(ULocale.VALID_LOCALE ), calendar.getLocale(ULocale.ACTUAL_LOCALE));\r
-        initLocalZeroPaddingNumberFormat();\r
-\r
-        if (override != null) {\r
-           initNumberFormatters(locale);\r
-        }\r
-\r
-    }\r
-\r
-    // privates for the default pattern\r
-    private static ULocale cachedDefaultLocale = null;\r
-    private static String cachedDefaultPattern = null;\r
-    private static final String FALLBACKPATTERN = "yy/MM/dd HH:mm";\r
-\r
-    /*\r
-     * Returns the default date and time pattern (SHORT) for the default locale.\r
-     * This method is only used by the default SimpleDateFormat constructor.\r
-     */\r
-    private static synchronized String getDefaultPattern() {\r
-        ULocale defaultLocale = ULocale.getDefault();\r
-        if (!defaultLocale.equals(cachedDefaultLocale)) {\r
-            cachedDefaultLocale = defaultLocale;\r
-            Calendar cal = Calendar.getInstance(cachedDefaultLocale);\r
-            try {\r
-                CalendarData calData = new CalendarData(cachedDefaultLocale, cal.getType());\r
-                String[] dateTimePatterns = calData.getDateTimePatterns();\r
-                int glueIndex = 8;\r
-                if (dateTimePatterns.length >= 13)\r
-                {\r
-                    glueIndex += (SHORT + 1);\r
-                }\r
-                cachedDefaultPattern = MessageFormat.format(dateTimePatterns[glueIndex],\r
-                        new Object[] {dateTimePatterns[SHORT], dateTimePatterns[SHORT + 4]});\r
-            } catch (MissingResourceException e) {\r
-                cachedDefaultPattern = FALLBACKPATTERN;\r
-            }\r
-        }\r
-        return cachedDefaultPattern;\r
-    }\r
-\r
-    /* Define one-century window into which to disambiguate dates using\r
-     * two-digit years.\r
-     */\r
-    private void parseAmbiguousDatesAsAfter(Date startDate) {\r
-        defaultCenturyStart = startDate;\r
-        calendar.setTime(startDate);\r
-        defaultCenturyStartYear = calendar.get(Calendar.YEAR);\r
-    }\r
-\r
-    /* Initialize defaultCenturyStart and defaultCenturyStartYear by base time.\r
-     * The default start time is 80 years before the creation time of this object.\r
-     */\r
-    private void initializeDefaultCenturyStart(long baseTime) {\r
-        defaultCenturyBase = baseTime;\r
-        // clone to avoid messing up date stored in calendar object\r
-        // when this method is called while parsing\r
-        Calendar tmpCal = (Calendar)calendar.clone();\r
-        tmpCal.setTimeInMillis(baseTime);\r
-        tmpCal.add(Calendar.YEAR, -80);\r
-        defaultCenturyStart = tmpCal.getTime();\r
-        defaultCenturyStartYear = tmpCal.get(Calendar.YEAR);\r
-    }\r
-\r
-    /* Gets the default century start date for this object */\r
-    private Date getDefaultCenturyStart() {\r
-        if (defaultCenturyStart == null) {\r
-            // not yet initialized\r
-            initializeDefaultCenturyStart(defaultCenturyBase);\r
-        }\r
-        return defaultCenturyStart;\r
-    }\r
-\r
-    /* Gets the default century start year for this object */\r
-    private int getDefaultCenturyStartYear() {\r
-        if (defaultCenturyStart == null) {\r
-            // not yet initialized\r
-            initializeDefaultCenturyStart(defaultCenturyBase);\r
-        }\r
-        return defaultCenturyStartYear;\r
-    }\r
-\r
-    /**\r
-     * Sets the 100-year period 2-digit years will be interpreted as being in\r
-     * to begin on the date the user specifies.\r
-     * @param startDate During parsing, two digit years will be placed in the range\r
-     * <code>startDate</code> to <code>startDate + 100 years</code>.\r
-     * @stable ICU 2.0\r
-     */\r
-    public void set2DigitYearStart(Date startDate) {\r
-        parseAmbiguousDatesAsAfter(startDate);\r
-    }\r
-\r
-    /**\r
-     * Returns the beginning date of the 100-year period 2-digit years are interpreted\r
-     * as being within.\r
-     * @return the start of the 100-year period into which two digit years are\r
-     * parsed\r
-     * @stable ICU 2.0\r
-     */\r
-    public Date get2DigitYearStart() {\r
-        return getDefaultCenturyStart();\r
-    }\r
-\r
-    /**\r
-     * Overrides DateFormat.\r
-     * <p>Formats a date or time, which is the standard millis\r
-     * since January 1, 1970, 00:00:00 GMT.\r
-     * <p>Example: using the US locale:\r
-     * "yyyy.MM.dd G 'at' HH:mm:ss zzz" ->> 1996.07.10 AD at 15:08:56 PDT\r
-     * @param cal the calendar whose date-time value is to be formatted into a date-time string\r
-     * @param toAppendTo where the new date-time text is to be appended\r
-     * @param pos the formatting position. On input: an alignment field,\r
-     * if desired. On output: the offsets of the alignment field.\r
-     * @return the formatted date-time string.\r
-     * @see DateFormat\r
-     * @stable ICU 2.0\r
-     */\r
-    public StringBuffer format(Calendar cal, StringBuffer toAppendTo,\r
-                               FieldPosition pos) {\r
-        TimeZone backupTZ = null;\r
-        if (cal != calendar && !cal.getType().equals(calendar.getType())) {\r
-            // Different calendar type\r
-            // We use the time and time zone from the input calendar, but\r
-            // do not use the input calendar for field calculation.\r
-            calendar.setTimeInMillis(cal.getTimeInMillis());\r
-            backupTZ = calendar.getTimeZone();\r
-            calendar.setTimeZone(cal.getTimeZone());\r
-            cal = calendar;\r
-        }\r
-        StringBuffer result = format(cal, toAppendTo, pos, null);\r
-        if (backupTZ != null) {\r
-            // Restore the original time zone\r
-            calendar.setTimeZone(backupTZ);\r
-        }\r
-        return result;\r
-    }\r
-\r
-    // The actual method to format date. If List attributes is not null,\r
-    // then attribute information will be recorded.\r
-    private StringBuffer format(Calendar cal, StringBuffer toAppendTo,\r
-            FieldPosition pos, List attributes) {\r
-        // Initialize\r
-        pos.setBeginIndex(0);\r
-        pos.setEndIndex(0);\r
-\r
-        // Careful: For best performance, minimize the number of calls\r
-        // to StringBuffer.append() by consolidating appends when\r
-        // possible.\r
-\r
-        Object[] items = getPatternItems();\r
-        for (int i = 0; i < items.length; i++) {\r
-            if (items[i] instanceof String) {\r
-                toAppendTo.append((String)items[i]);\r
-            } else {\r
-                PatternItem item = (PatternItem)items[i];\r
-//#if defined(FOUNDATION10) || defined(J2SE13)\r
-//#else\r
-                int start = 0;\r
-                if (attributes != null) {\r
-                    // Save the current length\r
-                    start = toAppendTo.length();\r
-                }\r
-//#endif\r
-                if (useFastFormat) {\r
-                    subFormat(toAppendTo, item.type, item.length, toAppendTo.length(), pos, cal);\r
-                } else {\r
-                    toAppendTo.append(subFormat(item.type, item.length, toAppendTo.length(), pos, formatData, cal));\r
-                }\r
-//#if defined(FOUNDATION10) || defined(J2SE13)\r
-//#else\r
-                if (attributes != null) {\r
-                    // Check the sub format length\r
-                    int end = toAppendTo.length();\r
-                    if (end - start > 0) {\r
-                        // Append the attribute to the list\r
-                        DateFormat.Field attr = patternCharToDateFormatField(item.type);\r
-                        FieldPosition fp = new FieldPosition(attr);\r
-                        fp.setBeginIndex(start);\r
-                        fp.setEndIndex(end);\r
-                        attributes.add(fp);\r
-                    }\r
-                }\r
-//#endif\r
-            }\r
-        }\r
-        return toAppendTo;\r
-        \r
-    }\r
-\r
-    // Map pattern character to index\r
-    private static final int PATTERN_CHAR_BASE = 0x40;\r
-    private static final int[] PATTERN_CHAR_TO_INDEX =\r
-    {\r
-    //       A   B   C   D   E   F   G   H   I   J   K   L   M   N   O\r
-        -1, 22, -1, -1, 10,  9, 11,  0,  5, -1, -1, 16, 26,  2, -1, -1,\r
-    //   P   Q   R   S   T   U   V   W   X   Y   Z\r
-        -1, 27, -1,  8, -1, -1, 29, 13, -1, 18, 23, -1, -1, -1, -1, -1,\r
-    //       a   b   c   d   e   f   g   h   i   j   k   l   m   n   o\r
-        -1, 14, -1, 25,  3, 19, -1, 21, 15, -1, -1,  4, -1,  6, -1, -1,\r
-    //   p   q   r   s   t   u   v   w   x   y   z\r
-        -1, 28, -1,  7, -1, 20, 24, 12, -1,  1, 17, -1, -1, -1, -1, -1\r
-    };\r
-    \r
-    // Map pattern character index to Calendar field number\r
-    private static final int[] PATTERN_INDEX_TO_CALENDAR_FIELD =\r
-    {\r
-        /*GyM*/ Calendar.ERA, Calendar.YEAR, Calendar.MONTH,\r
-        /*dkH*/ Calendar.DATE, Calendar.HOUR_OF_DAY, Calendar.HOUR_OF_DAY,\r
-        /*msS*/ Calendar.MINUTE, Calendar.SECOND, Calendar.MILLISECOND,\r
-        /*EDF*/ Calendar.DAY_OF_WEEK, Calendar.DAY_OF_YEAR, Calendar.DAY_OF_WEEK_IN_MONTH,\r
-        /*wWa*/ Calendar.WEEK_OF_YEAR, Calendar.WEEK_OF_MONTH, Calendar.AM_PM,\r
-        /*hKz*/ Calendar.HOUR, Calendar.HOUR, Calendar.ZONE_OFFSET,\r
-        /*Yeu*/ Calendar.YEAR_WOY, Calendar.DOW_LOCAL, Calendar.EXTENDED_YEAR,\r
-        /*gAZ*/ Calendar.JULIAN_DAY, Calendar.MILLISECONDS_IN_DAY, Calendar.ZONE_OFFSET,\r
-        /*v*/   Calendar.ZONE_OFFSET,\r
-        /*c*/   Calendar.DOW_LOCAL,\r
-        /*L*/   Calendar.MONTH,\r
-        /*Qq*/  Calendar.MONTH, Calendar.MONTH,\r
-        /*V*/   Calendar.ZONE_OFFSET,\r
-    };\r
-\r
-    // Map pattern character index to DateFormat field number\r
-    private static final int[] PATTERN_INDEX_TO_DATE_FORMAT_FIELD = {\r
-        /*GyM*/ DateFormat.ERA_FIELD, DateFormat.YEAR_FIELD, DateFormat.MONTH_FIELD,\r
-        /*dkH*/ DateFormat.DATE_FIELD, DateFormat.HOUR_OF_DAY1_FIELD, DateFormat.HOUR_OF_DAY0_FIELD,\r
-        /*msS*/ DateFormat.MINUTE_FIELD, DateFormat.SECOND_FIELD, DateFormat.FRACTIONAL_SECOND_FIELD,\r
-        /*EDF*/ DateFormat.DAY_OF_WEEK_FIELD, DateFormat.DAY_OF_YEAR_FIELD, DateFormat.DAY_OF_WEEK_IN_MONTH_FIELD,\r
-        /*wWa*/ DateFormat.WEEK_OF_YEAR_FIELD, DateFormat.WEEK_OF_MONTH_FIELD, DateFormat.AM_PM_FIELD,\r
-        /*hKz*/ DateFormat.HOUR1_FIELD, DateFormat.HOUR0_FIELD, DateFormat.TIMEZONE_FIELD,\r
-        /*Yeu*/ DateFormat.YEAR_WOY_FIELD, DateFormat.DOW_LOCAL_FIELD, DateFormat.EXTENDED_YEAR_FIELD,\r
-        /*gAZ*/ DateFormat.JULIAN_DAY_FIELD, DateFormat.MILLISECONDS_IN_DAY_FIELD, DateFormat.TIMEZONE_RFC_FIELD,\r
-        /*v*/   DateFormat.TIMEZONE_GENERIC_FIELD, \r
-        /*c*/   DateFormat.STANDALONE_DAY_FIELD,\r
-        /*L*/   DateFormat.STANDALONE_MONTH_FIELD,\r
-        /*Qq*/  DateFormat.QUARTER_FIELD, DateFormat.STANDALONE_QUARTER_FIELD,\r
-        /*V*/   DateFormat.TIMEZONE_SPECIAL_FIELD, \r
-    };\r
-\r
-//#if defined(FOUNDATION10) || defined(J2SE13)\r
-//#else\r
-    // Map pattern character index to DateFormat.Field\r
-    private static final DateFormat.Field[] PATTERN_INDEX_TO_DATE_FORMAT_ATTRIBUTE = {\r
-        /*GyM*/ DateFormat.Field.ERA, DateFormat.Field.YEAR, DateFormat.Field.MONTH,\r
-        /*dkH*/ DateFormat.Field.DAY_OF_MONTH, DateFormat.Field.HOUR_OF_DAY1, DateFormat.Field.HOUR_OF_DAY0,\r
-        /*msS*/ DateFormat.Field.MINUTE, DateFormat.Field.SECOND, DateFormat.Field.MILLISECOND,\r
-        /*EDF*/ DateFormat.Field.DAY_OF_WEEK, DateFormat.Field.DAY_OF_YEAR, DateFormat.Field.DAY_OF_WEEK_IN_MONTH,\r
-        /*wWa*/ DateFormat.Field.WEEK_OF_YEAR, DateFormat.Field.WEEK_OF_MONTH, DateFormat.Field.AM_PM,\r
-        /*hKz*/ DateFormat.Field.HOUR1, DateFormat.Field.HOUR0, DateFormat.Field.TIME_ZONE,\r
-        /*Yeu*/ DateFormat.Field.YEAR_WOY, DateFormat.Field.DOW_LOCAL, DateFormat.Field.EXTENDED_YEAR,\r
-        /*gAZ*/ DateFormat.Field.JULIAN_DAY, DateFormat.Field.MILLISECONDS_IN_DAY, DateFormat.Field.TIME_ZONE,\r
-        /*v*/   DateFormat.Field.TIME_ZONE,\r
-        /*c*/   DateFormat.Field.DAY_OF_WEEK,\r
-        /*L*/   DateFormat.Field.MONTH,\r
-        /*Qq*/  DateFormat.Field.QUARTER, DateFormat.Field.QUARTER,\r
-        /*V*/   DateFormat.Field.TIME_ZONE,\r
-    };\r
-\r
-    /**\r
-     * Return a DateFormat.Field constant associated with the specified format pattern\r
-     * character.\r
-     * \r
-     * @param ch The pattern character\r
-     * @return DateFormat.Field associated with the pattern character\r
-     * \r
-     * @stable ICU 3.8\r
-     */\r
-    protected DateFormat.Field patternCharToDateFormatField(char ch) {\r
-        int patternCharIndex = -1;\r
-        if ('A' <= ch && ch <= 'z') {\r
-            patternCharIndex = PATTERN_CHAR_TO_INDEX[(int)ch - PATTERN_CHAR_BASE];\r
-        }\r
-        if (patternCharIndex != -1) {\r
-            return PATTERN_INDEX_TO_DATE_FORMAT_ATTRIBUTE[patternCharIndex];\r
-        }\r
-        return null;\r
-    }\r
-//#endif\r
-\r
-    /**\r
-     * Format a single field, given its pattern character.  Subclasses may\r
-     * override this method in order to modify or add formatting\r
-     * capabilities.\r
-     * @param ch the pattern character\r
-     * @param count the number of times ch is repeated in the pattern\r
-     * @param beginOffset the offset of the output string at the start of\r
-     * this field; used to set pos when appropriate\r
-     * @param pos receives the position of a field, when appropriate\r
-     * @param fmtData the symbols for this formatter\r
-     * @stable ICU 2.0\r
-     */\r
-    protected String subFormat(char ch, int count, int beginOffset,\r
-                               FieldPosition pos, DateFormatSymbols fmtData,\r
-                               Calendar cal)\r
-        throws IllegalArgumentException\r
-    {\r
-        // Note: formatData is ignored\r
-        StringBuffer buf = new StringBuffer();\r
-        subFormat(buf, ch, count, beginOffset, pos, cal);\r
-        return buf.toString();\r
-    }\r
-\r
-    /**\r
-     * Format a single field; useFastFormat variant.  Reuses a\r
-     * StringBuffer for results instead of creating a String on the\r
-     * heap for each call.\r
-     *\r
-     * NOTE We don't really need the beginOffset parameter, EXCEPT for\r
-     * the need to support the slow subFormat variant (above) which\r
-     * has to pass it in to us.\r
-     *\r
-     * TODO make this API public\r
-     *\r
-     * @internal\r
-     * @deprecated This API is ICU internal only.\r
-     */\r
-    protected void subFormat(StringBuffer buf,\r
-                             char ch, int count, int beginOffset,\r
-                             FieldPosition pos,\r
-                             Calendar cal) {\r
-        final int maxIntCount = Integer.MAX_VALUE;\r
-        final int bufstart = buf.length();\r
-\r
-        // final int patternCharIndex = DateFormatSymbols.patternChars.indexOf(ch);\r
-        int patternCharIndex = -1;\r
-        if ('A' <= ch && ch <= 'z') {\r
-            patternCharIndex = PATTERN_CHAR_TO_INDEX[(int)ch - PATTERN_CHAR_BASE];\r
-        }\r
-\r
-        if (patternCharIndex == -1) {\r
-            throw new IllegalArgumentException("Illegal pattern character " +\r
-                                               "'" + ch + "' in \"" +\r
-                                               new String(pattern) + '"');\r
-        }\r
-\r
-        final int field = PATTERN_INDEX_TO_CALENDAR_FIELD[patternCharIndex];\r
-        int value = cal.get(field);\r
-\r
-        String zoneString = null;\r
-\r
-        NumberFormat currentNumberFormat = getNumberFormat(ch);\r
-\r
-        switch (patternCharIndex) {\r
-        case 0: // 'G' - ERA\r
-            if (count == 5) {\r
-                safeAppend(formatData.narrowEras, value, buf);\r
-            } else if (count == 4) {\r
-                safeAppend(formatData.eraNames, value, buf);\r
-            } else {\r
-                safeAppend(formatData.eras, value, buf);\r
-            }\r
-            break;\r
-        case 1: // 'y' - YEAR\r
-            /* According to the specification, if the number of pattern letters ('y') is 2,\r
-             * the year is truncated to 2 digits; otherwise it is interpreted as a number.\r
-             * But the original code process 'y', 'yy', 'yyy' in the same way. and process\r
-             * patterns with 4 or more than 4 'y' characters in the same way.\r
-             * So I change the codes to meet the specification. [Richard/GCl]\r
-             */\r
-            if (count == 2)\r
-                zeroPaddingNumber(currentNumberFormat,buf, value, 2, 2); // clip 1996 to 96\r
-            else //count = 1 or count > 2\r
-                zeroPaddingNumber(currentNumberFormat,buf, value, count, maxIntCount);\r
-            break;\r
-        case 2: // 'M' - MONTH\r
-            if (count == 5) {\r
-                safeAppend(formatData.narrowMonths, value, buf);\r
-            } else if (count == 4) {\r
-                safeAppend(formatData.months, value, buf);\r
-            } else if (count == 3) {\r
-                safeAppend(formatData.shortMonths, value, buf);\r
-            } else {\r
-                zeroPaddingNumber(currentNumberFormat,buf, value+1, count, maxIntCount);\r
-            }\r
-            break;\r
-        case 4: // 'k' - HOUR_OF_DAY (1..24)\r
-            if (value == 0)\r
-                zeroPaddingNumber(currentNumberFormat,buf,\r
-                                  cal.getMaximum(Calendar.HOUR_OF_DAY)+1,\r
-                                  count, maxIntCount);\r
-            else\r
-                zeroPaddingNumber(currentNumberFormat,buf, value, count, maxIntCount);\r
-            break;\r
-        case 8: // 'S' - FRACTIONAL_SECOND\r
-            // Fractional seconds left-justify\r
-            {\r
-                numberFormat.setMinimumIntegerDigits(Math.min(3, count));\r
-                numberFormat.setMaximumIntegerDigits(maxIntCount);\r
-                if (count == 1) {\r
-                    value = (value + 50) / 100;\r
-                } else if (count == 2) {\r
-                    value = (value + 5) / 10;\r
-                }\r
-                FieldPosition p = new FieldPosition(-1);\r
-                numberFormat.format((long) value, buf, p);\r
-                if (count > 3) {\r
-                    numberFormat.setMinimumIntegerDigits(count - 3);\r
-                    numberFormat.format(0L, buf, p);\r
-                }\r
-            }\r
-            break;\r
-        case 19: // 'e' - DOW_LOCAL (use DOW_LOCAL for numeric, DAY_OF_WEEK for format names)\r
-            if (count < 3) {\r
-                zeroPaddingNumber(currentNumberFormat,buf, value, count, maxIntCount);\r
-                break;\r
-            }\r
-            // For alpha day-of-week, we don't want DOW_LOCAL,\r
-            // we need the standard DAY_OF_WEEK.\r
-            value = cal.get(Calendar.DAY_OF_WEEK);\r
-            // fall through, do not break here\r
-        case 9: // 'E' - DAY_OF_WEEK\r
-            if (count == 5) {\r
-                safeAppend(formatData.narrowWeekdays, value, buf);\r
-            } else if (count == 4) {\r
-                safeAppend(formatData.weekdays, value, buf);\r
-            } else {// count <= 3, use abbreviated form if exists\r
-                safeAppend(formatData.shortWeekdays, value, buf);\r
-            }\r
-            break;\r
-        case 14: // 'a' - AM_PM\r
-            safeAppend(formatData.ampms, value, buf);\r
-            break;\r
-        case 15: // 'h' - HOUR (1..12)\r
-            if (value == 0)\r
-                zeroPaddingNumber(currentNumberFormat,buf,\r
-                                  cal.getLeastMaximum(Calendar.HOUR)+1,\r
-                                  count, maxIntCount);\r
-            else\r
-                zeroPaddingNumber(currentNumberFormat,buf, value, count, maxIntCount);\r
-            break;\r
-        case 17: // 'z' - ZONE_OFFSET\r
-            if (count < 4) {\r
-                // "z", "zz", "zzz"\r
-                zoneString = formatData.getZoneStringFormat().getSpecificShortString(cal, true /* commonly used only */);\r
-            } else {\r
-                zoneString = formatData.getZoneStringFormat().getSpecificLongString(cal);\r
-            }\r
-            if (zoneString != null && zoneString.length() != 0) {\r
-                buf.append(zoneString);\r
-            } else {\r
-                // Use localized GMT format as fallback\r
-                appendGMT(currentNumberFormat,buf, cal);\r
-            }\r
-            break;\r
-        case 23: // 'Z' - TIMEZONE_RFC\r
-            if (count < 4) {\r
-                // RFC822 format, must use ASCII digits\r
-                int val = (cal.get(Calendar.ZONE_OFFSET) + cal.get(Calendar.DST_OFFSET));\r
-                char sign = '+';\r
-                if (val < 0) {\r
-                    val = -val;\r
-                    sign = '-';\r
-                }\r
-                buf.append(sign);\r
-\r
-                int offsetH = val / millisPerHour;\r
-                val = val % millisPerHour;\r
-                int offsetM = val / millisPerMinute;\r
-                val = val % millisPerMinute;\r
-                int offsetS = val / millisPerSecond;\r
-\r
-                int num = 0, denom = 0;\r
-                if (offsetS == 0) {\r
-                    val = offsetH*100 + offsetM; // HHmm\r
-                    num = val % 10000;\r
-                    denom = 1000;\r
-                } else {\r
-                    val = offsetH*10000 + offsetM*100 + offsetS; // HHmmss\r
-                    num = val % 1000000;\r
-                    denom = 100000;\r
-                }\r
-                while (denom >= 1) {\r
-                    char digit = (char)((num / denom) + '0');\r
-                    buf.append(digit);\r
-                    num = num % denom;\r
-                    denom /= 10;\r
-                }\r
-            } else {\r
-                // long form, localized GMT pattern\r
-                appendGMT(currentNumberFormat,buf, cal);\r
-            }\r
-            break;\r
-        case 24: // 'v' - TIMEZONE_GENERIC\r
-            if (count == 1) {\r
-                // "v"\r
-                zoneString = formatData.getZoneStringFormat().getGenericShortString(cal, true /* commonly used only */);\r
-            } else if (count == 4) {\r
-                // "vvvv"\r
-                zoneString = formatData.getZoneStringFormat().getGenericLongString(cal);\r
-            }\r
-            if (zoneString != null && zoneString.length() != 0) {\r
-                buf.append(zoneString);\r
-            } else {\r
-                // Use localized GMT format as fallback\r
-                appendGMT(currentNumberFormat,buf, cal);\r
-            }\r
-            break;\r
-        case 25: // 'c' - STANDALONE DAY (use DOW_LOCAL for numeric, DAY_OF_WEEK for standalone names)\r
-            if (count < 3) {\r
-                zeroPaddingNumber(currentNumberFormat,buf, value, 1, maxIntCount);\r
-                break;\r
-            }\r
-            // For alpha day-of-week, we don't want DOW_LOCAL,\r
-            // we need the standard DAY_OF_WEEK.\r
-            value = cal.get(Calendar.DAY_OF_WEEK);\r
-            if (count == 5) {\r
-                safeAppend(formatData.standaloneNarrowWeekdays, value, buf);\r
-            } else if (count == 4) {\r
-                safeAppend(formatData.standaloneWeekdays, value, buf);\r
-            } else { // count == 3\r
-                safeAppend(formatData.standaloneShortWeekdays, value, buf);\r
-            }\r
-            break;\r
-        case 26: // 'L' - STANDALONE MONTH\r
-            if (count == 5) {\r
-                safeAppend(formatData.standaloneNarrowMonths, value, buf);\r
-            } else if (count == 4) {\r
-                safeAppend(formatData.standaloneMonths, value, buf);\r
-            } else if (count == 3) {\r
-                safeAppend(formatData.standaloneShortMonths, value, buf);\r
-            } else {\r
-                zeroPaddingNumber(currentNumberFormat,buf, value+1, count, maxIntCount);\r
-            }\r
-            break;\r
-        case 27: // 'Q' - QUARTER\r
-            if (count >= 4) {\r
-                safeAppend(formatData.quarters, value/3, buf);\r
-            } else if (count == 3) {\r
-                safeAppend(formatData.shortQuarters, value/3, buf);\r
-            } else {\r
-                zeroPaddingNumber(currentNumberFormat,buf, (value/3)+1, count, maxIntCount);\r
-            }\r
-            break;\r
-        case 28: // 'q' - STANDALONE QUARTER\r
-            if (count >= 4) {\r
-                safeAppend(formatData.standaloneQuarters, value/3, buf);\r
-            } else if (count == 3) {\r
-                safeAppend(formatData.standaloneShortQuarters, value/3, buf);\r
-            } else {\r
-                zeroPaddingNumber(currentNumberFormat,buf, (value/3)+1, count, maxIntCount);\r
-            }\r
-            break;\r
-        case 29: // 'V' - TIMEZONE_SPECIAL\r
-            if (count == 1) {\r
-                // "V"\r
-                zoneString = formatData.getZoneStringFormat().getSpecificShortString(cal, false /* ignoring commonly used */);\r
-            } else if (count == 4) {\r
-                // "VVVV"\r
-                zoneString = formatData.getZoneStringFormat().getGenericLocationString(cal);\r
-            }\r
-            if (zoneString != null && zoneString.length() != 0) {\r
-                buf.append(zoneString);\r
-            } else {\r
-                // Use localized GMT format as fallback\r
-                appendGMT(currentNumberFormat,buf, cal);\r
-            }\r
-            break;\r
-        default:\r
-            // case 3: // 'd' - DATE\r
-            // case 5: // 'H' - HOUR_OF_DAY (0..23)\r
-            // case 6: // 'm' - MINUTE\r
-            // case 7: // 's' - SECOND\r
-            // case 10: // 'D' - DAY_OF_YEAR\r
-            // case 11: // 'F' - DAY_OF_WEEK_IN_MONTH\r
-            // case 12: // 'w' - WEEK_OF_YEAR\r
-            // case 13: // 'W' - WEEK_OF_MONTH\r
-            // case 16: // 'K' - HOUR (0..11)\r
-            // case 18: // 'Y' - YEAR_WOY\r
-            // case 20: // 'u' - EXTENDED_YEAR\r
-            // case 21: // 'g' - JULIAN_DAY\r
-            // case 22: // 'A' - MILLISECONDS_IN_DAY\r
-\r
-            zeroPaddingNumber(currentNumberFormat,buf, value, count, maxIntCount);\r
-            break;\r
-        } // switch (patternCharIndex)\r
-\r
-        // Set the FieldPosition (for the first occurrence only)\r
-        if (pos.getBeginIndex() == pos.getEndIndex()) {\r
-            if (pos.getField() == PATTERN_INDEX_TO_DATE_FORMAT_FIELD[patternCharIndex]) {\r
-                pos.setBeginIndex(beginOffset);\r
-                pos.setEndIndex(beginOffset + buf.length() - bufstart);\r
-            }\r
-//#if defined(FOUNDATION10) || defined(J2SE13)\r
-//#else\r
-            else if (pos.getFieldAttribute() == PATTERN_INDEX_TO_DATE_FORMAT_ATTRIBUTE[patternCharIndex]) {\r
-                pos.setBeginIndex(beginOffset);\r
-                pos.setEndIndex(beginOffset + buf.length() - bufstart);\r
-            }\r
-//#endif\r
-        }\r
-    }\r
-\r
-    private static void safeAppend(String[] array, int value, StringBuffer appendTo) {\r
-        if (array != null && value >= 0 && value < array.length) {\r
-            appendTo.append(array[value]);\r
-        }\r
-    }\r
-\r
-    /*\r
-     * PatternItem store parsed date/time field pattern information.\r
-     */\r
-    private static class PatternItem {\r
-        final char type;\r
-        final int length;\r
-        final boolean isNumeric;\r
-\r
-        PatternItem(char type, int length) {\r
-            this.type = type;\r
-            this.length = length;\r
-            isNumeric = isNumeric(type, length);\r
-        }\r
-    }\r
-\r
-    private static ICUCache PARSED_PATTERN_CACHE = new SimpleCache();\r
-    private transient Object[] patternItems;\r
-\r
-    /*\r
-     * Returns parsed pattern items.  Each item is either String or\r
-     * PatternItem.\r
-     */\r
-    private Object[] getPatternItems() {\r
-        if (patternItems != null) {\r
-            return patternItems;\r
-        }\r
-\r
-        patternItems = (Object[])PARSED_PATTERN_CACHE.get(pattern);\r
-        if (patternItems != null) {\r
-            return patternItems;\r
-        }\r
-\r
-        boolean isPrevQuote = false;\r
-        boolean inQuote = false;\r
-        StringBuffer text = new StringBuffer();\r
-        char itemType = 0;  // 0 for string literal, otherwise date/time pattern character\r
-        int itemLength = 1;\r
-\r
-        List items = new ArrayList();\r
-\r
-        for (int i = 0; i < pattern.length(); i++) {\r
-            char ch = pattern.charAt(i);\r
-            if (ch == '\'') {\r
-                if (isPrevQuote) {\r
-                    text.append('\'');\r
-                    isPrevQuote = false;\r
-                } else {\r
-                    isPrevQuote = true;\r
-                    if (itemType != 0) {\r
-                        items.add(new PatternItem(itemType, itemLength));\r
-                        itemType = 0;\r
-                    }\r
-                }\r
-                inQuote = !inQuote;\r
-            } else {\r
-                isPrevQuote = false;\r
-                if (inQuote) {\r
-                    text.append(ch);\r
-                } else {\r
-                    if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')) {\r
-                        // a date/time pattern character\r
-                        if (ch == itemType) {\r
-                            itemLength++;\r
-                        } else {\r
-                            if (itemType == 0) {\r
-                                if (text.length() > 0) {\r
-                                    items.add(text.toString());\r
-                                    text.setLength(0);\r
-                                }\r
-                            } else {\r
-                                items.add(new PatternItem(itemType, itemLength));\r
-                            }\r
-                            itemType = ch;\r
-                            itemLength = 1;\r
-                        }\r
-                    } else {\r
-                        // a string literal\r
-                        if (itemType != 0) {\r
-                            items.add(new PatternItem(itemType, itemLength));\r
-                            itemType = 0;\r
-                        }\r
-                        text.append(ch);\r
-                    }\r
-                }\r
-            }\r
-        }\r
-        // handle last item\r
-        if (itemType == 0) {\r
-            if (text.length() > 0) {\r
-                items.add(text.toString());\r
-                text.setLength(0);\r
-            }\r
-        } else {\r
-            items.add(new PatternItem(itemType, itemLength));\r
-        }\r
-\r
-        patternItems = new Object[items.size()];\r
-        items.toArray(patternItems);\r
-\r
-        PARSED_PATTERN_CACHE.put(pattern, patternItems);\r
-        \r
-        return patternItems;\r
-    }\r
-\r
-    /*\r
-     * Time zone localized GMT format stuffs\r
-     */\r
-    private static final String STR_GMT = "GMT";\r
-    private static final String STR_UT = "UT";\r
-    private static final String STR_UTC = "UTC";\r
-    private static final int STR_GMT_LEN = 3;\r
-    private static final int STR_UT_LEN = 2;\r
-    private static final int STR_UTC_LEN = 3;\r
-    private static final char PLUS = '+';\r
-    private static final char MINUS = '-';\r
-    private static final char COLON = ':';\r
-\r
-    private void appendGMT(NumberFormat currentNumberFormat,StringBuffer buf, Calendar cal) {\r
-        int offset = cal.get(Calendar.ZONE_OFFSET) + cal.get(Calendar.DST_OFFSET);\r
-\r
-        if (isDefaultGMTFormat()) {\r
-            formatGMTDefault(currentNumberFormat,buf, offset);\r
-        } else {\r
-            int sign = DateFormatSymbols.OFFSET_POSITIVE;\r
-            if (offset < 0) {\r
-                offset = -offset;\r
-                sign = DateFormatSymbols.OFFSET_NEGATIVE;\r
-            }\r
-            int width = offset%(60*1000) == 0 ? DateFormatSymbols.OFFSET_HM : DateFormatSymbols.OFFSET_HMS;\r
-\r
-            MessageFormat fmt = getGMTFormatter(sign, width);\r
-            fmt.format(new Object[] {new Long(offset)}, buf, null);\r
-        }\r
-    }\r
-\r
-    private void formatGMTDefault(NumberFormat currentNumberFormat,StringBuffer buf, int offset) {\r
-        buf.append(STR_GMT);\r
-        if (offset >= 0) {\r
-            buf.append(PLUS);\r
-        } else {\r
-            buf.append(MINUS);\r
-            offset = -offset;\r
-        }\r
-        offset /= 1000; // now in seconds\r
-        int sec = offset % 60;\r
-        offset /= 60;\r
-        int min = offset % 60;\r
-        int hour = offset / 60;\r
-\r
-        zeroPaddingNumber(currentNumberFormat,buf, hour, 2, 2);\r
-        buf.append(COLON);\r
-        zeroPaddingNumber(currentNumberFormat,buf, min, 2, 2);\r
-        if (sec != 0) {\r
-            buf.append(COLON);\r
-            zeroPaddingNumber(currentNumberFormat,buf, sec, 2, 2);\r
-        }\r
-    }\r
-\r
-    private Integer parseGMT(String text, ParsePosition pos, NumberFormat currentNumberFormat) {\r
-        if (!isDefaultGMTFormat()) {\r
-            int start = pos.getIndex();\r
-            String gmtPattern = formatData.gmtFormat;\r
-\r
-            // Quick check\r
-            boolean prefixMatch = false;\r
-            int prefixLen = gmtPattern.indexOf('{');\r
-            if (prefixLen > 0 && text.regionMatches(start, gmtPattern, 0, prefixLen)) {\r
-                prefixMatch = true;\r
-            }\r
-\r
-            if (prefixMatch) {\r
-                // Prefix matched\r
-                MessageFormat fmt;\r
-                Object[] parsedObjects;\r
-                int offset;\r
-\r
-                // Try negative Hms\r
-                fmt = getGMTFormatter(DateFormatSymbols.OFFSET_NEGATIVE, DateFormatSymbols.OFFSET_HMS);\r
-                parsedObjects = fmt.parse(text, pos);\r
-                if ((parsedObjects != null) && (parsedObjects[0] instanceof Date)\r
-                        && (pos.getIndex() - start) >= getGMTFormatMinHMSLen(DateFormatSymbols.OFFSET_NEGATIVE)) {\r
-                    offset = (int)((Date)parsedObjects[0]).getTime();\r
-                    return new Integer(-offset /* negative */);\r
-                }\r
-\r
-                // Reset ParsePosition\r
-                pos.setIndex(start);\r
-                pos.setErrorIndex(-1);\r
-\r
-                // Try positive Hms\r
-                fmt = getGMTFormatter(DateFormatSymbols.OFFSET_POSITIVE, DateFormatSymbols.OFFSET_HMS);\r
-                parsedObjects = fmt.parse(text, pos);\r
-                if ((parsedObjects != null) && (parsedObjects[0] instanceof Date)\r
-                        && (pos.getIndex() - start) >= getGMTFormatMinHMSLen(DateFormatSymbols.OFFSET_POSITIVE)) {\r
-                    offset = (int)((Date)parsedObjects[0]).getTime();\r
-                    return new Integer(offset);\r
-                }\r
-\r
-                // Reset ParsePosition\r
-                pos.setIndex(start);\r
-                pos.setErrorIndex(-1);\r
-\r
-                // Try negative Hm\r
-                fmt = getGMTFormatter(DateFormatSymbols.OFFSET_NEGATIVE, DateFormatSymbols.OFFSET_HM);\r
-                parsedObjects = fmt.parse(text, pos);\r
-                if ((parsedObjects != null) && (parsedObjects[0] instanceof Date)) {\r
-                    offset = (int)((Date)parsedObjects[0]).getTime();\r
-                    return new Integer(-offset /* negative */);\r
-                }\r
-\r
-                // Reset ParsePosition\r
-                pos.setIndex(start);\r
-                pos.setErrorIndex(-1);\r
-\r
-                // Try positive Hm\r
-                fmt = getGMTFormatter(DateFormatSymbols.OFFSET_POSITIVE, DateFormatSymbols.OFFSET_HM);\r
-                parsedObjects = fmt.parse(text, pos);\r
-                if ((parsedObjects != null) && (parsedObjects[0] instanceof Date)) {\r
-                    offset = (int)((Date)parsedObjects[0]).getTime();\r
-                    return new Integer(offset);\r
-                }\r
-\r
-                // Reset ParsePosition\r
-                pos.setIndex(start);\r
-                pos.setErrorIndex(-1);\r
-            }\r
-        }\r
-\r
-        return parseGMTDefault(text, pos, currentNumberFormat);\r
-    }\r
-\r
-    private Integer parseGMTDefault(String text, ParsePosition pos, NumberFormat currentNumberFormat) {\r
-        int start = pos.getIndex();\r
-\r
-        if (start + STR_UT_LEN + 1 >= text.length()) {\r
-            pos.setErrorIndex(start);\r
-            return null;\r
-        }\r
-\r
-        int cur = start;\r
-        // "GMT"\r
-        if (text.regionMatches(true, start, STR_GMT, 0, STR_GMT_LEN)) {\r
-            cur += STR_GMT_LEN;\r
-        } else if (text.regionMatches(true, start, STR_UT, 0, STR_UT_LEN)) {\r
-            cur += STR_UT_LEN;\r
-        } else {\r
-            pos.setErrorIndex(start);\r
-            return null;\r
-        }\r
-        // Sign\r
-        boolean negative = false;\r
-        if (text.charAt(cur) == MINUS) {\r
-            negative = true;\r
-        } else if (text.charAt(cur) != PLUS) {\r
-            pos.setErrorIndex(cur);\r
-            return null;\r
-        }\r
-        cur++;\r
-\r
-        // Numbers\r
-        int numLen;\r
-        pos.setIndex(cur);\r
-\r
-        Number n = parseInt(text, 6, pos, false,currentNumberFormat);\r
-        numLen = pos.getIndex() - cur;\r
-\r
-        if (n == null || numLen <= 0 || numLen > 6) {\r
-            pos.setIndex(start);\r
-            pos.setErrorIndex(cur);\r
-            return null;\r
-        }\r
-\r
-        int numVal = n.intValue();\r
-\r
-        int hour = 0;\r
-        int min = 0;\r
-        int sec = 0;\r
-\r
-        if (numLen <= 2) {\r
-            // H[H][:mm[:ss]]\r
-            hour = numVal;\r
-            cur += numLen;\r
-            if (cur + 2 < text.length() && text.charAt(cur) == COLON) {\r
-                cur++;\r
-                pos.setIndex(cur);\r
-                n = parseInt(text, 2, pos, false,currentNumberFormat);\r
-                numLen = pos.getIndex() - cur;\r
-                if (n != null && numLen == 2) {\r
-                    // got minute field\r
-                    min = n.intValue();\r
-                    cur += numLen;\r
-                    if (cur + 2 < text.length() && text.charAt(cur) == COLON) {\r
-                        cur++;\r
-                        pos.setIndex(cur);\r
-                        n = parseInt(text, 2, pos, false,currentNumberFormat);\r
-                        numLen = pos.getIndex() - cur;\r
-                        if (n != null && numLen == 2) {\r
-                            // got second field\r
-                            sec = n.intValue();\r
-                        } else {\r
-                            // reset position\r
-                            pos.setIndex(cur - 1);\r
-                            pos.setErrorIndex(-1);\r
-                        }\r
-                    }\r
-                } else {\r
-                    // reset postion\r
-                    pos.setIndex(cur - 1);\r
-                    pos.setErrorIndex(-1);\r
-                }\r
-            }\r
-        } else if (numLen == 3 || numLen == 4) {\r
-            // Hmm or HHmm\r
-            hour = numVal / 100;\r
-            min = numVal % 100;\r
-        } else { // numLen == 5 || numLen == 6\r
-            // Hmmss or HHmmss\r
-            hour = numVal / 10000;\r
-            min = (numVal % 10000) / 100;\r
-            sec = numVal % 100;\r
-        }\r
-\r
-        int offset = ((hour*60 + min)*60 + sec)*1000;\r
-        if (negative) {\r
-            offset = -offset;\r
-        }\r
-        return new Integer(offset);\r
-    }\r
-\r
-    transient private WeakReference[] gmtfmtCache;\r
-\r
-    private MessageFormat getGMTFormatter(int sign, int width) {\r
-        MessageFormat fmt = null;\r
-        if (gmtfmtCache == null) {\r
-            gmtfmtCache = new WeakReference[4];\r
-        }\r
-        int cacheIdx = sign*2 + width;\r
-        if (gmtfmtCache[cacheIdx] != null) {\r
-            fmt = (MessageFormat)gmtfmtCache[cacheIdx].get();\r
-        }\r
-        if (fmt == null) {\r
-            fmt = new MessageFormat(formatData.gmtFormat);\r
-            GregorianCalendar gcal = new GregorianCalendar(TimeZone.getTimeZone("Etc/UTC"));\r
-            SimpleDateFormat sdf = (SimpleDateFormat)this.clone();\r
-            sdf.setCalendar(gcal);\r
-            sdf.applyPattern(formatData.getGmtHourFormat(sign, width));\r
-            fmt.setFormat(0, sdf);\r
-            gmtfmtCache[cacheIdx] = new WeakReference(fmt);\r
-        }\r
-        return fmt;\r
-    }\r
-\r
-    transient private int[] gmtFormatHmsMinLen = null;\r
-\r
-    private int getGMTFormatMinHMSLen(int sign) {\r
-        if (gmtFormatHmsMinLen == null) {\r
-            gmtFormatHmsMinLen = new int[2];\r
-            Long offset = new Long(60*60*1000); // 1 hour\r
-\r
-            StringBuffer buf = new StringBuffer();\r
-            MessageFormat fmtNeg = getGMTFormatter(DateFormatSymbols.OFFSET_NEGATIVE, DateFormatSymbols.OFFSET_HMS);\r
-            fmtNeg.format(new Object[] {offset}, buf, null);\r
-            gmtFormatHmsMinLen[0] = buf.length();\r
-\r
-            buf.setLength(0);\r
-            MessageFormat fmtPos = getGMTFormatter(DateFormatSymbols.OFFSET_POSITIVE, DateFormatSymbols.OFFSET_HMS);\r
-            fmtPos.format(new Object[] {offset}, buf, null);\r
-            gmtFormatHmsMinLen[1] = buf.length();\r
-        }\r
-        return gmtFormatHmsMinLen[(sign < 0 ? 0 : 1)];\r
-    }\r
-\r
-    private boolean isDefaultGMTFormat() {\r
-        // GMT pattern\r
-        if (!DateFormatSymbols.DEFAULT_GMT_PATTERN.equals(formatData.getGmtFormat())) {\r
-            return false;\r
-        }\r
-        // GMT offset hour patters\r
-        boolean res = true;\r
-        for (int sign = 0; sign < 2 && res; sign++) {\r
-            for (int width = 0; width < 2; width++) {\r
-                if (!DateFormatSymbols.DEFAULT_GMT_HOUR_PATTERNS[sign][width].equals(formatData.getGmtHourFormat(sign, width))) {\r
-                    res = false;\r
-                    break;\r
-                }\r
-            }\r
-        }\r
-        return res;\r
-    }\r
-\r
-    /*\r
-     * Internal method. Returns null if the value of an array is empty, or if the\r
-     * index is out of bounds\r
-     */\r
-/*    private String getZoneArrayValue(String[] zs, int ix) {\r
-        if (ix >= 0 && ix < zs.length) {\r
-            String result = zs[ix];\r
-            if (result != null && result.length() != 0) {\r
-                return result;\r
-            }\r
-        }\r
-        return null;\r
-    }*/\r
-\r
-    /**\r
-     * Internal high-speed method.  Reuses a StringBuffer for results\r
-     * instead of creating a String on the heap for each call.\r
-     * @internal\r
-     * @deprecated This API is ICU internal only.\r
-     */\r
-    protected void zeroPaddingNumber(NumberFormat nf,StringBuffer buf, int value,\r
-                                     int minDigits, int maxDigits) {\r
-        if (useLocalZeroPaddingNumberFormat) {\r
-            fastZeroPaddingNumber(buf, value, minDigits, maxDigits);\r
-        } else {\r
-            nf.setMinimumIntegerDigits(minDigits);\r
-            nf.setMaximumIntegerDigits(maxDigits);\r
-            nf.format(value, buf, new FieldPosition(-1));\r
-        }\r
-    }\r
-\r
-    /**\r
-     * Overrides superclass method\r
-     * @stable ICU 2.0\r
-     */\r
-    public void setNumberFormat(NumberFormat newNumberFormat) {\r
-        // Override this method to update local zero padding number formatter\r
-        super.setNumberFormat(newNumberFormat);\r
-        initLocalZeroPaddingNumberFormat();\r
-    }\r
-\r
-    private void initLocalZeroPaddingNumberFormat() {\r
-        if (numberFormat instanceof DecimalFormat) {\r
-            zeroDigit = ((DecimalFormat)numberFormat).getDecimalFormatSymbols().getZeroDigit();\r
-            useLocalZeroPaddingNumberFormat = true;\r
-        } else if (numberFormat instanceof DateNumberFormat) {\r
-            zeroDigit = ((DateNumberFormat)numberFormat).getZeroDigit();\r
-            useLocalZeroPaddingNumberFormat = true;\r
-        } else {\r
-            useLocalZeroPaddingNumberFormat = false;\r
-        }\r
-\r
-        if (useLocalZeroPaddingNumberFormat) {\r
-            decimalBuf = new char[10];  // sufficient for int numbers\r
-        }\r
-    }\r
-\r
-    // If true, use local version of zero padding number format\r
-    private transient boolean useLocalZeroPaddingNumberFormat;\r
-    private transient char zeroDigit;\r
-    private transient char[] decimalBuf;\r
-\r
-    /*\r
-     * Lightweight zero padding integer number format function.\r
-     * \r
-     * Note: This implementation is almost equivalent to format method in DateNumberFormat.\r
-     * In the method zeroPaddingNumber above should be able to use the one in DateNumberFormat,\r
-     * but, it does not help IBM J9's JIT to optimize the performance much.  In simple repeative\r
-     * date format test case, having local implementation is ~10% faster than using one in\r
-     * DateNumberFormat on IBM J9 VM.  On Sun Hotspot VM, I do not see such difference.\r
-     * \r
-     * -Yoshito\r
-     */\r
-    private void fastZeroPaddingNumber(StringBuffer buf, int value, int minDigits, int maxDigits) {\r
-        int limit = decimalBuf.length < maxDigits ? decimalBuf.length : maxDigits;\r
-        int index = limit - 1;\r
-        while (true) {\r
-            decimalBuf[index] = (char)((value % 10) + zeroDigit);\r
-            value /= 10;\r
-            if (index == 0 || value == 0) {\r
-                break;\r
-            }\r
-            index--;\r
-        }\r
-        int padding = minDigits - (limit - index);\r
-        for (; padding > 0; padding--) {\r
-            decimalBuf[--index] = zeroDigit;\r
-        }\r
-        int length = limit - index;\r
-        buf.append(decimalBuf, index, length);        \r
-    }\r
-\r
-    /**\r
-     * Formats a number with the specified minimum and maximum number of digits.\r
-     * @stable ICU 2.0\r
-     */\r
-    protected String zeroPaddingNumber(long value, int minDigits, int maxDigits)\r
-    {\r
-        numberFormat.setMinimumIntegerDigits(minDigits);\r
-        numberFormat.setMaximumIntegerDigits(maxDigits);\r
-        return numberFormat.format(value);\r
-    }\r
-\r
-    /**\r
-     * Format characters that indicate numeric fields.  The character\r
-     * at index 0 is treated specially.\r
-     */\r
-    private static final String NUMERIC_FORMAT_CHARS = "MYyudehHmsSDFwWkK";\r
-\r
-    /**\r
-     * Return true if the given format character, occuring count\r
-     * times, represents a numeric field.\r
-     */\r
-    private static final boolean isNumeric(char formatChar, int count) {\r
-        int i = NUMERIC_FORMAT_CHARS.indexOf(formatChar);\r
-        return (i > 0 || (i == 0 && count < 3));\r
-    }\r
-\r
-    /**\r
-     * Overrides DateFormat\r
-     * @see DateFormat\r
-     * @stable ICU 2.0\r
-     */\r
-    public void parse(String text, Calendar cal, ParsePosition parsePos)\r
-    {\r
-        TimeZone backupTZ = null;\r
-        Calendar resultCal = null;\r
-        if (cal != calendar && !cal.getType().equals(calendar.getType())) {\r
-            // Different calendar type\r
-            // We use the time/zone from the input calendar, but\r
-            // do not use the input calendar for field calculation.\r
-            calendar.setTimeInMillis(cal.getTimeInMillis());\r
-            backupTZ = calendar.getTimeZone();\r
-            calendar.setTimeZone(cal.getTimeZone());\r
-            resultCal = cal;\r
-            cal = calendar;\r
-        }\r
-\r
-        int pos = parsePos.getIndex();\r
-        int start = pos;\r
-\r
-        // Reset tztype\r
-        tztype = TZTYPE_UNK;\r
-        boolean[] ambiguousYear = { false };\r
-\r
-        // item index for the first numeric field within a contiguous numeric run\r
-        int numericFieldStart = -1;\r
-        // item length for the first numeric field within a contiguous numeric run\r
-        int numericFieldLength = 0;\r
-        // start index of numeric text run in the input text\r
-        int numericStartPos = 0;\r
-\r
-        Object[] items = getPatternItems();\r
-        int i = 0;\r
-        while (i < items.length) {\r
-            if (items[i] instanceof PatternItem) {\r
-                // Handle pattern field\r
-                PatternItem field = (PatternItem)items[i];\r
-                if (field.isNumeric) {\r
-                    // Handle fields within a run of abutting numeric fields.  Take\r
-                    // the pattern "HHmmss" as an example. We will try to parse\r
-                    // 2/2/2 characters of the input text, then if that fails,\r
-                    // 1/2/2.  We only adjust the width of the leftmost field; the\r
-                    // others remain fixed.  This allows "123456" => 12:34:56, but\r
-                    // "12345" => 1:23:45.  Likewise, for the pattern "yyyyMMdd" we\r
-                    // try 4/2/2, 3/2/2, 2/2/2, and finally 1/2/2.\r
-                    if (numericFieldStart == -1) {\r
-                        // check if this field is followed by abutting another numeric field\r
-                        if ((i + 1) < items.length \r
-                                && (items[i + 1] instanceof PatternItem)\r
-                                && ((PatternItem)items[i + 1]).isNumeric) {\r
-                            // record the first numeric field within a numeric text run\r
-                            numericFieldStart = i;\r
-                            numericFieldLength = field.length;\r
-                            numericStartPos = pos; \r
-                        }\r
-                    }\r
-                }\r
-                if (numericFieldStart != -1) {\r
-                    // Handle a numeric field within abutting numeric fields\r
-                    int len = field.length;\r
-                    if (numericFieldStart == i) {\r
-                        len = numericFieldLength;\r
-                    }\r
-\r
-                    // Parse a numeric field\r
-                    pos = subParse(text, pos, field.type, len,\r
-                            true, false, ambiguousYear, cal);\r
-\r
-                    if (pos < 0) {\r
-                        // If the parse fails anywhere in the numeric run, back up to the\r
-                        // start of the run and use shorter pattern length for the first\r
-                        // numeric field.\r
-                        --numericFieldLength;\r
-                        if (numericFieldLength == 0) {\r
-                            // can not make shorter any more\r
-                            parsePos.setIndex(start);\r
-                            parsePos.setErrorIndex(pos);\r
-                            if (backupTZ != null) {\r
-                                calendar.setTimeZone(backupTZ);\r
-                            }\r
-                            return;\r
-                        }\r
-                        i = numericFieldStart;\r
-                        pos = numericStartPos;\r
-                        continue;\r
-                    }\r
-\r
-                } else {\r
-                    // Handle a non-numeric field or a non-abutting numeric field\r
-                    numericFieldStart = -1;\r
-\r
-                    int s = pos;\r
-                    pos = subParse(text, pos, field.type, field.length,\r
-                            false, true, ambiguousYear, cal);\r
-                    if (pos < 0) {\r
-                        parsePos.setIndex(start);\r
-                        parsePos.setErrorIndex(s);\r
-                        if (backupTZ != null) {\r
-                            calendar.setTimeZone(backupTZ);\r
-                        }\r
-                        return;\r
-                    }\r
-                }\r
-            } else {\r
-                // Handle literal pattern text literal\r
-                numericFieldStart = -1;\r
-\r
-                String patl = (String)items[i];\r
-                int plen = patl.length();\r
-                int tlen = text.length();\r
-                int idx = 0;\r
-                while (idx < plen && pos < tlen) {\r
-                    char pch = patl.charAt(idx);\r
-                    char ich = text.charAt(pos);\r
-                    if (UCharacterProperty.isRuleWhiteSpace(pch) && UCharacterProperty.isRuleWhiteSpace(ich)) {\r
-                        // White space characters found in both patten and input.\r
-                        // Skip contiguous white spaces.\r
-                        while ((idx + 1) < plen &&\r
-                                UCharacterProperty.isRuleWhiteSpace(patl.charAt(idx + 1))) {\r
-                             ++idx;\r
-                        }\r
-                        while ((pos + 1) < tlen &&\r
-                                UCharacterProperty.isRuleWhiteSpace(text.charAt(pos + 1))) {\r
-                             ++pos;\r
-                        }\r
-                    } else if (pch != ich) {\r
-                        break;\r
-                    }\r
-                    ++idx;\r
-                    ++pos;\r
-                }\r
-                if (idx != plen) {\r
-                    // Set the position of mismatch\r
-                    parsePos.setIndex(start);\r
-                    parsePos.setErrorIndex(pos);\r
-                    if (backupTZ != null) {\r
-                        calendar.setTimeZone(backupTZ);\r
-                    }\r
-                    return;\r
-                }\r
-            }\r
-            ++i;\r
-        }\r
-\r
-        // At this point the fields of Calendar have been set.  Calendar\r
-        // will fill in default values for missing fields when the time\r
-        // is computed.\r
-\r
-        parsePos.setIndex(pos);\r
-\r
-        // This part is a problem:  When we call parsedDate.after, we compute the time.\r
-        // Take the date April 3 2004 at 2:30 am.  When this is first set up, the year\r
-        // will be wrong if we're parsing a 2-digit year pattern.  It will be 1904.\r
-        // April 3 1904 is a Sunday (unlike 2004) so it is the DST onset day.  2:30 am\r
-        // is therefore an "impossible" time, since the time goes from 1:59 to 3:00 am\r
-        // on that day.  It is therefore parsed out to fields as 3:30 am.  Then we\r
-        // add 100 years, and get April 3 2004 at 3:30 am.  Note that April 3 2004 is\r
-        // a Saturday, so it can have a 2:30 am -- and it should. [LIU]\r
-        /*\r
-          Date parsedDate = cal.getTime();\r
-          if( ambiguousYear[0] && !parsedDate.after(getDefaultCenturyStart()) ) {\r
-          cal.add(Calendar.YEAR, 100);\r
-          parsedDate = cal.getTime();\r
-          }\r
-        */\r
-        // Because of the above condition, save off the fields in case we need to readjust.\r
-        // The procedure we use here is not particularly efficient, but there is no other\r
-        // way to do this given the API restrictions present in Calendar.  We minimize\r
-        // inefficiency by only performing this computation when it might apply, that is,\r
-        // when the two-digit year is equal to the start year, and thus might fall at the\r
-        // front or the back of the default century.  This only works because we adjust\r
-        // the year correctly to start with in other cases -- see subParse().\r
-        try {\r
-            if (ambiguousYear[0] || tztype != TZTYPE_UNK) {\r
-                // We need a copy of the fields, and we need to avoid triggering a call to\r
-                // complete(), which will recalculate the fields.  Since we can't access\r
-                // the fields[] array in Calendar, we clone the entire object.  This will\r
-                // stop working if Calendar.clone() is ever rewritten to call complete().\r
-                Calendar copy;\r
-                if (ambiguousYear[0]) { // the two-digit year == the default start year\r
-                    copy = (Calendar)cal.clone();\r
-                    Date parsedDate = copy.getTime();\r
-                    if (parsedDate.before(getDefaultCenturyStart())) {\r
-                        // We can't use add here because that does a complete() first.\r
-                        cal.set(Calendar.YEAR, getDefaultCenturyStartYear() + 100);\r
-                    }\r
-                }\r
-                if (tztype != TZTYPE_UNK) {\r
-                    copy = (Calendar)cal.clone();\r
-                    TimeZone tz = copy.getTimeZone();\r
-                    BasicTimeZone btz = null;\r
-                    if (tz instanceof BasicTimeZone) {\r
-                        btz = (BasicTimeZone)tz;\r
-                    }\r
-\r
-                    // Get local millis\r
-                    copy.set(Calendar.ZONE_OFFSET, 0);\r
-                    copy.set(Calendar.DST_OFFSET, 0);\r
-                    long localMillis = copy.getTimeInMillis();\r
-\r
-                    // Make sure parsed time zone type (Standard or Daylight)\r
-                    // matches the rule used by the parsed time zone.\r
-                    int[] offsets = new int[2];\r
-                    if (btz != null) {\r
-                        if (tztype == TZTYPE_STD) {\r
-                            btz.getOffsetFromLocal(localMillis,\r
-                                    BasicTimeZone.LOCAL_STD, BasicTimeZone.LOCAL_STD, offsets);\r
-                        } else {\r
-                            btz.getOffsetFromLocal(localMillis,\r
-                                    BasicTimeZone.LOCAL_DST, BasicTimeZone.LOCAL_DST, offsets);\r
-                        }\r
-                    } else {\r
-                        // No good way to resolve ambiguous time at transition,\r
-                        // but following code work in most case.\r
-                        tz.getOffset(localMillis, true, offsets);\r
-\r
-                        if (tztype == TZTYPE_STD && offsets[1] != 0 || tztype == TZTYPE_DST && offsets[1] == 0) {\r
-                            // Roll back one day and try it again.\r
-                            // Note: This code assumes 1. timezone transition only happens once within 24 hours at max\r
-                            // 2. the difference of local offsets at the transition is less than 24 hours.\r
-                            tz.getOffset(localMillis - (24*60*60*1000), true, offsets);\r
-                        }\r
-                    }\r
-\r
-                    // Now, compare the results with parsed type, either standard or daylight saving time\r
-                    int resolvedSavings = offsets[1];\r
-                    if (tztype == TZTYPE_STD) {\r
-                        if (offsets[1] != 0) {\r
-                            // Override DST_OFFSET = 0 in the result calendar\r
-                            resolvedSavings = 0;\r
-                        }\r
-                    } else { // tztype == TZTYPE_DST\r
-                        if (offsets[1] == 0) {\r
-                            if (btz != null) {\r
-                                long time = localMillis + offsets[0];\r
-                                // We use the nearest daylight saving time rule.\r
-                                TimeZoneTransition beforeTrs, afterTrs;\r
-                                long beforeT = time, afterT = time;\r
-                                int beforeSav = 0, afterSav = 0;\r
-\r
-                                // Search for DST rule before or on the time\r
-                                while (true) {\r
-                                    beforeTrs = btz.getPreviousTransition(beforeT, true);\r
-                                    if (beforeTrs == null) {\r
-                                        break;\r
-                                    }\r
-                                    beforeT = beforeTrs.getTime() - 1;\r
-                                    beforeSav = beforeTrs.getFrom().getDSTSavings();\r
-                                    if (beforeSav != 0) {\r
-                                        break;\r
-                                    }\r
-                                }\r
-\r
-                                // Search for DST rule after the time\r
-                                while (true) {\r
-                                    afterTrs = btz.getNextTransition(afterT, false);\r
-                                    if (afterTrs == null) {\r
-                                        break;\r
-                                    }\r
-                                    afterT = afterTrs.getTime();\r
-                                    afterSav = afterTrs.getTo().getDSTSavings();\r
-                                    if (afterSav != 0) {\r
-                                        break;\r
-                                    }\r
-                                }\r
-\r
-                                if (beforeTrs != null && afterTrs != null) {\r
-                                    if (time - beforeT > afterT - time) {\r
-                                        resolvedSavings = afterSav;\r
-                                    } else {\r
-                                        resolvedSavings = beforeSav;\r
-                                    }\r
-                                } else if (beforeTrs != null && beforeSav != 0) {\r
-                                    resolvedSavings = beforeSav;\r
-                                } else if (afterTrs != null && afterSav != 0) {\r
-                                    resolvedSavings = afterSav;\r
-                                } else {\r
-                                    resolvedSavings = btz.getDSTSavings();\r
-                                }\r
-                            } else {\r
-                                resolvedSavings = tz.getDSTSavings();\r
-                            }\r
-                            if (resolvedSavings == 0) {\r
-                                // Final fallback\r
-                                resolvedSavings = millisPerHour;\r
-                            }\r
-                        }\r
-                    }\r
-                    cal.set(Calendar.ZONE_OFFSET, offsets[0]);\r
-                    cal.set(Calendar.DST_OFFSET, resolvedSavings);\r
-                }\r
-            }\r
-        }\r
-        // An IllegalArgumentException will be thrown by Calendar.getTime()\r
-        // if any fields are out of range, e.g., MONTH == 17.\r
-        catch (IllegalArgumentException e) {\r
-            parsePos.setErrorIndex(pos);\r
-            parsePos.setIndex(start);\r
-            if (backupTZ != null) {\r
-                calendar.setTimeZone(backupTZ);\r
-            }\r
-            return;\r
-        }\r
-        // Set the parsed result if local calendar is used\r
-        // instead of the input calendar\r
-        if (resultCal != null) {\r
-            resultCal.setTimeZone(cal.getTimeZone());\r
-            resultCal.setTimeInMillis(cal.getTimeInMillis());\r
-        }\r
-        // Restore the original time zone if required\r
-        if (backupTZ != null) {\r
-            calendar.setTimeZone(backupTZ);\r
-        }\r
-    }\r
-\r
-    /**\r
-     * Attempt to match the text at a given position against an array of\r
-     * strings.  Since multiple strings in the array may match (for\r
-     * example, if the array contains "a", "ab", and "abc", all will match\r
-     * the input string "abcd") the longest match is returned.  As a side\r
-     * effect, the given field of <code>cal</code> is set to the index\r
-     * of the best match, if there is one.\r
-     * @param text the time text being parsed.\r
-     * @param start where to start parsing.\r
-     * @param field the date field being parsed.\r
-     * @param data the string array to parsed.\r
-     * @return the new start position if matching succeeded; a negative\r
-     * number indicating matching failure, otherwise.  As a side effect,\r
-     * sets the <code>cal</code> field <code>field</code> to the index\r
-     * of the best match, if matching succeeded.\r
-     * @stable ICU 2.0\r
-     */\r
-    protected int matchString(String text, int start, int field, String[] data, Calendar cal)\r
-    {\r
-        int i = 0;\r
-        int count = data.length;\r
-\r
-        if (field == Calendar.DAY_OF_WEEK) i = 1;\r
-\r
-        // There may be multiple strings in the data[] array which begin with\r
-        // the same prefix (e.g., Cerven and Cervenec (June and July) in Czech).\r
-        // We keep track of the longest match, and return that.  Note that this\r
-        // unfortunately requires us to test all array elements.\r
-        int bestMatchLength = 0, bestMatch = -1;\r
-        for (; i<count; ++i)\r
-            {\r
-                int length = data[i].length();\r
-                // Always compare if we have no match yet; otherwise only compare\r
-                // against potentially better matches (longer strings).\r
-                if (length > bestMatchLength &&\r
-                    text.regionMatches(true, start, data[i], 0, length))\r
-                    {\r
-                        bestMatch = i;\r
-                        bestMatchLength = length;\r
-                    }\r
-            }\r
-        if (bestMatch >= 0)\r
-            {\r
-                cal.set(field, bestMatch);\r
-                return start + bestMatchLength;\r
-            }\r
-        return -start;\r
-    }\r
-\r
-    /**\r
-     * Attempt to match the text at a given position against an array of quarter\r
-     * strings.  Since multiple strings in the array may match (for\r
-     * example, if the array contains "a", "ab", and "abc", all will match\r
-     * the input string "abcd") the longest match is returned.  As a side\r
-     * effect, the given field of <code>cal</code> is set to the index\r
-     * of the best match, if there is one.\r
-     * @param text the time text being parsed.\r
-     * @param start where to start parsing.\r
-     * @param field the date field being parsed.\r
-     * @param data the string array to parsed.\r
-     * @return the new start position if matching succeeded; a negative\r
-     * number indicating matching failure, otherwise.  As a side effect,\r
-     * sets the <code>cal</code> field <code>field</code> to the index\r
-     * of the best match, if matching succeeded.\r
-     * @stable ICU 2.0\r
-     */\r
-    protected int matchQuarterString(String text, int start, int field, String[] data, Calendar cal)\r
-    {\r
-        int i = 0;\r
-        int count = data.length;\r
-\r
-        // There may be multiple strings in the data[] array which begin with\r
-        // the same prefix (e.g., Cerven and Cervenec (June and July) in Czech).\r
-        // We keep track of the longest match, and return that.  Note that this\r
-        // unfortunately requires us to test all array elements.\r
-        int bestMatchLength = 0, bestMatch = -1;\r
-        for (; i<count; ++i) {\r
-            int length = data[i].length();\r
-            // Always compare if we have no match yet; otherwise only compare\r
-            // against potentially better matches (longer strings).\r
-            if (length > bestMatchLength &&\r
-                text.regionMatches(true, start, data[i], 0, length)) {\r
-                bestMatch = i;\r
-                bestMatchLength = length;\r
-            }\r
-        }\r
-        \r
-        if (bestMatch >= 0) {\r
-            cal.set(field, bestMatch * 3);\r
-            return start + bestMatchLength;\r
-        }\r
-        \r
-        return -start;\r
-    }\r
-    \r
-    /**\r
-     * Protected method that converts one field of the input string into a\r
-     * numeric field value in <code>cal</code>.  Returns -start (for\r
-     * ParsePosition) if failed.  Subclasses may override this method to\r
-     * modify or add parsing capabilities.\r
-     * @param text the time text to be parsed.\r
-     * @param start where to start parsing.\r
-     * @param ch the pattern character for the date field text to be parsed.\r
-     * @param count the count of a pattern character.\r
-     * @param obeyCount if true, then the next field directly abuts this one,\r
-     * and we should use the count to know when to stop parsing.\r
-     * @param ambiguousYear return parameter; upon return, if ambiguousYear[0]\r
-     * is true, then a two-digit year was parsed and may need to be readjusted.\r
-     * @return the new start position if matching succeeded; a negative\r
-     * number indicating matching failure, otherwise.  As a side effect,\r
-     * set the appropriate field of <code>cal</code> with the parsed\r
-     * value.\r
-     * @stable ICU 2.0\r
-     */\r
-    protected int subParse(String text, int start, char ch, int count,\r
-                           boolean obeyCount, boolean allowNegative,\r
-                           boolean[] ambiguousYear, Calendar cal)\r
-    {\r
-        Number number = null;\r
-        NumberFormat currentNumberFormat = null;\r
-        int value = 0;\r
-        int i;\r
-        ParsePosition pos = new ParsePosition(0);\r
-        //int patternCharIndex = DateFormatSymbols.patternChars.indexOf(ch);c\r
-        int patternCharIndex = -1;\r
-        if ('A' <= ch && ch <= 'z') {\r
-            patternCharIndex = PATTERN_CHAR_TO_INDEX[(int)ch - PATTERN_CHAR_BASE];\r
-        }\r
-\r
-        if (patternCharIndex == -1) {\r
-            return -start;\r
-        }\r
-\r
-        currentNumberFormat = getNumberFormat(ch);\r
\r
-        int field = PATTERN_INDEX_TO_CALENDAR_FIELD[patternCharIndex];\r
-\r
-        // If there are any spaces here, skip over them.  If we hit the end\r
-        // of the string, then fail.\r
-        for (;;) {\r
-            if (start >= text.length()) {\r
-                return -start;\r
-            }\r
-            int c = UTF16.charAt(text, start);\r
-            if (!UCharacter.isUWhiteSpace(c)) {\r
-                break;\r
-            }\r
-            start += UTF16.getCharCount(c);\r
-        }\r
-        pos.setIndex(start);\r
-\r
-        // We handle a few special cases here where we need to parse\r
-        // a number value.  We handle further, more generic cases below.  We need\r
-        // to handle some of them here because some fields require extra processing on\r
-        // the parsed value.\r
-        if (patternCharIndex == 4 /*HOUR_OF_DAY1_FIELD*/ ||\r
-            patternCharIndex == 15 /*HOUR1_FIELD*/ ||\r
-            (patternCharIndex == 2 /*MONTH_FIELD*/ && count <= 2) ||\r
-            patternCharIndex == 1 ||\r
-            patternCharIndex == 8)\r
-            {\r
-                // It would be good to unify this with the obeyCount logic below,\r
-                // but that's going to be difficult.\r
-                if (obeyCount)\r
-                    {\r
-                        if ((start+count) > text.length()) return -start;\r
-                        number = parseInt(text, count, pos, allowNegative,currentNumberFormat);\r
-                    }\r
-                else number = parseInt(text, pos, allowNegative,currentNumberFormat);\r
-                if (number == null)\r
-                    return -start;\r
-                value = number.intValue();\r
-            }\r
-\r
-        switch (patternCharIndex)\r
-            {\r
-            case 0: // 'G' - ERA\r
-                if (count == 4) {\r
-                    return matchString(text, start, Calendar.ERA, formatData.eraNames, cal);\r
-                } else {\r
-                    return matchString(text, start, Calendar.ERA, formatData.eras, cal);\r
-                }\r
-            case 1: // 'y' - YEAR\r
-                // If there are 3 or more YEAR pattern characters, this indicates\r
-                // that the year value is to be treated literally, without any\r
-                // two-digit year adjustments (e.g., from "01" to 2001).  Otherwise\r
-                // we made adjustments to place the 2-digit year in the proper\r
-                // century, for parsed strings from "00" to "99".  Any other string\r
-                // is treated literally:  "2250", "-1", "1", "002".\r
-                /* 'yy' is the only special case, 'y' is interpreted as number. [Richard/GCL]*/\r
-                if (count == 2 && (pos.getIndex() - start) == 2\r
-                    && UCharacter.isDigit(text.charAt(start))\r
-                    && UCharacter.isDigit(text.charAt(start+1)))\r
-                    {\r
-                        // Assume for example that the defaultCenturyStart is 6/18/1903.\r
-                        // This means that two-digit years will be forced into the range\r
-                        // 6/18/1903 to 6/17/2003.  As a result, years 00, 01, and 02\r
-                        // correspond to 2000, 2001, and 2002.  Years 04, 05, etc. correspond\r
-                        // to 1904, 1905, etc.  If the year is 03, then it is 2003 if the\r
-                        // other fields specify a date before 6/18, or 1903 if they specify a\r
-                        // date afterwards.  As a result, 03 is an ambiguous year.  All other\r
-                        // two-digit years are unambiguous.\r
-                        int ambiguousTwoDigitYear = getDefaultCenturyStartYear() % 100;\r
-                        ambiguousYear[0] = value == ambiguousTwoDigitYear;\r
-                        value += (getDefaultCenturyStartYear()/100)*100 +\r
-                            (value < ambiguousTwoDigitYear ? 100 : 0);\r
-                    }\r
-                cal.set(Calendar.YEAR, value);\r
-                return pos.getIndex();\r
-            case 2: // 'M' - MONTH\r
-                if (count <= 2) // i.e., M or MM.\r
-                    {\r
-                        // Don't want to parse the month if it is a string\r
-                        // while pattern uses numeric style: M or MM.\r
-                        // [We computed 'value' above.]\r
-                        cal.set(Calendar.MONTH, value - 1);\r
-                        return pos.getIndex();\r
-                    }\r
-                else\r
-                    {\r
-                        // count >= 3 // i.e., MMM or MMMM\r
-                        // Want to be able to parse both short and long forms.\r
-                        // Try count == 4 first:\r
-                        int newStart = matchString(text, start, Calendar.MONTH,\r
-                                                   formatData.months, cal);\r
-                        if (newStart > 0) {\r
-                            return newStart;\r
-                        } else { // count == 4 failed, now try count == 3\r
-                            return matchString(text, start, Calendar.MONTH,\r
-                                               formatData.shortMonths, cal);\r
-                        }\r
-                    }\r
-            case 26: // 'L' - STAND_ALONE_MONTH\r
-                if (count <= 2) // i.e., M or MM.\r
-                    {\r
-                        // Don't want to parse the month if it is a string\r
-                        // while pattern uses numeric style: M or MM.\r
-                        // [We computed 'value' above.]\r
-                        cal.set(Calendar.MONTH, value - 1);\r
-                        return pos.getIndex();\r
-                    }\r
-                else\r
-                    {\r
-                        // count >= 3 // i.e., MMM or MMMM\r
-                        // Want to be able to parse both short and long forms.\r
-                        // Try count == 4 first:\r
-                        int newStart = matchString(text, start, Calendar.MONTH,\r
-                                                   formatData.standaloneMonths, cal);\r
-                        if (newStart > 0) {\r
-                            return newStart;\r
-                        } else { // count == 4 failed, now try count == 3\r
-                            return matchString(text, start, Calendar.MONTH,\r
-                                               formatData.standaloneShortMonths, cal);\r
-                        }\r
-                    }\r
-            case 4: // 'k' - HOUR_OF_DAY (1..24)\r
-                // [We computed 'value' above.]\r
-                if (value == cal.getMaximum(Calendar.HOUR_OF_DAY)+1) value = 0;\r
-                cal.set(Calendar.HOUR_OF_DAY, value);\r
-                return pos.getIndex();\r
-            case 8: // 'S' - FRACTIONAL_SECOND\r
-                // Fractional seconds left-justify\r
-                i = pos.getIndex() - start;\r
-                if (i < 3) {\r
-                    while (i < 3) {\r
-                        value *= 10;\r
-                        i++;\r
-                    }\r
-                } else {\r
-                    int a = 1;\r
-                    while (i > 3) {\r
-                        a *= 10;\r
-                        i--;\r
-                    }\r
-                    value = (value + (a>>1)) / a;\r
-                }\r
-                cal.set(Calendar.MILLISECOND, value);\r
-                return pos.getIndex();\r
-            case 9: { // 'E' - DAY_OF_WEEK\r
-                // Want to be able to parse both short and long forms.\r
-                // Try count == 4 (EEEE) first:\r
-                int newStart = matchString(text, start, Calendar.DAY_OF_WEEK,\r
-                                           formatData.weekdays, cal);\r
-                if (newStart > 0) {\r
-                    return newStart;\r
-                } else { // EEEE failed, now try EEE\r
-                    return matchString(text, start, Calendar.DAY_OF_WEEK,\r
-                                       formatData.shortWeekdays, cal);\r
-                }\r
-            }\r
-            case 25: { // 'c' - STAND_ALONE_DAY_OF_WEEK\r
-                // Want to be able to parse both short and long forms.\r
-                // Try count == 4 (cccc) first:\r
-                int newStart = matchString(text, start, Calendar.DAY_OF_WEEK,\r
-                                           formatData.standaloneWeekdays, cal);\r
-                if (newStart > 0) {\r
-                    return newStart;\r
-                } else { // cccc failed, now try ccc\r
-                    return matchString(text, start, Calendar.DAY_OF_WEEK,\r
-                                       formatData.standaloneShortWeekdays, cal);\r
-                }\r
-            }\r
-            case 14: // 'a' - AM_PM\r
-                return matchString(text, start, Calendar.AM_PM, formatData.ampms, cal);\r
-            case 15: // 'h' - HOUR (1..12)\r
-                // [We computed 'value' above.]\r
-                if (value == cal.getLeastMaximum(Calendar.HOUR)+1) value = 0;\r
-                cal.set(Calendar.HOUR, value);\r
-                return pos.getIndex();\r
-            case 17: // 'z' - ZONE_OFFSET\r
-            case 23: // 'Z' - TIMEZONE_RFC\r
-            case 24: // 'v' - TIMEZONE_GENERIC\r
-            case 29: // 'V' - TIMEZONE_SPECIAL\r
-                {\r
-                    TimeZone tz = null;\r
-                    int offset = 0;\r
-                    boolean parsed = false;\r
-\r
-                    // Step 1\r
-                    // Check if this is a long GMT offset string (either localized or default)\r
-                    Integer gmtoff = parseGMT(text, pos, currentNumberFormat);\r
-                    if (gmtoff != null) {\r
-                        offset = gmtoff.intValue();\r
-                        parsed = true;\r
-                    }\r
-\r
-                    if (!parsed) {\r
-                        // Step 2\r
-                        // Check if this is an RFC822 time zone offset.\r
-                        // ICU supports the standard RFC822 format [+|-]HHmm\r
-                        // and its extended form [+|-]HHmmSS.\r
-                        \r
-                        do {\r
-                            int sign = 0;\r
-                            char signChar = text.charAt(start);\r
-                            if (signChar == '+') {\r
-                                sign = 1;\r
-                            } else if (signChar == '-') {\r
-                                sign = -1;\r
-                            } else {\r
-                                // Not an RFC822 offset string\r
-                                break;\r
-                            }\r
-\r
-                            // Parse digits\r
-                            int orgPos = start + 1;\r
-                            pos.setIndex(orgPos);\r
-                            number = parseInt(text, 6, pos, false,currentNumberFormat);\r
-                            int numLen = pos.getIndex() - orgPos;\r
-                            if (numLen <= 0) {\r
-                                break;\r
-                            }\r
-\r
-                            // Followings are possible format (excluding sign char)\r
-                            // HHmmSS\r
-                            // HmmSS\r
-                            // HHmm\r
-                            // Hmm\r
-                            // HH\r
-                            // H\r
-                            int val = number.intValue();\r
-                            int hour = 0, min = 0, sec = 0;\r
-                            switch(numLen) {\r
-                            case 1: // H\r
-                            case 2: // HH\r
-                                hour = val;\r
-                                break;\r
-                            case 3: // Hmm\r
-                            case 4: // HHmm\r
-                                hour = val / 100;\r
-                                min = val % 100;\r
-                                break;\r
-                            case 5: // Hmmss\r
-                            case 6: // HHmmss\r
-                                hour = val / 10000;\r
-                                min = (val % 10000) / 100;\r
-                                sec = val % 100;\r
-                                break;\r
-                            }\r
-                            if (hour > 23 || min > 59 || sec > 59) {\r
-                                // Invalid value range\r
-                                break;\r
-                            }\r
-                            offset = (((hour * 60) + min) * 60 + sec) * 1000 * sign;\r
-                            parsed = true;\r
-                        } while (false);\r
-\r
-                        if (!parsed) {\r
-                            // Failed to parse.  Reset the position.\r
-                            pos.setIndex(start);\r
-                        }\r
-                    }\r
-\r
-                    if (parsed) {\r
-                        // offset was successfully parsed as either a long GMT string or RFC822 zone offset\r
-                        // string.  Create normalized zone ID for the offset.\r
-                        tz = ZoneMeta.getCustomTimeZone(offset);\r
-                        cal.setTimeZone(tz);\r
-                        return pos.getIndex();\r
-                    }\r
-\r
-                    // Step 3\r
-                    // At this point, check for named time zones by looking through\r
-                    // the locale data from the DateFormatZoneData strings.\r
-                    // Want to be able to parse both short and long forms.\r
-                    // optimize for calendar's current time zone\r
-                    ZoneStringInfo zsinfo = null;\r
-                    switch (patternCharIndex) {\r
-                    case 17: // 'z' - ZONE_OFFSET\r
-                        if (count < 4) {\r
-                            zsinfo = formatData.getZoneStringFormat().findSpecificShort(text, start);\r
-                        } else {\r
-                            zsinfo = formatData.getZoneStringFormat().findSpecificLong(text, start);\r
-                        }\r
-                        break;\r
-                    case 24: // 'v' - TIMEZONE_GENERIC\r
-                        if (count == 1) {\r
-                            zsinfo = formatData.getZoneStringFormat().findGenericShort(text, start);\r
-                        } else if (count == 4) {\r
-                            zsinfo = formatData.getZoneStringFormat().findGenericLong(text, start);\r
-                        }\r
-                        break;\r
-                    case 29: // 'V' - TIMEZONE_SPECIAL\r
-                        if (count == 1) {\r
-                            zsinfo = formatData.getZoneStringFormat().findSpecificShort(text, start);\r
-                        } else if (count == 4) {\r
-                            zsinfo = formatData.getZoneStringFormat().findGenericLocation(text, start);\r
-                        }\r
-                        break;\r
-                    }\r
-                    if (zsinfo != null) {\r
-                        if (zsinfo.isStandard()) {\r
-                            tztype = TZTYPE_STD;\r
-                        } else if (zsinfo.isDaylight()) {\r
-                            tztype = TZTYPE_DST;\r
-                        }\r
-                        tz = TimeZone.getTimeZone(zsinfo.getID());\r
-                        cal.setTimeZone(tz);\r
-                        return start + zsinfo.getString().length();\r
-                    }\r
-                    // Step 4\r
-                    // Final attempt - is this standalone GMT/UT/UTC?\r
-                    int gmtLen = 0;\r
-                    if (text.regionMatches(true, start, STR_GMT, 0, STR_GMT_LEN)) {\r
-                        gmtLen = STR_GMT_LEN;\r
-                    } else if (text.regionMatches(true, start, STR_UTC, 0, STR_UTC_LEN)) {\r
-                        gmtLen = STR_UTC_LEN;\r
-                    } else if (text.regionMatches(true, start, STR_UT, 0, STR_UT_LEN)) {\r
-                        gmtLen = STR_UT_LEN;\r
-                    }\r
-                    if (gmtLen > 0) {\r
-                        tz = TimeZone.getTimeZone("Etc/GMT");\r
-                        cal.setTimeZone(tz);\r
-                        return start + gmtLen;\r
-                    }\r
-\r
-                    // complete failure\r
-                    return -start;\r
-                }\r
-\r
-            case 27: // 'Q' - QUARTER\r
-                if (count <= 2) // i.e., Q or QQ.\r
-                {\r
-                    // Don't want to parse the quarter if it is a string\r
-                    // while pattern uses numeric style: Q or QQ.\r
-                    // [We computed 'value' above.]\r
-                    cal.set(Calendar.MONTH, (value - 1) * 3);\r
-                    return pos.getIndex();\r
-                }\r
-            else\r
-                {\r
-                    // count >= 3 // i.e., QQQ or QQQQ\r
-                    // Want to be able to parse both short and long forms.\r
-                    // Try count == 4 first:\r
-                    int newStart = matchQuarterString(text, start, Calendar.MONTH,\r
-                                               formatData.quarters, cal);\r
-                    if (newStart > 0) {\r
-                        return newStart;\r
-                    } else { // count == 4 failed, now try count == 3\r
-                        return matchQuarterString(text, start, Calendar.MONTH,\r
-                                           formatData.shortQuarters, cal);\r
-                    }\r
-                }\r
-                \r
-            case 28: // 'q' - STANDALONE QUARTER\r
-                if (count <= 2) // i.e., q or qq.\r
-                {\r
-                    // Don't want to parse the quarter if it is a string\r
-                    // while pattern uses numeric style: q or qq.\r
-                    // [We computed 'value' above.]\r
-                    cal.set(Calendar.MONTH, (value - 1) * 3);\r
-                    return pos.getIndex();\r
-                }\r
-            else\r
-                {\r
-                    // count >= 3 // i.e., qqq or qqqq\r
-                    // Want to be able to parse both short and long forms.\r
-                    // Try count == 4 first:\r
-                    int newStart = matchQuarterString(text, start, Calendar.MONTH,\r
-                                               formatData.standaloneQuarters, cal);\r
-                    if (newStart > 0) {\r
-                        return newStart;\r
-                    } else { // count == 4 failed, now try count == 3\r
-                        return matchQuarterString(text, start, Calendar.MONTH,\r
-                                           formatData.standaloneShortQuarters, cal);\r
-                    }\r
-                }\r
-\r
-            default:\r
-                // case 3: // 'd' - DATE\r
-                // case 5: // 'H' - HOUR_OF_DAY (0..23)\r
-                // case 6: // 'm' - MINUTE\r
-                // case 7: // 's' - SECOND\r
-                // case 10: // 'D' - DAY_OF_YEAR\r
-                // case 11: // 'F' - DAY_OF_WEEK_IN_MONTH\r
-                // case 12: // 'w' - WEEK_OF_YEAR\r
-                // case 13: // 'W' - WEEK_OF_MONTH\r
-                // case 16: // 'K' - HOUR (0..11)\r
-                // case 18: // 'Y' - YEAR_WOY\r
-                // case 19: // 'e' - DOW_LOCAL\r
-                // case 20: // 'u' - EXTENDED_YEAR\r
-                // case 21: // 'g' - JULIAN_DAY\r
-                // case 22: // 'A' - MILLISECONDS_IN_DAY\r
-\r
-                // Handle "generic" fields\r
-                if (obeyCount)\r
-                    {\r
-                        if ((start+count) > text.length()) return -start;\r
-                        number = parseInt(text, count, pos, allowNegative,currentNumberFormat);\r
-                    }\r
-                else number = parseInt(text, pos, allowNegative,currentNumberFormat);\r
-                if (number != null) {\r
-                    cal.set(field, number.intValue());\r
-                    return pos.getIndex();\r
-                }\r
-                return -start;\r
-            }\r
-    }\r
-\r
-    /**\r
-     * Parse an integer using numberFormat.  This method is semantically\r
-     * const, but actually may modify fNumberFormat.\r
-     */\r
-    private Number parseInt(String text,\r
-                            ParsePosition pos,\r
-                            boolean allowNegative,\r
-                            NumberFormat fmt) {\r
-        return parseInt(text, -1, pos, allowNegative, fmt);\r
-    }\r
-    \r
-    /**\r
-     * Parse an integer using numberFormat up to maxDigits.\r
-     */\r
-    private Number parseInt(String text,\r
-                            int maxDigits,\r
-                            ParsePosition pos,\r
-                            boolean allowNegative,\r
-                            NumberFormat fmt) {\r
-        Number number;\r
-        int oldPos = pos.getIndex();\r
-        if (allowNegative) {\r
-            number = fmt.parse(text, pos);\r
-        } else {\r
-            // Invalidate negative numbers\r
-            if (fmt instanceof DecimalFormat) {\r
-                String oldPrefix = ((DecimalFormat)fmt).getNegativePrefix();\r
-                ((DecimalFormat)fmt).setNegativePrefix(SUPPRESS_NEGATIVE_PREFIX);\r
-                number = fmt.parse(text, pos);\r
-                ((DecimalFormat)fmt).setNegativePrefix(oldPrefix);\r
-            } else {\r
-                boolean dateNumberFormat = (fmt instanceof DateNumberFormat);\r
-                if (dateNumberFormat) {\r
-                    ((DateNumberFormat)fmt).setParsePositiveOnly(true);\r
-                }\r
-                number = fmt.parse(text, pos);                \r
-                if (dateNumberFormat) {\r
-                    ((DateNumberFormat)fmt).setParsePositiveOnly(false);\r
-                }\r
-            }\r
-        }\r
-        if (maxDigits > 0) {\r
-            // adjust the result to fit into\r
-            // the maxDigits and move the position back\r
-            int nDigits = pos.getIndex() - oldPos;\r
-            if (nDigits > maxDigits) {\r
-                double val = number.doubleValue();\r
-                nDigits -= maxDigits;\r
-                while (nDigits > 0) {\r
-                    val /= 10;\r
-                    nDigits--;\r
-                }\r
-                pos.setIndex(oldPos + maxDigits);\r
-                number = new Integer((int)val);\r
-            }\r
-        }\r
-        return number;\r
-    }\r
-\r
-    \r
-    /**\r
-     * Translate a pattern, mapping each character in the from string to the\r
-     * corresponding character in the to string.\r
-     */\r
-    private String translatePattern(String pat, String from, String to) {\r
-        StringBuffer result = new StringBuffer();\r
-        boolean inQuote = false;\r
-        for (int i = 0; i < pat.length(); ++i) {\r
-            char c = pat.charAt(i);\r
-            if (inQuote) {\r
-                if (c == '\'')\r
-                    inQuote = false;\r
-            }\r
-            else {\r
-                if (c == '\'')\r
-                    inQuote = true;\r
-                else if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) {\r
-                    int ci = from.indexOf(c);\r
-                    if (ci != -1) {\r
-                        c = to.charAt(ci);\r
-                    }\r
-                    // do not worry on translatepattern if the character is not listed\r
-                    // we do the validity check elsewhere\r
-                }\r
-            }\r
-            result.append(c);\r
-        }\r
-        if (inQuote)\r
-            throw new IllegalArgumentException("Unfinished quote in pattern");\r
-        return result.toString();\r
-    }\r
-\r
-    /**\r
-     * Return a pattern string describing this date format.\r
-     * @stable ICU 2.0\r
-     */\r
-    public String toPattern() {\r
-        return pattern;\r
-    }\r
-\r
-    /**\r
-     * Return a localized pattern string describing this date format.\r
-     * @stable ICU 2.0\r
-     */\r
-    public String toLocalizedPattern() {\r
-        return translatePattern(pattern,\r
-                                DateFormatSymbols.patternChars,\r
-                                formatData.localPatternChars);\r
-    }\r
-\r
-    /**\r
-     * Apply the given unlocalized pattern string to this date format.\r
-     * @stable ICU 2.0\r
-     */\r
-    public void applyPattern(String pat)\r
-    {\r
-        this.pattern = pat;\r
-        setLocale(null, null);\r
-        // reset parsed pattern items\r
-        patternItems = null;\r
-    }\r
-\r
-    /**\r
-     * Apply the given localized pattern string to this date format.\r
-     * @stable ICU 2.0\r
-     */\r
-    public void applyLocalizedPattern(String pat) {\r
-        this.pattern = translatePattern(pat,\r
-                                        formatData.localPatternChars,\r
-                                        DateFormatSymbols.patternChars);\r
-        setLocale(null, null);\r
-    }\r
-\r
-    /**\r
-     * Gets the date/time formatting data.\r
-     * @return a copy of the date-time formatting data associated\r
-     * with this date-time formatter.\r
-     * @stable ICU 2.0\r
-     */\r
-    public DateFormatSymbols getDateFormatSymbols()\r
-    {\r
-        return (DateFormatSymbols)formatData.clone();\r
-    }\r
-\r
-    /**\r
-     * Allows you to set the date/time formatting data.\r
-     * @param newFormatSymbols the new symbols\r
-     * @stable ICU 2.0\r
-     */\r
-    public void setDateFormatSymbols(DateFormatSymbols newFormatSymbols)\r
-    {\r
-        this.formatData = (DateFormatSymbols)newFormatSymbols.clone();\r
-        gmtfmtCache = null;\r
-    }\r
-\r
-    /**\r
-     * Method for subclasses to access the DateFormatSymbols.\r
-     * @stable ICU 2.0\r
-     */\r
-    protected DateFormatSymbols getSymbols() {\r
-        return formatData;\r
-    }\r
-\r
-    /**\r
-     * Overrides Cloneable\r
-     * @stable ICU 2.0\r
-     */\r
-    public Object clone() {\r
-        SimpleDateFormat other = (SimpleDateFormat) super.clone();\r
-        other.formatData = (DateFormatSymbols) formatData.clone();\r
-        return other;\r
-    }\r
-\r
-    /**\r
-     * Override hashCode.\r
-     * Generates the hash code for the SimpleDateFormat object\r
-     * @stable ICU 2.0\r
-     */\r
-    public int hashCode()\r
-    {\r
-        return pattern.hashCode();\r
-        // just enough fields for a reasonable distribution\r
-    }\r
-\r
-    /**\r
-     * Override equals.\r
-     * @stable ICU 2.0\r
-     */\r
-    public boolean equals(Object obj)\r
-    {\r
-        if (!super.equals(obj)) return false; // super does class check\r
-        SimpleDateFormat that = (SimpleDateFormat) obj;\r
-        return (pattern.equals(that.pattern)\r
-                && formatData.equals(that.formatData));\r
-    }\r
-\r
-    /**\r
-     * Override writeObject.\r
-     */\r
-    private void writeObject(ObjectOutputStream stream) throws IOException{\r
-        if (defaultCenturyStart == null) {\r
-            // if defaultCenturyStart is not yet initialized,\r
-            // calculate and set value before serialization.\r
-            initializeDefaultCenturyStart(defaultCenturyBase);\r
-        }\r
-        stream.defaultWriteObject();\r
-    }\r
-    \r
-    /**\r
-     * Override readObject.\r
-     */\r
-    private void readObject(ObjectInputStream stream)\r
-        throws IOException, ClassNotFoundException {\r
-        stream.defaultReadObject();\r
-        ///CLOVER:OFF\r
-        // don't have old serial data to test with\r
-        if (serialVersionOnStream < 1) {\r
-            // didn't have defaultCenturyStart field\r
-            defaultCenturyBase = System.currentTimeMillis();\r
-        }\r
-        ///CLOVER:ON\r
-        else {\r
-            // fill in dependent transient field\r
-            parseAmbiguousDatesAsAfter(defaultCenturyStart);\r
-        }\r
-        serialVersionOnStream = currentSerialVersion;\r
-        locale = getLocale(ULocale.VALID_LOCALE);\r
-\r
-        initLocalZeroPaddingNumberFormat();\r
-    }\r
-\r
-//#if defined(FOUNDATION10) || defined(J2SE13)\r
-//#else\r
-    /**\r
-     * Format the object to an attributed string, and return the corresponding iterator\r
-     * Overrides superclass method.\r
-     * \r
-     * @param obj The object to format\r
-     * @return <code>AttributedCharacterIterator</code> describing the formatted value.\r
-     * \r
-     * @stable ICU 3.8\r
-     */\r
-    public AttributedCharacterIterator formatToCharacterIterator(Object obj) {\r
-        Calendar cal = calendar;\r
-        if (obj instanceof Calendar) {\r
-            cal = (Calendar)obj;\r
-        } else if (obj instanceof Date) {\r
-            calendar.setTime((Date)obj);\r
-        } else if (obj instanceof Number) {\r
-            calendar.setTimeInMillis(((Number)obj).longValue());\r
-        } else { \r
-            throw new IllegalArgumentException("Cannot format given Object as a Date");\r
-        }\r
-        StringBuffer toAppendTo = new StringBuffer();\r
-        FieldPosition pos = new FieldPosition(0);\r
-        List attributes = new LinkedList();\r
-        format(cal, toAppendTo, pos, attributes);\r
-\r
-        AttributedString as = new AttributedString(toAppendTo.toString());\r
-        \r
-        // add DateFormat field attributes to the AttributedString\r
-        for (int i = 0; i < attributes.size(); i++) {\r
-            FieldPosition fp = (FieldPosition) attributes.get(i);\r
-            Format.Field attribute = fp.getFieldAttribute();\r
-            as.addAttribute(attribute, attribute, fp.getBeginIndex(), fp.getEndIndex());\r
-        }\r
-        // return the CharacterIterator from AttributedString\r
-        return as.getIterator();\r
-    }\r
-//#endif\r
-\r
-\r
-    /**\r
-     * Get the locale of this simple date formatter.\r
-     * It is package accessible. also used in DateIntervalFormat.\r
-     *\r
-     * @return   locale in this simple date formatter\r
-     */\r
-    ULocale getLocale() \r
-    {\r
-        return locale;\r
-    }\r
-\r
-\r
-    \r
-    /**\r
-     * Check whether the 'field' is smaller than all the fields covered in\r
-     * pattern, return true if it is.\r
-     * The sequence of calendar field,\r
-     * from large to small is: ERA, YEAR, MONTH, DATE, AM_PM, HOUR, MINUTE,...\r
-     * @param field    the calendar field need to check against\r
-     * @return         true if the 'field' is smaller than all the fields \r
-     *                 covered in pattern. false otherwise.\r
-     */\r
-\r
-    boolean isFieldUnitIgnored(int field) {\r
-        return isFieldUnitIgnored(pattern, field);\r
-    }\r
-\r
-\r
-    /*\r
-     * Check whether the 'field' is smaller than all the fields covered in\r
-     * pattern, return true if it is.\r
-     * The sequence of calendar field,\r
-     * from large to small is: ERA, YEAR, MONTH, DATE, AM_PM, HOUR, MINUTE,...\r
-     * @param pattern  the pattern to check against\r
-     * @param field    the calendar field need to check against\r
-     * @return         true if the 'field' is smaller than all the fields \r
-     *                 covered in pattern. false otherwise.\r
-     * @internal ICU 4.0\r
-     */\r
-    static boolean isFieldUnitIgnored(String pattern, int field) {\r
-        int fieldLevel = CALENDAR_FIELD_TO_LEVEL[field];\r
-        int level;\r
-        char ch;\r
-        boolean inQuote = false;\r
-        char prevCh = 0;\r
-        int count = 0;\r
-    \r
-        for (int i = 0; i < pattern.length(); ++i) {\r
-            ch = pattern.charAt(i);\r
-            if (ch != prevCh && count > 0) {\r
-                level = PATTERN_CHAR_TO_LEVEL[prevCh - PATTERN_CHAR_BASE];\r
-                if ( fieldLevel <= level ) {\r
-                    return false;\r
-                }\r
-                count = 0;\r
-            }\r
-            if (ch == '\'') {\r
-                if ((i+1) < pattern.length() && pattern.charAt(i+1) == '\'') {\r
-                    ++i;\r
-                } else {\r
-                    inQuote = ! inQuote;\r
-                }\r
-            } \r
-            else if ( ! inQuote && ((ch >= 0x0061 /*'a'*/ && ch <= 0x007A /*'z'*/) \r
-                        || (ch >= 0x0041 /*'A'*/ && ch <= 0x005A /*'Z'*/))) {\r
-                prevCh = ch;\r
-                ++count;\r
-            }\r
-        }\r
-        if ( count > 0 ) {\r
-            // last item\r
-            level = PATTERN_CHAR_TO_LEVEL[prevCh - PATTERN_CHAR_BASE];\r
-                if ( fieldLevel <= level ) {\r
-                    return false;\r
-                }\r
-        }\r
-        return true;\r
-    }\r
-\r
-\r
-    /**\r
-     * Format date interval by algorithm. \r
-     * It is supposed to be used only by CLDR survey tool.\r
-     *\r
-     * @param fromCalendar      calendar set to the from date in date interval\r
-     *                          to be formatted into date interval stirng\r
-     * @param toCalendar        calendar set to the to date in date interval\r
-     *                          to be formatted into date interval stirng\r
-     * @param appendTo          Output parameter to receive result.\r
-     *                          Result is appended to existing contents.\r
-     * @param pos               On input: an alignment field, if desired.\r
-     *                          On output: the offsets of the alignment field.\r
-     * @exception IllegalArgumentException when there is non-recognized\r
-     *                                     pattern letter\r
-     * @return                  Reference to 'appendTo' parameter.\r
-     * @internal ICU 4.0\r
-     */\r
-    public final StringBuffer intervalFormatByAlgorithm(Calendar fromCalendar,\r
-                                                        Calendar toCalendar,\r
-                                                        StringBuffer appendTo,\r
-                                                        FieldPosition pos)\r
-                              throws IllegalArgumentException\r
-    {\r
-        // not support different calendar types and time zones\r
-        if ( !fromCalendar.isEquivalentTo(toCalendar) ) {\r
-            throw new IllegalArgumentException("can not format on two different calendars");\r
-        }\r
-     \r
-        Object[] items = getPatternItems();\r
-        int diffBegin = -1;\r
-        int diffEnd = -1;\r
-\r
-        /* look for different formatting string range */\r
-        // look for start of difference\r
-        try {\r
-            for (int i = 0; i < items.length; i++) {\r
-                if ( diffCalFieldValue(fromCalendar, toCalendar, items, i) ) {\r
-                    diffBegin = i;\r
-                    break;\r
-                }\r
-            } \r
-        \r
-            if ( diffBegin == -1 ) { \r
-                // no difference, single date format\r
-                return format(fromCalendar, appendTo, pos);\r
-            }\r
-    \r
-            // look for end of difference\r
-            for (int i = items.length-1; i >= diffBegin; i--) {\r
-                if ( diffCalFieldValue(fromCalendar, toCalendar, items, i) ) {\r
-                    diffEnd = i;\r
-                    break;\r
-                }\r
-            }\r
-        } catch ( IllegalArgumentException e ) {\r
-            throw new IllegalArgumentException(e.toString());\r
-        }\r
-\r
-        // full range is different\r
-        if ( diffBegin == 0 && diffEnd == items.length-1 ) {\r
-            format(fromCalendar, appendTo, pos);\r
-            appendTo.append(" \u2013 "); // default separator\r
-            format(toCalendar, appendTo, pos);\r
-            return appendTo;\r
-        }\r
-\r
-\r
-        /* search for largest calendar field within the different range */\r
-        int highestLevel = 1000;\r
-        for (int i = diffBegin; i <= diffEnd; i++) {\r
-            if ( items[i] instanceof String) {\r
-                continue;\r
-            } \r
-            PatternItem item = (PatternItem)items[i];\r
-            char ch = item.type; \r
-            int patternCharIndex = -1;\r
-            if ('A' <= ch && ch <= 'z') {\r
-                patternCharIndex = PATTERN_CHAR_TO_LEVEL[(int)ch - PATTERN_CHAR_BASE];\r
-            }\r
-    \r
-            if (patternCharIndex == -1) {\r
-                throw new IllegalArgumentException("Illegal pattern character " +\r
-                                                   "'" + ch + "' in \"" +\r
-                                                   new String(pattern) + '"');\r
-            }\r
-    \r
-            if ( patternCharIndex < highestLevel ) {\r
-                highestLevel = patternCharIndex;\r
-            }\r
-        }\r
-\r
-        /* re-calculate diff range, including those calendar field which\r
-           is in lower level than the largest calendar field covered\r
-           in diff range calculated. */\r
-        try {\r
-            for (int i = 0; i < diffBegin; i++) {\r
-                if ( lowerLevel(items, i, highestLevel) ) {\r
-                    diffBegin = i;\r
-                    break;\r
-                }\r
-            }\r
-    \r
-    \r
-            for (int i = items.length-1; i > diffEnd; i--) {\r
-                if ( lowerLevel(items, i, highestLevel) ) {\r
-                    diffEnd = i;\r
-                    break;\r
-                }\r
-            }\r
-        } catch ( IllegalArgumentException e ) {\r
-            throw new IllegalArgumentException(e.toString());\r
-        }\r
-\r
-\r
-        // full range is different\r
-        if ( diffBegin == 0 && diffEnd == items.length-1 ) {\r
-            format(fromCalendar, appendTo, pos);\r
-            appendTo.append(" \u2013 "); // default separator\r
-            format(toCalendar, appendTo, pos);\r
-            return appendTo;\r
-        }\r
-\r
-\r
-        // formatting\r
-        // Initialize\r
-        pos.setBeginIndex(0);\r
-        pos.setEndIndex(0);\r
-\r
-        // formatting date 1\r
-        for (int i = 0; i <= diffEnd; i++) {\r
-            if (items[i] instanceof String) {\r
-                appendTo.append((String)items[i]);\r
-            } else {\r
-                PatternItem item = (PatternItem)items[i];\r
-                if (useFastFormat) {\r
-                    subFormat(appendTo, item.type, item.length, appendTo.length(), pos, fromCalendar);\r
-                } else {\r
-                    appendTo.append(subFormat(item.type, item.length, appendTo.length(), pos, formatData, fromCalendar));\r
-                }\r
-            }\r
-        }\r
-\r
-        appendTo.append(" \u2013 "); // default separator\r
-\r
-        // formatting date 2\r
-        for (int i = diffBegin; i < items.length; i++) {\r
-            if (items[i] instanceof String) {\r
-                appendTo.append((String)items[i]);\r
-            } else {\r
-                PatternItem item = (PatternItem)items[i];\r
-                if (useFastFormat) {\r
-                    subFormat(appendTo, item.type, item.length, appendTo.length(), pos, toCalendar);\r
-                } else {\r
-                    appendTo.append(subFormat(item.type, item.length, appendTo.length(), pos, formatData, toCalendar));\r
-                }\r
-            }\r
-        }\r
-        return appendTo;\r
-    }\r
-\r
-\r
-    /**\r
-     * check whether the i-th item in 2 calendar is in different value.\r
-     *\r
-     * It is supposed to be used only by CLDR survey tool.\r
-     * It is used by intervalFormatByAlgorithm().\r
-     *\r
-     * @param fromCalendar   one calendar\r
-     * @param toCalendar     the other calendar\r
-     * @param items          pattern items\r
-     * @param i              the i-th item in pattern items\r
-     * @exception IllegalArgumentException when there is non-recognized\r
-     *                                     pattern letter\r
-     * @return               true is i-th item in 2 calendar is in different \r
-     *                       value, false otherwise.\r
-     */\r
-    private boolean diffCalFieldValue(Calendar fromCalendar,\r
-                                      Calendar toCalendar,\r
-                                      Object[] items,\r
-                                      int i) throws IllegalArgumentException {\r
-        if ( items[i] instanceof String) {\r
-            return false;\r
-        } \r
-        PatternItem item = (PatternItem)items[i];\r
-        char ch = item.type; \r
-        int patternCharIndex = -1;\r
-        if ('A' <= ch && ch <= 'z') {\r
-            patternCharIndex = PATTERN_CHAR_TO_INDEX[(int)ch - PATTERN_CHAR_BASE];\r
-        }\r
-   \r
-        if (patternCharIndex == -1) {\r
-            throw new IllegalArgumentException("Illegal pattern character " +\r
-                                               "'" + ch + "' in \"" +\r
-                                               new String(pattern) + '"');\r
-        }\r
-  \r
-        final int field = PATTERN_INDEX_TO_CALENDAR_FIELD[patternCharIndex];\r
-        int value = fromCalendar.get(field);\r
-        int value_2 = toCalendar.get(field);\r
-        if ( value != value_2 ) {\r
-            return true;\r
-        }\r
-        return false;\r
-    }\r
-\r
-\r
-    /**\r
-     * check whether the i-th item's level is lower than the input 'level'\r
-     *\r
-     * It is supposed to be used only by CLDR survey tool.\r
-     * It is used by intervalFormatByAlgorithm().\r
-     *\r
-     * @param items  the pattern items\r
-     * @param i      the i-th item in pattern items\r
-     * @param level  the level with which the i-th pattern item compared to\r
-     * @exception IllegalArgumentException when there is non-recognized\r
-     *                                     pattern letter\r
-     * @return       true if i-th pattern item is lower than 'level',\r
-     *               false otherwise\r
-     */\r
-    private boolean lowerLevel(Object[] items, int i, int level) \r
-                    throws IllegalArgumentException {\r
-        if ( items[i] instanceof String) {\r
-            return false;\r
-        } \r
-        PatternItem item = (PatternItem)items[i];\r
-        char ch = item.type; \r
-        int patternCharIndex = -1;\r
-        if ('A' <= ch && ch <= 'z') {\r
-            patternCharIndex = PATTERN_CHAR_TO_LEVEL[(int)ch - PATTERN_CHAR_BASE];\r
-        }\r
-    \r
-        if (patternCharIndex == -1) {\r
-            throw new IllegalArgumentException("Illegal pattern character " +\r
-                                               "'" + ch + "' in \"" +\r
-                                               new String(pattern) + '"');\r
-        }\r
-    \r
-        if ( patternCharIndex >= level ) {\r
-            return true;\r
-        }    \r
-        return false;\r
-    }\r
-\r
-    protected NumberFormat getNumberFormat(char ch) {\r
-\r
-       Character ovrField;\r
-       ovrField = new Character(ch);\r
-       if (overrideMap != null && overrideMap.containsKey(ovrField)) {\r
-           String nsName = overrideMap.get(ovrField).toString();\r
-           NumberFormat nf = (NumberFormat)numberFormatters.get(nsName);\r
-           return nf;\r
-       } else {\r
-           return numberFormat;\r
-       }\r
-    }\r
-\r
-    private void initNumberFormatters(ULocale loc) {\r
-\r
-       numberFormatters = new HashMap();\r
-       overrideMap = new HashMap();\r
-       processOverrideString(loc,override); \r
-\r
-    }\r
-\r
-    private void processOverrideString(ULocale loc, String str) {\r
-\r
-        if ( str == null || str.length() == 0 )\r
-            return;\r
-\r
-        int start = 0;\r
-        int end;\r
-        String nsName;\r
-        Character ovrField;\r
-        boolean moreToProcess = true;\r
-        boolean fullOverride;\r
-\r
-        while (moreToProcess) {\r
-            int delimiterPosition = str.indexOf(";",start);\r
-            if (delimiterPosition == -1) {\r
-                moreToProcess = false;\r
-                end = str.length();\r
-            } else {\r
-                end = delimiterPosition;\r
-            }\r
-\r
-            String currentString = str.substring(start,end);\r
-            int equalSignPosition = currentString.indexOf("=");\r
-            if (equalSignPosition == -1) { // Simple override string such as "hebrew"\r
-               nsName = currentString;\r
-               fullOverride = true;\r
-            } else { // Field specific override string such as "y=hebrew"\r
-               nsName = currentString.substring(equalSignPosition+1);\r
-               ovrField = new Character(currentString.charAt(0));\r
-               overrideMap.put(ovrField,nsName);\r
-               fullOverride = false;\r
-            }\r
-\r
-            ULocale ovrLoc = new ULocale(loc.getBaseName()+"@numbers="+nsName);\r
-            NumberFormat nf = NumberFormat.createInstance(ovrLoc,NumberFormat.NUMBERSTYLE);\r
-            \r
-            if (fullOverride) {\r
-                setNumberFormat(nf);\r
-            } else {\r
-                // Since one or more of the override number formatters might be complex,\r
-                // we can't rely on the fast numfmt where we have a partial field override.\r
-                useLocalZeroPaddingNumberFormat = false;\r
-            }\r
-\r
-            if (!numberFormatters.containsKey(nsName)) {\r
-                  numberFormatters.put(nsName,nf);\r
-            }\r
-\r
-            start = delimiterPosition + 1;\r
-\r
-        }    \r
-    }\r
-}\r
+//##header J2SE15
+/*
+ *******************************************************************************
+ * Copyright (C) 1996-2009, International Business Machines Corporation and    *
+ * others. All Rights Reserved.                                                *
+ *******************************************************************************
+ */
+
+package com.ibm.icu.text;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.lang.ref.WeakReference;
+import java.lang.Character;
+import java.text.FieldPosition;
+import java.text.ParsePosition;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.MissingResourceException;
+//#if defined(FOUNDATION10) || defined(J2SE13)
+//#else
+import java.text.AttributedCharacterIterator;
+import java.text.AttributedString;
+import java.text.Format;
+import java.util.LinkedList;
+//#endif
+
+import com.ibm.icu.impl.CalendarData;
+import com.ibm.icu.impl.DateNumberFormat;
+import com.ibm.icu.impl.ICUCache;
+import com.ibm.icu.impl.SimpleCache;
+import com.ibm.icu.impl.UCharacterProperty;
+import com.ibm.icu.impl.ZoneMeta;
+import com.ibm.icu.impl.ZoneStringFormat.ZoneStringInfo;
+import com.ibm.icu.lang.UCharacter;
+import com.ibm.icu.util.BasicTimeZone;
+import com.ibm.icu.util.Calendar;
+import com.ibm.icu.util.GregorianCalendar;
+import com.ibm.icu.util.TimeZone;
+import com.ibm.icu.util.TimeZoneTransition;
+import com.ibm.icu.util.ULocale;
+
+
+/**
+ * <code>SimpleDateFormat</code> is a concrete class for formatting and
+ * parsing dates in a locale-sensitive manner. It allows for formatting
+ * (date -> text), parsing (text -> date), and normalization.
+ *
+ * <p>
+ * <code>SimpleDateFormat</code> allows you to start by choosing
+ * any user-defined patterns for date-time formatting. However, you
+ * are encouraged to create a date-time formatter with either
+ * <code>getTimeInstance</code>, <code>getDateInstance</code>, or
+ * <code>getDateTimeInstance</code> in <code>DateFormat</code>. Each
+ * of these class methods can return a date/time formatter initialized
+ * with a default format pattern. You may modify the format pattern
+ * using the <code>applyPattern</code> methods as desired.
+ * For more information on using these methods, see
+ * {@link DateFormat}.
+ *
+ * <p>
+ * <strong>Time Format Syntax:</strong>
+ * <p>
+ * To specify the time format use a <em>time pattern</em> string.
+ * In this pattern, all ASCII letters are reserved as pattern letters,
+ * which are defined as the following:
+ * <blockquote>
+ * <pre>
+ * Symbol   Meaning                 Presentation        Example
+ * ------   -------                 ------------        -------
+ * G        era designator          (Text)              AD
+ * y&#x2020;       year                    (Number)            1996
+ * Y*       year (week of year)     (Number)            1997
+ * u*       extended year           (Number)            4601
+ * M        month in year           (Text & Number)     July & 07
+ * d        day in month            (Number)            10
+ * h        hour in am/pm (1~12)    (Number)            12
+ * H        hour in day (0~23)      (Number)            0
+ * m        minute in hour          (Number)            30
+ * s        second in minute        (Number)            55
+ * S        fractional second       (Number)            978
+ * E        day of week             (Text)              Tuesday
+ * e*       day of week (local 1~7) (Text & Number)     Tuesday & 2
+ * D        day in year             (Number)            189
+ * F        day of week in month    (Number)            2 (2nd Wed in July)
+ * w        week in year            (Number)            27
+ * W        week in month           (Number)            2
+ * a        am/pm marker            (Text)              PM
+ * k        hour in day (1~24)      (Number)            24
+ * K        hour in am/pm (0~11)    (Number)            0
+ * z        time zone               (Text)              Pacific Standard Time
+ * Z        time zone (RFC 822)     (Number)            -0800
+ * v        time zone (generic)     (Text)              Pacific Time
+ * V        time zone (location)    (Text)              United States (Los Angeles)
+ * g*       Julian day              (Number)            2451334
+ * A*       milliseconds in day     (Number)            69540000
+ * Q*       quarter in year         (Text & Number)     Q1 & 01
+ * c*       stand alone day of week (Text & Number)     Tuesday & 2
+ * L*       stand alone month       (Text & Number)     July & 07
+ * q*       stand alone quarter     (Text & Number)     Q1 & 01
+ * '        escape for text         (Delimiter)         'Date='
+ * ''       single quote            (Literal)           'o''clock'
+ * </pre>
+ * </blockquote>
+ * <tt><b>*</b></tt> These items are not supported by Java's SimpleDateFormat.<br>
+ * <tt><b>&#x2020;</b></tt> ICU interprets a single 'y' differently than Java.</p>
+ * <p>
+ * The count of pattern letters determine the format.
+ * <p>
+ * <strong>(Text)</strong>: 4 or more pattern letters--use full form,
+ * &lt; 4--use short or abbreviated form if one exists.
+ * <p>
+ * <strong>(Number)</strong>: the minimum number of digits. Shorter
+ * numbers are zero-padded to this amount. Year is handled specially;
+ * that is, if the count of 'y' is 2, the Year will be truncated to 2 digits.
+ * (e.g., if "yyyy" produces "1997", "yy" produces "97".)
+ * Unlike other fields, fractional seconds are padded on the right with zero.
+ * <p>
+ * <strong>(Text & Number)</strong>: 3 or over, use text, otherwise use number.
+ * <p>
+ * Any characters in the pattern that are not in the ranges of ['a'..'z']
+ * and ['A'..'Z'] will be treated as quoted text. For instance, characters
+ * like ':', '.', ' ', '#' and '@' will appear in the resulting time text
+ * even they are not embraced within single quotes.
+ * <p>
+ * A pattern containing any invalid pattern letter will result in a thrown
+ * exception during formatting or parsing.
+ *
+ * <p>
+ * <strong>Examples Using the US Locale:</strong>
+ * <blockquote>
+ * <pre>
+ * Format Pattern                         Result
+ * --------------                         -------
+ * "yyyy.MM.dd G 'at' HH:mm:ss vvvv" ->>  1996.07.10 AD at 15:08:56 Pacific Time
+ * "EEE, MMM d, ''yy"                ->>  Wed, July 10, '96
+ * "h:mm a"                          ->>  12:08 PM
+ * "hh 'o''clock' a, zzzz"           ->>  12 o'clock PM, Pacific Daylight Time
+ * "K:mm a, vvv"                     ->>  0:00 PM, PT
+ * "yyyyy.MMMMM.dd GGG hh:mm aaa"    ->>  01996.July.10 AD 12:08 PM
+ * </pre>
+ * </blockquote>
+ * <strong>Code Sample:</strong>
+ * <blockquote>
+ * <pre>
+ * SimpleTimeZone pdt = new SimpleTimeZone(-8 * 60 * 60 * 1000, "PST");
+ * pdt.setStartRule(Calendar.APRIL, 1, Calendar.SUNDAY, 2*60*60*1000);
+ * pdt.setEndRule(Calendar.OCTOBER, -1, Calendar.SUNDAY, 2*60*60*1000);
+ * <br>
+ * // Format the current time.
+ * SimpleDateFormat formatter
+ *     = new SimpleDateFormat ("yyyy.MM.dd G 'at' hh:mm:ss a zzz");
+ * Date currentTime_1 = new Date();
+ * String dateString = formatter.format(currentTime_1);
+ * <br>
+ * // Parse the previous string back into a Date.
+ * ParsePosition pos = new ParsePosition(0);
+ * Date currentTime_2 = formatter.parse(dateString, pos);
+ * </pre>
+ * </blockquote>
+ * In the example, the time value <code>currentTime_2</code> obtained from
+ * parsing will be equal to <code>currentTime_1</code>. However, they may not be
+ * equal if the am/pm marker 'a' is left out from the format pattern while
+ * the "hour in am/pm" pattern symbol is used. This information loss can
+ * happen when formatting the time in PM.
+ *
+ * <p>
+ * When parsing a date string using the abbreviated year pattern ("yy"),
+ * SimpleDateFormat must interpret the abbreviated year
+ * relative to some century.  It does this by adjusting dates to be
+ * within 80 years before and 20 years after the time the SimpleDateFormat
+ * instance is created. For example, using a pattern of "MM/dd/yy" and a
+ * SimpleDateFormat instance created on Jan 1, 1997,  the string
+ * "01/11/12" would be interpreted as Jan 11, 2012 while the string "05/04/64"
+ * would be interpreted as May 4, 1964.
+ * During parsing, only strings consisting of exactly two digits, as defined by
+ * {@link com.ibm.icu.lang.UCharacter#isDigit(int)}, will be parsed into the default
+ * century.
+ * Any other numeric string, such as a one digit string, a three or more digit
+ * string, or a two digit string that isn't all digits (for example, "-1"), is
+ * interpreted literally.  So "01/02/3" or "01/02/003" are parsed, using the
+ * same pattern, as Jan 2, 3 AD.  Likewise, "01/02/-3" is parsed as Jan 2, 4 BC.
+ *
+ * <p>
+ * If the year pattern does not have exactly two 'y' characters, the year is
+ * interpreted literally, regardless of the number of digits.  So using the
+ * pattern "MM/dd/yyyy", "01/11/12" parses to Jan 11, 12 A.D.
+ *
+ * <p>
+ * When numeric fields abut one another directly, with no intervening delimiter
+ * characters, they constitute a run of abutting numeric fields.  Such runs are
+ * parsed specially.  For example, the format "HHmmss" parses the input text
+ * "123456" to 12:34:56, parses the input text "12345" to 1:23:45, and fails to
+ * parse "1234".  In other words, the leftmost field of the run is flexible,
+ * while the others keep a fixed width.  If the parse fails anywhere in the run,
+ * then the leftmost field is shortened by one character, and the entire run is
+ * parsed again. This is repeated until either the parse succeeds or the
+ * leftmost field is one character in length.  If the parse still fails at that
+ * point, the parse of the run fails.
+ *
+ * <p>
+ * For time zones that have no names, use strings GMT+hours:minutes or
+ * GMT-hours:minutes.
+ *
+ * <p>
+ * The calendar defines what is the first day of the week, the first week
+ * of the year, whether hours are zero based or not (0 vs 12 or 24), and the
+ * time zone. There is one common decimal format to handle all the numbers;
+ * the digit count is handled programmatically according to the pattern.
+ *
+ * <h4>Synchronization</h4>
+ *
+ * Date formats are not synchronized. It is recommended to create separate
+ * format instances for each thread. If multiple threads access a format
+ * concurrently, it must be synchronized externally.
+ *
+ * @see          com.ibm.icu.util.Calendar
+ * @see          com.ibm.icu.util.GregorianCalendar
+ * @see          com.ibm.icu.util.TimeZone
+ * @see          DateFormat
+ * @see          DateFormatSymbols
+ * @see          DecimalFormat
+ * @author       Mark Davis, Chen-Lieh Huang, Alan Liu
+ * @stable ICU 2.0
+ */
+public class SimpleDateFormat extends DateFormat {
+
+    // the official serial version ID which says cryptically
+    // which version we're compatible with
+    private static final long serialVersionUID = 4774881970558875024L;
+
+    // the internal serial version which says which version was written
+    // - 0 (default) for version up to JDK 1.1.3
+    // - 1 for version from JDK 1.1.4, which includes a new field
+    static final int currentSerialVersion = 1;
+
+    
+    /*
+     * From calendar field to its level.
+     * Used to order calendar field.
+     * For example, calendar fields can be defined in the following order:
+     * year >  month > date > am-pm > hour >  minute
+     * YEAR --> 10, MONTH -->20, DATE --> 30; 
+     * AM_PM -->40, HOUR --> 50, MINUTE -->60
+     */
+    private static final int[] CALENDAR_FIELD_TO_LEVEL =
+    {
+        /*GyM*/ 0, 10, 20,
+        /*wW*/ 20, 30,
+        /*dDEF*/ 30, 20, 30, 30,
+        /*ahHm*/ 40, 50, 50, 60,
+        /*sS..*/ 70, 80, 
+        /*z?Y*/ 0, 0, 10, 
+        /*eug*/ 30, 10, 0,
+        /*A*/ 40 
+    };
+
+
+    
+    /*
+     * From calendar field letter to its level.
+     * Used to order calendar field.
+     * For example, calendar fields can be defined in the following order:
+     * year >  month > date > am-pm > hour >  minute
+     * 'y' --> 10, 'M' -->20, 'd' --> 30; 'a' -->40, 'h' --> 50, 'm' -->60
+     */
+    private static final int[] PATTERN_CHAR_TO_LEVEL = 
+    {
+    //       A   B   C   D    E   F   G    H   I   J   K   L    M   N   O
+        -1, 40, -1, -1, 20,  30, 30,  0,  50, -1, -1, 50, 20,  20, -1, -1,
+    //   P   Q   R    S   T   U  V   W   X   Y  Z
+        -1, 20, -1,  80, -1, -1, 0, 30, -1, 10, 0, -1, -1, -1, -1, -1,
+    //       a   b   c   d    e   f  g   h   i   j    k   l    m   n   o
+        -1, 40, -1, 30,  30, 30, -1, 0, 50, -1, -1,  50, -1,  60, -1, -1,
+    //   p   q   r    s   t   u  v   w   x    y  z
+        -1, 20, -1,  70, -1, 10, 0, 20, -1,  10, 0, -1, -1, -1, -1, -1
+    };
+
+
+    /**
+     * The version of the serialized data on the stream.  Possible values:
+     * <ul>
+     * <li><b>0</b> or not present on stream: JDK 1.1.3.  This version
+     * has no <code>defaultCenturyStart</code> on stream.
+     * <li><b>1</b> JDK 1.1.4 or later.  This version adds
+     * <code>defaultCenturyStart</code>.
+     * </ul>
+     * When streaming out this class, the most recent format
+     * and the highest allowable <code>serialVersionOnStream</code>
+     * is written.
+     * @serial
+     */
+    private int serialVersionOnStream = currentSerialVersion;
+
+    /**
+     * The pattern string of this formatter.  This is always a non-localized
+     * pattern.  May not be null.  See class documentation for details.
+     * @serial
+     */
+    private String pattern;
+
+    /**
+     * The override string of this formatter.  Used to override the
+     * numbering system for one or more fields.
+     * @serial
+     */
+    private String override;
+
+    /**
+     * The hash map used for number format overrides.
+     * @serial
+     */
+    private HashMap numberFormatters;
+
+    /**
+     * The hash map used for number format overrides.
+     * @serial
+     */
+    private HashMap overrideMap;
+
+    /**
+     * The symbols used by this formatter for week names, month names,
+     * etc.  May not be null.
+     * @serial
+     * @see DateFormatSymbols
+     */
+    private DateFormatSymbols formatData;
+
+    private transient ULocale locale;
+
+    /**
+     * We map dates with two-digit years into the century starting at
+     * <code>defaultCenturyStart</code>, which may be any date.  May
+     * not be null.
+     * @serial
+     * @since JDK1.1.4
+     */
+    private Date defaultCenturyStart;
+
+    private transient int defaultCenturyStartYear;
+
+    // defaultCenturyBase is set when an instance is created
+    // and may be used for calculating defaultCenturyStart when needed.
+    private transient long defaultCenturyBase;
+
+    // We need to preserve time zone type when parsing specific
+    // time zone text (xxx Standard Time vs xxx Daylight Time)
+    private static final int TZTYPE_UNK = 0, TZTYPE_STD = 1, TZTYPE_DST = 2;
+    private transient int tztype = TZTYPE_UNK;
+
+    private static final int millisPerHour = 60 * 60 * 1000;
+    private static final int millisPerMinute = 60 * 1000;
+    private static final int millisPerSecond = 1000;
+
+    // This prefix is designed to NEVER MATCH real text, in order to
+    // suppress the parsing of negative numbers.  Adjust as needed (if
+    // this becomes valid Unicode).
+    private static final String SUPPRESS_NEGATIVE_PREFIX = "\uAB00";
+
+    /**
+     * If true, this object supports fast formatting using the
+     * subFormat variant that takes a StringBuffer.
+     */
+    private transient boolean useFastFormat;
+
+    /**
+     * Construct a SimpleDateFormat using the default pattern for the default
+     * locale.  <b>Note:</b> Not all locales support SimpleDateFormat; for full
+     * generality, use the factory methods in the DateFormat class.
+     *
+     * @see DateFormat
+     * @stable ICU 2.0
+     */
+    public SimpleDateFormat() {
+        this(getDefaultPattern(), null, null, null, null, true, null);
+    }
+
+    /**
+     * Construct a SimpleDateFormat using the given pattern in the default
+     * locale.  <b>Note:</b> Not all locales support SimpleDateFormat; for full
+     * generality, use the factory methods in the DateFormat class.
+     * @stable ICU 2.0
+     */
+    public SimpleDateFormat(String pattern)
+    {
+        this(pattern, null, null, null, null, true, null);
+    }
+
+    /**
+     * Construct a SimpleDateFormat using the given pattern and locale.
+     * <b>Note:</b> Not all locales support SimpleDateFormat; for full
+     * generality, use the factory methods in the DateFormat class.
+     * @stable ICU 2.0
+     */
+    public SimpleDateFormat(String pattern, Locale loc)
+    {
+        this(pattern, null, null, null, ULocale.forLocale(loc), true, null);
+    }
+
+    /**
+     * Construct a SimpleDateFormat using the given pattern and locale.
+     * <b>Note:</b> Not all locales support SimpleDateFormat; for full
+     * generality, use the factory methods in the DateFormat class.
+     * @stable ICU 3.2
+     */
+    public SimpleDateFormat(String pattern, ULocale loc)
+    {
+        this(pattern, null, null, null, loc, true, null);
+    }
+
+    /**
+     * Construct a SimpleDateFormat using the given pattern , override and locale.
+     * @draft ICU 4.2
+     * @provisional This API might change or be removed in a future release.
+     */
+    public SimpleDateFormat(String pattern, String override, ULocale loc)
+    {
+        this(pattern, null, null, null, loc, false,override);
+    }
+
+    /**
+     * Construct a SimpleDateFormat using the given pattern and
+     * locale-specific symbol data.
+     * Warning: uses default locale for digits!
+     * @stable ICU 2.0
+     */
+    public SimpleDateFormat(String pattern, DateFormatSymbols formatData)
+    {
+        this(pattern, (DateFormatSymbols)formatData.clone(), null, null, null, true, null);
+    }
+
+    /**
+     * @internal ICU 3.2
+     * @deprecated This API is ICU internal only.
+     */
+    public SimpleDateFormat(String pattern, DateFormatSymbols formatData, ULocale loc)
+    {
+        this(pattern, (DateFormatSymbols)formatData.clone(), null, null, loc, true,null);
+    }
+
+    /**
+     * Package-private constructor that allows a subclass to specify
+     * whether it supports fast formatting.
+     *
+     * TODO make this API public.
+     */
+    SimpleDateFormat(String pattern, DateFormatSymbols formatData, Calendar calendar, ULocale locale,
+                     boolean useFastFormat, String override) {
+        this(pattern, (DateFormatSymbols)formatData.clone(), (Calendar)calendar.clone(), null, locale, useFastFormat,override);
+    }
+
+    SimpleDateFormat(String pattern, DateFormatSymbols formatData, Calendar calendar, ULocale locale,
+                     boolean useFastFormat) {
+        this(pattern, (DateFormatSymbols)formatData.clone(), (Calendar)calendar.clone(), null, locale, useFastFormat,null);
+    }
+
+    /*
+     * The constructor called from all other SimpleDateFormat constructors
+     */
+    private SimpleDateFormat(String pattern, DateFormatSymbols formatData, Calendar calendar,
+            NumberFormat numberFormat, ULocale locale, boolean useFastFormat,String override) {
+        this.pattern = pattern;
+        this.formatData = formatData;
+        this.calendar = calendar;
+        this.numberFormat = numberFormat;
+        this.locale = locale; // time zone formatting
+        this.useFastFormat = useFastFormat;
+        this.override = override;
+        initialize();
+    }
+
+    /**
+     * Create an instance of SimpleDateForamt for the given format configuration
+     * @param formatConfig the format configuration
+     * @return A SimpleDateFormat instance
+     * @internal ICU 3.8
+     * @deprecated This API is ICU internal only.
+     */
+    public static SimpleDateFormat getInstance(Calendar.FormatConfiguration formatConfig) {
+       
+        String ostr = formatConfig.getOverrideString();
+        boolean useFast = ( ostr != null && ostr.length() > 0 );
+
+        return new SimpleDateFormat(formatConfig.getPatternString(),
+                    formatConfig.getDateFormatSymbols(),
+                    formatConfig.getCalendar(),
+                    null,
+                    formatConfig.getLocale(),
+                    useFast,
+                    formatConfig.getOverrideString());
+    }
+
+    /*
+     * Initialized fields
+     */
+    private void initialize() {
+        if (locale == null) {
+            locale = ULocale.getDefault();
+        }
+        if (formatData == null) {
+            formatData = new DateFormatSymbols(locale);
+        }
+        if (calendar == null) {
+            calendar = Calendar.getInstance(locale);
+        }
+        if (numberFormat == null) {
+            NumberingSystem ns = NumberingSystem.getInstance(locale);
+            if ( ns.isAlgorithmic() ) {
+                numberFormat = NumberFormat.getInstance(locale);
+            } else {
+                char digit0 = ns.getDescription().charAt(0);
+                // Use a NumberFormat optimized for date formatting
+                numberFormat = new DateNumberFormat(locale, digit0);
+            }
+        }
+        // Note: deferring calendar calculation until when we really need it.
+        // Instead, we just record time of construction for backward compatibility.
+        defaultCenturyBase = System.currentTimeMillis();
+
+        setLocale(calendar.getLocale(ULocale.VALID_LOCALE ), calendar.getLocale(ULocale.ACTUAL_LOCALE));
+        initLocalZeroPaddingNumberFormat();
+
+        if (override != null) {
+           initNumberFormatters(locale);
+        }
+
+    }
+
+    // privates for the default pattern
+    private static ULocale cachedDefaultLocale = null;
+    private static String cachedDefaultPattern = null;
+    private static final String FALLBACKPATTERN = "yy/MM/dd HH:mm";
+
+    /*
+     * Returns the default date and time pattern (SHORT) for the default locale.
+     * This method is only used by the default SimpleDateFormat constructor.
+     */
+    private static synchronized String getDefaultPattern() {
+        ULocale defaultLocale = ULocale.getDefault();
+        if (!defaultLocale.equals(cachedDefaultLocale)) {
+            cachedDefaultLocale = defaultLocale;
+            Calendar cal = Calendar.getInstance(cachedDefaultLocale);
+            try {
+                CalendarData calData = new CalendarData(cachedDefaultLocale, cal.getType());
+                String[] dateTimePatterns = calData.getDateTimePatterns();
+                int glueIndex = 8;
+                if (dateTimePatterns.length >= 13)
+                {
+                    glueIndex += (SHORT + 1);
+                }
+                cachedDefaultPattern = MessageFormat.format(dateTimePatterns[glueIndex],
+                        new Object[] {dateTimePatterns[SHORT], dateTimePatterns[SHORT + 4]});
+            } catch (MissingResourceException e) {
+                cachedDefaultPattern = FALLBACKPATTERN;
+            }
+        }
+        return cachedDefaultPattern;
+    }
+
+    /* Define one-century window into which to disambiguate dates using
+     * two-digit years.
+     */
+    private void parseAmbiguousDatesAsAfter(Date startDate) {
+        defaultCenturyStart = startDate;
+        calendar.setTime(startDate);
+        defaultCenturyStartYear = calendar.get(Calendar.YEAR);
+    }
+
+    /* Initialize defaultCenturyStart and defaultCenturyStartYear by base time.
+     * The default start time is 80 years before the creation time of this object.
+     */
+    private void initializeDefaultCenturyStart(long baseTime) {
+        defaultCenturyBase = baseTime;
+        // clone to avoid messing up date stored in calendar object
+        // when this method is called while parsing
+        Calendar tmpCal = (Calendar)calendar.clone();
+        tmpCal.setTimeInMillis(baseTime);
+        tmpCal.add(Calendar.YEAR, -80);
+        defaultCenturyStart = tmpCal.getTime();
+        defaultCenturyStartYear = tmpCal.get(Calendar.YEAR);
+    }
+
+    /* Gets the default century start date for this object */
+    private Date getDefaultCenturyStart() {
+        if (defaultCenturyStart == null) {
+            // not yet initialized
+            initializeDefaultCenturyStart(defaultCenturyBase);
+        }
+        return defaultCenturyStart;
+    }
+
+    /* Gets the default century start year for this object */
+    private int getDefaultCenturyStartYear() {
+        if (defaultCenturyStart == null) {
+            // not yet initialized
+            initializeDefaultCenturyStart(defaultCenturyBase);
+        }
+        return defaultCenturyStartYear;
+    }
+
+    /**
+     * Sets the 100-year period 2-digit years will be interpreted as being in
+     * to begin on the date the user specifies.
+     * @param startDate During parsing, two digit years will be placed in the range
+     * <code>startDate</code> to <code>startDate + 100 years</code>.
+     * @stable ICU 2.0
+     */
+    public void set2DigitYearStart(Date startDate) {
+        parseAmbiguousDatesAsAfter(startDate);
+    }
+
+    /**
+     * Returns the beginning date of the 100-year period 2-digit years are interpreted
+     * as being within.
+     * @return the start of the 100-year period into which two digit years are
+     * parsed
+     * @stable ICU 2.0
+     */
+    public Date get2DigitYearStart() {
+        return getDefaultCenturyStart();
+    }
+
+    /**
+     * Overrides DateFormat.
+     * <p>Formats a date or time, which is the standard millis
+     * since January 1, 1970, 00:00:00 GMT.
+     * <p>Example: using the US locale:
+     * "yyyy.MM.dd G 'at' HH:mm:ss zzz" ->> 1996.07.10 AD at 15:08:56 PDT
+     * @param cal the calendar whose date-time value is to be formatted into a date-time string
+     * @param toAppendTo where the new date-time text is to be appended
+     * @param pos the formatting position. On input: an alignment field,
+     * if desired. On output: the offsets of the alignment field.
+     * @return the formatted date-time string.
+     * @see DateFormat
+     * @stable ICU 2.0
+     */
+    public StringBuffer format(Calendar cal, StringBuffer toAppendTo,
+                               FieldPosition pos) {
+        TimeZone backupTZ = null;
+        if (cal != calendar && !cal.getType().equals(calendar.getType())) {
+            // Different calendar type
+            // We use the time and time zone from the input calendar, but
+            // do not use the input calendar for field calculation.
+            calendar.setTimeInMillis(cal.getTimeInMillis());
+            backupTZ = calendar.getTimeZone();
+            calendar.setTimeZone(cal.getTimeZone());
+            cal = calendar;
+        }
+        StringBuffer result = format(cal, toAppendTo, pos, null);
+        if (backupTZ != null) {
+            // Restore the original time zone
+            calendar.setTimeZone(backupTZ);
+        }
+        return result;
+    }
+
+    // The actual method to format date. If List attributes is not null,
+    // then attribute information will be recorded.
+    private StringBuffer format(Calendar cal, StringBuffer toAppendTo,
+            FieldPosition pos, List attributes) {
+        // Initialize
+        pos.setBeginIndex(0);
+        pos.setEndIndex(0);
+
+        // Careful: For best performance, minimize the number of calls
+        // to StringBuffer.append() by consolidating appends when
+        // possible.
+
+        Object[] items = getPatternItems();
+        for (int i = 0; i < items.length; i++) {
+            if (items[i] instanceof String) {
+                toAppendTo.append((String)items[i]);
+            } else {
+                PatternItem item = (PatternItem)items[i];
+//#if defined(FOUNDATION10) || defined(J2SE13)
+//#else
+                int start = 0;
+                if (attributes != null) {
+                    // Save the current length
+                    start = toAppendTo.length();
+                }
+//#endif
+                if (useFastFormat) {
+                    subFormat(toAppendTo, item.type, item.length, toAppendTo.length(), pos, cal);
+                } else {
+                    toAppendTo.append(subFormat(item.type, item.length, toAppendTo.length(), pos, formatData, cal));
+                }
+//#if defined(FOUNDATION10) || defined(J2SE13)
+//#else
+                if (attributes != null) {
+                    // Check the sub format length
+                    int end = toAppendTo.length();
+                    if (end - start > 0) {
+                        // Append the attribute to the list
+                        DateFormat.Field attr = patternCharToDateFormatField(item.type);
+                        FieldPosition fp = new FieldPosition(attr);
+                        fp.setBeginIndex(start);
+                        fp.setEndIndex(end);
+                        attributes.add(fp);
+                    }
+                }
+//#endif
+            }
+        }
+        return toAppendTo;
+        
+    }
+
+    // Map pattern character to index
+    private static final int PATTERN_CHAR_BASE = 0x40;
+    private static final int[] PATTERN_CHAR_TO_INDEX =
+    {
+    //       A   B   C   D   E   F   G   H   I   J   K   L   M   N   O
+        -1, 22, -1, -1, 10,  9, 11,  0,  5, -1, -1, 16, 26,  2, -1, -1,
+    //   P   Q   R   S   T   U   V   W   X   Y   Z
+        -1, 27, -1,  8, -1, -1, 29, 13, -1, 18, 23, -1, -1, -1, -1, -1,
+    //       a   b   c   d   e   f   g   h   i   j   k   l   m   n   o
+        -1, 14, -1, 25,  3, 19, -1, 21, 15, -1, -1,  4, -1,  6, -1, -1,
+    //   p   q   r   s   t   u   v   w   x   y   z
+        -1, 28, -1,  7, -1, 20, 24, 12, -1,  1, 17, -1, -1, -1, -1, -1
+    };
+    
+    // Map pattern character index to Calendar field number
+    private static final int[] PATTERN_INDEX_TO_CALENDAR_FIELD =
+    {
+        /*GyM*/ Calendar.ERA, Calendar.YEAR, Calendar.MONTH,
+        /*dkH*/ Calendar.DATE, Calendar.HOUR_OF_DAY, Calendar.HOUR_OF_DAY,
+        /*msS*/ Calendar.MINUTE, Calendar.SECOND, Calendar.MILLISECOND,
+        /*EDF*/ Calendar.DAY_OF_WEEK, Calendar.DAY_OF_YEAR, Calendar.DAY_OF_WEEK_IN_MONTH,
+        /*wWa*/ Calendar.WEEK_OF_YEAR, Calendar.WEEK_OF_MONTH, Calendar.AM_PM,
+        /*hKz*/ Calendar.HOUR, Calendar.HOUR, Calendar.ZONE_OFFSET,
+        /*Yeu*/ Calendar.YEAR_WOY, Calendar.DOW_LOCAL, Calendar.EXTENDED_YEAR,
+        /*gAZ*/ Calendar.JULIAN_DAY, Calendar.MILLISECONDS_IN_DAY, Calendar.ZONE_OFFSET,
+        /*v*/   Calendar.ZONE_OFFSET,
+        /*c*/   Calendar.DOW_LOCAL,
+        /*L*/   Calendar.MONTH,
+        /*Qq*/  Calendar.MONTH, Calendar.MONTH,
+        /*V*/   Calendar.ZONE_OFFSET,
+    };
+
+    // Map pattern character index to DateFormat field number
+    private static final int[] PATTERN_INDEX_TO_DATE_FORMAT_FIELD = {
+        /*GyM*/ DateFormat.ERA_FIELD, DateFormat.YEAR_FIELD, DateFormat.MONTH_FIELD,
+        /*dkH*/ DateFormat.DATE_FIELD, DateFormat.HOUR_OF_DAY1_FIELD, DateFormat.HOUR_OF_DAY0_FIELD,
+        /*msS*/ DateFormat.MINUTE_FIELD, DateFormat.SECOND_FIELD, DateFormat.FRACTIONAL_SECOND_FIELD,
+        /*EDF*/ DateFormat.DAY_OF_WEEK_FIELD, DateFormat.DAY_OF_YEAR_FIELD, DateFormat.DAY_OF_WEEK_IN_MONTH_FIELD,
+        /*wWa*/ DateFormat.WEEK_OF_YEAR_FIELD, DateFormat.WEEK_OF_MONTH_FIELD, DateFormat.AM_PM_FIELD,
+        /*hKz*/ DateFormat.HOUR1_FIELD, DateFormat.HOUR0_FIELD, DateFormat.TIMEZONE_FIELD,
+        /*Yeu*/ DateFormat.YEAR_WOY_FIELD, DateFormat.DOW_LOCAL_FIELD, DateFormat.EXTENDED_YEAR_FIELD,
+        /*gAZ*/ DateFormat.JULIAN_DAY_FIELD, DateFormat.MILLISECONDS_IN_DAY_FIELD, DateFormat.TIMEZONE_RFC_FIELD,
+        /*v*/   DateFormat.TIMEZONE_GENERIC_FIELD, 
+        /*c*/   DateFormat.STANDALONE_DAY_FIELD,
+        /*L*/   DateFormat.STANDALONE_MONTH_FIELD,
+        /*Qq*/  DateFormat.QUARTER_FIELD, DateFormat.STANDALONE_QUARTER_FIELD,
+        /*V*/   DateFormat.TIMEZONE_SPECIAL_FIELD, 
+    };
+
+//#if defined(FOUNDATION10) || defined(J2SE13)
+//#else
+    // Map pattern character index to DateFormat.Field
+    private static final DateFormat.Field[] PATTERN_INDEX_TO_DATE_FORMAT_ATTRIBUTE = {
+        /*GyM*/ DateFormat.Field.ERA, DateFormat.Field.YEAR, DateFormat.Field.MONTH,
+        /*dkH*/ DateFormat.Field.DAY_OF_MONTH, DateFormat.Field.HOUR_OF_DAY1, DateFormat.Field.HOUR_OF_DAY0,
+        /*msS*/ DateFormat.Field.MINUTE, DateFormat.Field.SECOND, DateFormat.Field.MILLISECOND,
+        /*EDF*/ DateFormat.Field.DAY_OF_WEEK, DateFormat.Field.DAY_OF_YEAR, DateFormat.Field.DAY_OF_WEEK_IN_MONTH,
+        /*wWa*/ DateFormat.Field.WEEK_OF_YEAR, DateFormat.Field.WEEK_OF_MONTH, DateFormat.Field.AM_PM,
+        /*hKz*/ DateFormat.Field.HOUR1, DateFormat.Field.HOUR0, DateFormat.Field.TIME_ZONE,
+        /*Yeu*/ DateFormat.Field.YEAR_WOY, DateFormat.Field.DOW_LOCAL, DateFormat.Field.EXTENDED_YEAR,
+        /*gAZ*/ DateFormat.Field.JULIAN_DAY, DateFormat.Field.MILLISECONDS_IN_DAY, DateFormat.Field.TIME_ZONE,
+        /*v*/   DateFormat.Field.TIME_ZONE,
+        /*c*/   DateFormat.Field.DAY_OF_WEEK,
+        /*L*/   DateFormat.Field.MONTH,
+        /*Qq*/  DateFormat.Field.QUARTER, DateFormat.Field.QUARTER,
+        /*V*/   DateFormat.Field.TIME_ZONE,
+    };
+
+    /**
+     * Return a DateFormat.Field constant associated with the specified format pattern
+     * character.
+     * 
+     * @param ch The pattern character
+     * @return DateFormat.Field associated with the pattern character
+     * 
+     * @stable ICU 3.8
+     */
+    protected DateFormat.Field patternCharToDateFormatField(char ch) {
+        int patternCharIndex = -1;
+        if ('A' <= ch && ch <= 'z') {
+            patternCharIndex = PATTERN_CHAR_TO_INDEX[(int)ch - PATTERN_CHAR_BASE];
+        }
+        if (patternCharIndex != -1) {
+            return PATTERN_INDEX_TO_DATE_FORMAT_ATTRIBUTE[patternCharIndex];
+        }
+        return null;
+    }
+//#endif
+
+    /**
+     * Format a single field, given its pattern character.  Subclasses may
+     * override this method in order to modify or add formatting
+     * capabilities.
+     * @param ch the pattern character
+     * @param count the number of times ch is repeated in the pattern
+     * @param beginOffset the offset of the output string at the start of
+     * this field; used to set pos when appropriate
+     * @param pos receives the position of a field, when appropriate
+     * @param fmtData the symbols for this formatter
+     * @stable ICU 2.0
+     */
+    protected String subFormat(char ch, int count, int beginOffset,
+                               FieldPosition pos, DateFormatSymbols fmtData,
+                               Calendar cal)
+        throws IllegalArgumentException
+    {
+        // Note: formatData is ignored
+        StringBuffer buf = new StringBuffer();
+        subFormat(buf, ch, count, beginOffset, pos, cal);
+        return buf.toString();
+    }
+
+    /**
+     * Format a single field; useFastFormat variant.  Reuses a
+     * StringBuffer for results instead of creating a String on the
+     * heap for each call.
+     *
+     * NOTE We don't really need the beginOffset parameter, EXCEPT for
+     * the need to support the slow subFormat variant (above) which
+     * has to pass it in to us.
+     *
+     * TODO make this API public
+     *
+     * @internal
+     * @deprecated This API is ICU internal only.
+     */
+    protected void subFormat(StringBuffer buf,
+                             char ch, int count, int beginOffset,
+                             FieldPosition pos,
+                             Calendar cal) {
+        final int maxIntCount = Integer.MAX_VALUE;
+        final int bufstart = buf.length();
+
+        // final int patternCharIndex = DateFormatSymbols.patternChars.indexOf(ch);
+        int patternCharIndex = -1;
+        if ('A' <= ch && ch <= 'z') {
+            patternCharIndex = PATTERN_CHAR_TO_INDEX[(int)ch - PATTERN_CHAR_BASE];
+        }
+
+        if (patternCharIndex == -1) {
+            throw new IllegalArgumentException("Illegal pattern character " +
+                                               "'" + ch + "' in \"" +
+                                               new String(pattern) + '"');
+        }
+
+        final int field = PATTERN_INDEX_TO_CALENDAR_FIELD[patternCharIndex];
+        int value = cal.get(field);
+
+        String zoneString = null;
+
+        NumberFormat currentNumberFormat = getNumberFormat(ch);
+
+        switch (patternCharIndex) {
+        case 0: // 'G' - ERA
+            if (count == 5) {
+                safeAppend(formatData.narrowEras, value, buf);
+            } else if (count == 4) {
+                safeAppend(formatData.eraNames, value, buf);
+            } else {
+                safeAppend(formatData.eras, value, buf);
+            }
+            break;
+        case 1: // 'y' - YEAR
+            /* According to the specification, if the number of pattern letters ('y') is 2,
+             * the year is truncated to 2 digits; otherwise it is interpreted as a number.
+             * But the original code process 'y', 'yy', 'yyy' in the same way. and process
+             * patterns with 4 or more than 4 'y' characters in the same way.
+             * So I change the codes to meet the specification. [Richard/GCl]
+             */
+            if (count == 2)
+                zeroPaddingNumber(currentNumberFormat,buf, value, 2, 2); // clip 1996 to 96
+            else //count = 1 or count > 2
+                zeroPaddingNumber(currentNumberFormat,buf, value, count, maxIntCount);
+            break;
+        case 2: // 'M' - MONTH
+            if (count == 5) {
+                safeAppend(formatData.narrowMonths, value, buf);
+            } else if (count == 4) {
+                safeAppend(formatData.months, value, buf);
+            } else if (count == 3) {
+                safeAppend(formatData.shortMonths, value, buf);
+            } else {
+                zeroPaddingNumber(currentNumberFormat,buf, value+1, count, maxIntCount);
+            }
+            break;
+        case 4: // 'k' - HOUR_OF_DAY (1..24)
+            if (value == 0)
+                zeroPaddingNumber(currentNumberFormat,buf,
+                                  cal.getMaximum(Calendar.HOUR_OF_DAY)+1,
+                                  count, maxIntCount);
+            else
+                zeroPaddingNumber(currentNumberFormat,buf, value, count, maxIntCount);
+            break;
+        case 8: // 'S' - FRACTIONAL_SECOND
+            // Fractional seconds left-justify
+            {
+                numberFormat.setMinimumIntegerDigits(Math.min(3, count));
+                numberFormat.setMaximumIntegerDigits(maxIntCount);
+                if (count == 1) {
+                    value = (value + 50) / 100;
+                } else if (count == 2) {
+                    value = (value + 5) / 10;
+                }
+                FieldPosition p = new FieldPosition(-1);
+                numberFormat.format((long) value, buf, p);
+                if (count > 3) {
+                    numberFormat.setMinimumIntegerDigits(count - 3);
+                    numberFormat.format(0L, buf, p);
+                }
+            }
+            break;
+        case 19: // 'e' - DOW_LOCAL (use DOW_LOCAL for numeric, DAY_OF_WEEK for format names)
+            if (count < 3) {
+                zeroPaddingNumber(currentNumberFormat,buf, value, count, maxIntCount);
+                break;
+            }
+            // For alpha day-of-week, we don't want DOW_LOCAL,
+            // we need the standard DAY_OF_WEEK.
+            value = cal.get(Calendar.DAY_OF_WEEK);
+            // fall through, do not break here
+        case 9: // 'E' - DAY_OF_WEEK
+            if (count == 5) {
+                safeAppend(formatData.narrowWeekdays, value, buf);
+            } else if (count == 4) {
+                safeAppend(formatData.weekdays, value, buf);
+            } else {// count <= 3, use abbreviated form if exists
+                safeAppend(formatData.shortWeekdays, value, buf);
+            }
+            break;
+        case 14: // 'a' - AM_PM
+            safeAppend(formatData.ampms, value, buf);
+            break;
+        case 15: // 'h' - HOUR (1..12)
+            if (value == 0)
+                zeroPaddingNumber(currentNumberFormat,buf,
+                                  cal.getLeastMaximum(Calendar.HOUR)+1,
+                                  count, maxIntCount);
+            else
+                zeroPaddingNumber(currentNumberFormat,buf, value, count, maxIntCount);
+            break;
+        case 17: // 'z' - ZONE_OFFSET
+            if (count < 4) {
+                // "z", "zz", "zzz"
+                zoneString = formatData.getZoneStringFormat().getSpecificShortString(cal, true /* commonly used only */);
+            } else {
+                zoneString = formatData.getZoneStringFormat().getSpecificLongString(cal);
+            }
+            if (zoneString != null && zoneString.length() != 0) {
+                buf.append(zoneString);
+            } else {
+                // Use localized GMT format as fallback
+                appendGMT(currentNumberFormat,buf, cal);
+            }
+            break;
+        case 23: // 'Z' - TIMEZONE_RFC
+            if (count < 4) {
+                // RFC822 format, must use ASCII digits
+                int val = (cal.get(Calendar.ZONE_OFFSET) + cal.get(Calendar.DST_OFFSET));
+                char sign = '+';
+                if (val < 0) {
+                    val = -val;
+                    sign = '-';
+                }
+                buf.append(sign);
+
+                int offsetH = val / millisPerHour;
+                val = val % millisPerHour;
+                int offsetM = val / millisPerMinute;
+                val = val % millisPerMinute;
+                int offsetS = val / millisPerSecond;
+
+                int num = 0, denom = 0;
+                if (offsetS == 0) {
+                    val = offsetH*100 + offsetM; // HHmm
+                    num = val % 10000;
+                    denom = 1000;
+                } else {
+                    val = offsetH*10000 + offsetM*100 + offsetS; // HHmmss
+                    num = val % 1000000;
+                    denom = 100000;
+                }
+                while (denom >= 1) {
+                    char digit = (char)((num / denom) + '0');
+                    buf.append(digit);
+                    num = num % denom;
+                    denom /= 10;
+                }
+            } else {
+                // long form, localized GMT pattern
+                appendGMT(currentNumberFormat,buf, cal);
+            }
+            break;
+        case 24: // 'v' - TIMEZONE_GENERIC
+            if (count == 1) {
+                // "v"
+                zoneString = formatData.getZoneStringFormat().getGenericShortString(cal, true /* commonly used only */);
+            } else if (count == 4) {
+                // "vvvv"
+                zoneString = formatData.getZoneStringFormat().getGenericLongString(cal);
+            }
+            if (zoneString != null && zoneString.length() != 0) {
+                buf.append(zoneString);
+            } else {
+                // Use localized GMT format as fallback
+                appendGMT(currentNumberFormat,buf, cal);
+            }
+            break;
+        case 25: // 'c' - STANDALONE DAY (use DOW_LOCAL for numeric, DAY_OF_WEEK for standalone names)
+            if (count < 3) {
+                zeroPaddingNumber(currentNumberFormat,buf, value, 1, maxIntCount);
+                break;
+            }
+            // For alpha day-of-week, we don't want DOW_LOCAL,
+            // we need the standard DAY_OF_WEEK.
+            value = cal.get(Calendar.DAY_OF_WEEK);
+            if (count == 5) {
+                safeAppend(formatData.standaloneNarrowWeekdays, value, buf);
+            } else if (count == 4) {
+                safeAppend(formatData.standaloneWeekdays, value, buf);
+            } else { // count == 3
+                safeAppend(formatData.standaloneShortWeekdays, value, buf);
+            }
+            break;
+        case 26: // 'L' - STANDALONE MONTH
+            if (count == 5) {
+                safeAppend(formatData.standaloneNarrowMonths, value, buf);
+            } else if (count == 4) {
+                safeAppend(formatData.standaloneMonths, value, buf);
+            } else if (count == 3) {
+                safeAppend(formatData.standaloneShortMonths, value, buf);
+            } else {
+                zeroPaddingNumber(currentNumberFormat,buf, value+1, count, maxIntCount);
+            }
+            break;
+        case 27: // 'Q' - QUARTER
+            if (count >= 4) {
+                safeAppend(formatData.quarters, value/3, buf);
+            } else if (count == 3) {
+                safeAppend(formatData.shortQuarters, value/3, buf);
+            } else {
+                zeroPaddingNumber(currentNumberFormat,buf, (value/3)+1, count, maxIntCount);
+            }
+            break;
+        case 28: // 'q' - STANDALONE QUARTER
+            if (count >= 4) {
+                safeAppend(formatData.standaloneQuarters, value/3, buf);
+            } else if (count == 3) {
+                safeAppend(formatData.standaloneShortQuarters, value/3, buf);
+            } else {
+                zeroPaddingNumber(currentNumberFormat,buf, (value/3)+1, count, maxIntCount);
+            }
+            break;
+        case 29: // 'V' - TIMEZONE_SPECIAL
+            if (count == 1) {
+                // "V"
+                zoneString = formatData.getZoneStringFormat().getSpecificShortString(cal, false /* ignoring commonly used */);
+            } else if (count == 4) {
+                // "VVVV"
+                zoneString = formatData.getZoneStringFormat().getGenericLocationString(cal);
+            }
+            if (zoneString != null && zoneString.length() != 0) {
+                buf.append(zoneString);
+            } else {
+                // Use localized GMT format as fallback
+                appendGMT(currentNumberFormat,buf, cal);
+            }
+            break;
+        default:
+            // case 3: // 'd' - DATE
+            // case 5: // 'H' - HOUR_OF_DAY (0..23)
+            // case 6: // 'm' - MINUTE
+            // case 7: // 's' - SECOND
+            // case 10: // 'D' - DAY_OF_YEAR
+            // case 11: // 'F' - DAY_OF_WEEK_IN_MONTH
+            // case 12: // 'w' - WEEK_OF_YEAR
+            // case 13: // 'W' - WEEK_OF_MONTH
+            // case 16: // 'K' - HOUR (0..11)
+            // case 18: // 'Y' - YEAR_WOY
+            // case 20: // 'u' - EXTENDED_YEAR
+            // case 21: // 'g' - JULIAN_DAY
+            // case 22: // 'A' - MILLISECONDS_IN_DAY
+
+            zeroPaddingNumber(currentNumberFormat,buf, value, count, maxIntCount);
+            break;
+        } // switch (patternCharIndex)
+
+        // Set the FieldPosition (for the first occurrence only)
+        if (pos.getBeginIndex() == pos.getEndIndex()) {
+            if (pos.getField() == PATTERN_INDEX_TO_DATE_FORMAT_FIELD[patternCharIndex]) {
+                pos.setBeginIndex(beginOffset);
+                pos.setEndIndex(beginOffset + buf.length() - bufstart);
+            }
+//#if defined(FOUNDATION10) || defined(J2SE13)
+//#else
+            else if (pos.getFieldAttribute() == PATTERN_INDEX_TO_DATE_FORMAT_ATTRIBUTE[patternCharIndex]) {
+                pos.setBeginIndex(beginOffset);
+                pos.setEndIndex(beginOffset + buf.length() - bufstart);
+            }
+//#endif
+        }
+    }
+
+    private static void safeAppend(String[] array, int value, StringBuffer appendTo) {
+        if (array != null && value >= 0 && value < array.length) {
+            appendTo.append(array[value]);
+        }
+    }
+
+    /*
+     * PatternItem store parsed date/time field pattern information.
+     */
+    private static class PatternItem {
+        final char type;
+        final int length;
+        final boolean isNumeric;
+
+        PatternItem(char type, int length) {
+            this.type = type;
+            this.length = length;
+            isNumeric = isNumeric(type, length);
+        }
+    }
+
+    private static ICUCache PARSED_PATTERN_CACHE = new SimpleCache();
+    private transient Object[] patternItems;
+
+    /*
+     * Returns parsed pattern items.  Each item is either String or
+     * PatternItem.
+     */
+    private Object[] getPatternItems() {
+        if (patternItems != null) {
+            return patternItems;
+        }
+
+        patternItems = (Object[])PARSED_PATTERN_CACHE.get(pattern);
+        if (patternItems != null) {
+            return patternItems;
+        }
+
+        boolean isPrevQuote = false;
+        boolean inQuote = false;
+        StringBuffer text = new StringBuffer();
+        char itemType = 0;  // 0 for string literal, otherwise date/time pattern character
+        int itemLength = 1;
+
+        List items = new ArrayList();
+
+        for (int i = 0; i < pattern.length(); i++) {
+            char ch = pattern.charAt(i);
+            if (ch == '\'') {
+                if (isPrevQuote) {
+                    text.append('\'');
+                    isPrevQuote = false;
+                } else {
+                    isPrevQuote = true;
+                    if (itemType != 0) {
+                        items.add(new PatternItem(itemType, itemLength));
+                        itemType = 0;
+                    }
+                }
+                inQuote = !inQuote;
+            } else {
+                isPrevQuote = false;
+                if (inQuote) {
+                    text.append(ch);
+                } else {
+                    if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')) {
+                        // a date/time pattern character
+                        if (ch == itemType) {
+                            itemLength++;
+                        } else {
+                            if (itemType == 0) {
+                                if (text.length() > 0) {
+                                    items.add(text.toString());
+                                    text.setLength(0);
+                                }
+                            } else {
+                                items.add(new PatternItem(itemType, itemLength));
+                            }
+                            itemType = ch;
+                            itemLength = 1;
+                        }
+                    } else {
+                        // a string literal
+                        if (itemType != 0) {
+                            items.add(new PatternItem(itemType, itemLength));
+                            itemType = 0;
+                        }
+                        text.append(ch);
+                    }
+                }
+            }
+        }
+        // handle last item
+        if (itemType == 0) {
+            if (text.length() > 0) {
+                items.add(text.toString());
+                text.setLength(0);
+            }
+        } else {
+            items.add(new PatternItem(itemType, itemLength));
+        }
+
+        patternItems = new Object[items.size()];
+        items.toArray(patternItems);
+
+        PARSED_PATTERN_CACHE.put(pattern, patternItems);
+        
+        return patternItems;
+    }
+
+    /*
+     * Time zone localized GMT format stuffs
+     */
+    private static final String STR_GMT = "GMT";
+    private static final String STR_UT = "UT";
+    private static final String STR_UTC = "UTC";
+    private static final int STR_GMT_LEN = 3;
+    private static final int STR_UT_LEN = 2;
+    private static final int STR_UTC_LEN = 3;
+    private static final char PLUS = '+';
+    private static final char MINUS = '-';
+    private static final char COLON = ':';
+
+    private void appendGMT(NumberFormat currentNumberFormat,StringBuffer buf, Calendar cal) {
+        int offset = cal.get(Calendar.ZONE_OFFSET) + cal.get(Calendar.DST_OFFSET);
+
+        if (isDefaultGMTFormat()) {
+            formatGMTDefault(currentNumberFormat,buf, offset);
+        } else {
+            int sign = DateFormatSymbols.OFFSET_POSITIVE;
+            if (offset < 0) {
+                offset = -offset;
+                sign = DateFormatSymbols.OFFSET_NEGATIVE;
+            }
+            int width = offset%(60*1000) == 0 ? DateFormatSymbols.OFFSET_HM : DateFormatSymbols.OFFSET_HMS;
+
+            MessageFormat fmt = getGMTFormatter(sign, width);
+            fmt.format(new Object[] {new Long(offset)}, buf, null);
+        }
+    }
+
+    private void formatGMTDefault(NumberFormat currentNumberFormat,StringBuffer buf, int offset) {
+        buf.append(STR_GMT);
+        if (offset >= 0) {
+            buf.append(PLUS);
+        } else {
+            buf.append(MINUS);
+            offset = -offset;
+        }
+        offset /= 1000; // now in seconds
+        int sec = offset % 60;
+        offset /= 60;
+        int min = offset % 60;
+        int hour = offset / 60;
+
+        zeroPaddingNumber(currentNumberFormat,buf, hour, 2, 2);
+        buf.append(COLON);
+        zeroPaddingNumber(currentNumberFormat,buf, min, 2, 2);
+        if (sec != 0) {
+            buf.append(COLON);
+            zeroPaddingNumber(currentNumberFormat,buf, sec, 2, 2);
+        }
+    }
+
+    private Integer parseGMT(String text, ParsePosition pos, NumberFormat currentNumberFormat) {
+        if (!isDefaultGMTFormat()) {
+            int start = pos.getIndex();
+            String gmtPattern = formatData.gmtFormat;
+
+            // Quick check
+            boolean prefixMatch = false;
+            int prefixLen = gmtPattern.indexOf('{');
+            if (prefixLen > 0 && text.regionMatches(start, gmtPattern, 0, prefixLen)) {
+                prefixMatch = true;
+            }
+
+            if (prefixMatch) {
+                // Prefix matched
+                MessageFormat fmt;
+                Object[] parsedObjects;
+                int offset;
+
+                // Try negative Hms
+                fmt = getGMTFormatter(DateFormatSymbols.OFFSET_NEGATIVE, DateFormatSymbols.OFFSET_HMS);
+                parsedObjects = fmt.parse(text, pos);
+                if ((parsedObjects != null) && (parsedObjects[0] instanceof Date)
+                        && (pos.getIndex() - start) >= getGMTFormatMinHMSLen(DateFormatSymbols.OFFSET_NEGATIVE)) {
+                    offset = (int)((Date)parsedObjects[0]).getTime();
+                    return new Integer(-offset /* negative */);
+                }
+
+                // Reset ParsePosition
+                pos.setIndex(start);
+                pos.setErrorIndex(-1);
+
+                // Try positive Hms
+                fmt = getGMTFormatter(DateFormatSymbols.OFFSET_POSITIVE, DateFormatSymbols.OFFSET_HMS);
+                parsedObjects = fmt.parse(text, pos);
+                if ((parsedObjects != null) && (parsedObjects[0] instanceof Date)
+                        && (pos.getIndex() - start) >= getGMTFormatMinHMSLen(DateFormatSymbols.OFFSET_POSITIVE)) {
+                    offset = (int)((Date)parsedObjects[0]).getTime();
+                    return new Integer(offset);
+                }
+
+                // Reset ParsePosition
+                pos.setIndex(start);
+                pos.setErrorIndex(-1);
+
+                // Try negative Hm
+                fmt = getGMTFormatter(DateFormatSymbols.OFFSET_NEGATIVE, DateFormatSymbols.OFFSET_HM);
+                parsedObjects = fmt.parse(text, pos);
+                if ((parsedObjects != null) && (parsedObjects[0] instanceof Date)) {
+                    offset = (int)((Date)parsedObjects[0]).getTime();
+                    return new Integer(-offset /* negative */);
+                }
+
+                // Reset ParsePosition
+                pos.setIndex(start);
+                pos.setErrorIndex(-1);
+
+                // Try positive Hm
+                fmt = getGMTFormatter(DateFormatSymbols.OFFSET_POSITIVE, DateFormatSymbols.OFFSET_HM);
+                parsedObjects = fmt.parse(text, pos);
+                if ((parsedObjects != null) && (parsedObjects[0] instanceof Date)) {
+                    offset = (int)((Date)parsedObjects[0]).getTime();
+                    return new Integer(offset);
+                }
+
+                // Reset ParsePosition
+                pos.setIndex(start);
+                pos.setErrorIndex(-1);
+            }
+        }
+
+        return parseGMTDefault(text, pos, currentNumberFormat);
+    }
+
+    private Integer parseGMTDefault(String text, ParsePosition pos, NumberFormat currentNumberFormat) {
+        int start = pos.getIndex();
+
+        if (start + STR_UT_LEN + 1 >= text.length()) {
+            pos.setErrorIndex(start);
+            return null;
+        }
+
+        int cur = start;
+        // "GMT"
+        if (text.regionMatches(true, start, STR_GMT, 0, STR_GMT_LEN)) {
+            cur += STR_GMT_LEN;
+        } else if (text.regionMatches(true, start, STR_UT, 0, STR_UT_LEN)) {
+            cur += STR_UT_LEN;
+        } else {
+            pos.setErrorIndex(start);
+            return null;
+        }
+        // Sign
+        boolean negative = false;
+        if (text.charAt(cur) == MINUS) {
+            negative = true;
+        } else if (text.charAt(cur) != PLUS) {
+            pos.setErrorIndex(cur);
+            return null;
+        }
+        cur++;
+
+        // Numbers
+        int numLen;
+        pos.setIndex(cur);
+
+        Number n = parseInt(text, 6, pos, false,currentNumberFormat);
+        numLen = pos.getIndex() - cur;
+
+        if (n == null || numLen <= 0 || numLen > 6) {
+            pos.setIndex(start);
+            pos.setErrorIndex(cur);
+            return null;
+        }
+
+        int numVal = n.intValue();
+
+        int hour = 0;
+        int min = 0;
+        int sec = 0;
+
+        if (numLen <= 2) {
+            // H[H][:mm[:ss]]
+            hour = numVal;
+            cur += numLen;
+            if (cur + 2 < text.length() && text.charAt(cur) == COLON) {
+                cur++;
+                pos.setIndex(cur);
+                n = parseInt(text, 2, pos, false,currentNumberFormat);
+                numLen = pos.getIndex() - cur;
+                if (n != null && numLen == 2) {
+                    // got minute field
+                    min = n.intValue();
+                    cur += numLen;
+                    if (cur + 2 < text.length() && text.charAt(cur) == COLON) {
+                        cur++;
+                        pos.setIndex(cur);
+                        n = parseInt(text, 2, pos, false,currentNumberFormat);
+                        numLen = pos.getIndex() - cur;
+                        if (n != null && numLen == 2) {
+                            // got second field
+                            sec = n.intValue();
+                        } else {
+                            // reset position
+                            pos.setIndex(cur - 1);
+                            pos.setErrorIndex(-1);
+                        }
+                    }
+                } else {
+                    // reset postion
+                    pos.setIndex(cur - 1);
+                    pos.setErrorIndex(-1);
+                }
+            }
+        } else if (numLen == 3 || numLen == 4) {
+            // Hmm or HHmm
+            hour = numVal / 100;
+            min = numVal % 100;
+        } else { // numLen == 5 || numLen == 6
+            // Hmmss or HHmmss
+            hour = numVal / 10000;
+            min = (numVal % 10000) / 100;
+            sec = numVal % 100;
+        }
+
+        int offset = ((hour*60 + min)*60 + sec)*1000;
+        if (negative) {
+            offset = -offset;
+        }
+        return new Integer(offset);
+    }
+
+    transient private WeakReference[] gmtfmtCache;
+
+    private MessageFormat getGMTFormatter(int sign, int width) {
+        MessageFormat fmt = null;
+        if (gmtfmtCache == null) {
+            gmtfmtCache = new WeakReference[4];
+        }
+        int cacheIdx = sign*2 + width;
+        if (gmtfmtCache[cacheIdx] != null) {
+            fmt = (MessageFormat)gmtfmtCache[cacheIdx].get();
+        }
+        if (fmt == null) {
+            fmt = new MessageFormat(formatData.gmtFormat);
+            GregorianCalendar gcal = new GregorianCalendar(TimeZone.getTimeZone("Etc/UTC"));
+            SimpleDateFormat sdf = (SimpleDateFormat)this.clone();
+            sdf.setCalendar(gcal);
+            sdf.applyPattern(formatData.getGmtHourFormat(sign, width));
+            fmt.setFormat(0, sdf);
+            gmtfmtCache[cacheIdx] = new WeakReference(fmt);
+        }
+        return fmt;
+    }
+
+    transient private int[] gmtFormatHmsMinLen = null;
+
+    private int getGMTFormatMinHMSLen(int sign) {
+        if (gmtFormatHmsMinLen == null) {
+            gmtFormatHmsMinLen = new int[2];
+            Long offset = new Long(60*60*1000); // 1 hour
+
+            StringBuffer buf = new StringBuffer();
+            MessageFormat fmtNeg = getGMTFormatter(DateFormatSymbols.OFFSET_NEGATIVE, DateFormatSymbols.OFFSET_HMS);
+            fmtNeg.format(new Object[] {offset}, buf, null);
+            gmtFormatHmsMinLen[0] = buf.length();
+
+            buf.setLength(0);
+            MessageFormat fmtPos = getGMTFormatter(DateFormatSymbols.OFFSET_POSITIVE, DateFormatSymbols.OFFSET_HMS);
+            fmtPos.format(new Object[] {offset}, buf, null);
+            gmtFormatHmsMinLen[1] = buf.length();
+        }
+        return gmtFormatHmsMinLen[(sign < 0 ? 0 : 1)];
+    }
+
+    private boolean isDefaultGMTFormat() {
+        // GMT pattern
+        if (!DateFormatSymbols.DEFAULT_GMT_PATTERN.equals(formatData.getGmtFormat())) {
+            return false;
+        }
+        // GMT offset hour patters
+        boolean res = true;
+        for (int sign = 0; sign < 2 && res; sign++) {
+            for (int width = 0; width < 2; width++) {
+                if (!DateFormatSymbols.DEFAULT_GMT_HOUR_PATTERNS[sign][width].equals(formatData.getGmtHourFormat(sign, width))) {
+                    res = false;
+                    break;
+                }
+            }
+        }
+        return res;
+    }
+
+    /*
+     * Internal method. Returns null if the value of an array is empty, or if the
+     * index is out of bounds
+     */
+/*    private String getZoneArrayValue(String[] zs, int ix) {
+        if (ix >= 0 && ix < zs.length) {
+            String result = zs[ix];
+            if (result != null && result.length() != 0) {
+                return result;
+            }
+        }
+        return null;
+    }*/
+
+    /**
+     * Internal high-speed method.  Reuses a StringBuffer for results
+     * instead of creating a String on the heap for each call.
+     * @internal
+     * @deprecated This API is ICU internal only.
+     */
+    protected void zeroPaddingNumber(NumberFormat nf,StringBuffer buf, int value,
+                                     int minDigits, int maxDigits) {
+        if (useLocalZeroPaddingNumberFormat) {
+            fastZeroPaddingNumber(buf, value, minDigits, maxDigits);
+        } else {
+            nf.setMinimumIntegerDigits(minDigits);
+            nf.setMaximumIntegerDigits(maxDigits);
+            nf.format(value, buf, new FieldPosition(-1));
+        }
+    }
+
+    /**
+     * Overrides superclass method
+     * @stable ICU 2.0
+     */
+    public void setNumberFormat(NumberFormat newNumberFormat) {
+        // Override this method to update local zero padding number formatter
+        super.setNumberFormat(newNumberFormat);
+        initLocalZeroPaddingNumberFormat();
+    }
+
+    private void initLocalZeroPaddingNumberFormat() {
+        if (numberFormat instanceof DecimalFormat) {
+            zeroDigit = ((DecimalFormat)numberFormat).getDecimalFormatSymbols().getZeroDigit();
+            useLocalZeroPaddingNumberFormat = true;
+        } else if (numberFormat instanceof DateNumberFormat) {
+            zeroDigit = ((DateNumberFormat)numberFormat).getZeroDigit();
+            useLocalZeroPaddingNumberFormat = true;
+        } else {
+            useLocalZeroPaddingNumberFormat = false;
+        }
+
+        if (useLocalZeroPaddingNumberFormat) {
+            decimalBuf = new char[10];  // sufficient for int numbers
+        }
+    }
+
+    // If true, use local version of zero padding number format
+    private transient boolean useLocalZeroPaddingNumberFormat;
+    private transient char zeroDigit;
+    private transient char[] decimalBuf;
+
+    /*
+     * Lightweight zero padding integer number format function.
+     * 
+     * Note: This implementation is almost equivalent to format method in DateNumberFormat.
+     * In the method zeroPaddingNumber above should be able to use the one in DateNumberFormat,
+     * but, it does not help IBM J9's JIT to optimize the performance much.  In simple repeative
+     * date format test case, having local implementation is ~10% faster than using one in
+     * DateNumberFormat on IBM J9 VM.  On Sun Hotspot VM, I do not see such difference.
+     * 
+     * -Yoshito
+     */
+    private void fastZeroPaddingNumber(StringBuffer buf, int value, int minDigits, int maxDigits) {
+        int limit = decimalBuf.length < maxDigits ? decimalBuf.length : maxDigits;
+        int index = limit - 1;
+        while (true) {
+            decimalBuf[index] = (char)((value % 10) + zeroDigit);
+            value /= 10;
+            if (index == 0 || value == 0) {
+                break;
+            }
+            index--;
+        }
+        int padding = minDigits - (limit - index);
+        for (; padding > 0; padding--) {
+            decimalBuf[--index] = zeroDigit;
+        }
+        int length = limit - index;
+        buf.append(decimalBuf, index, length);        
+    }
+
+    /**
+     * Formats a number with the specified minimum and maximum number of digits.
+     * @stable ICU 2.0
+     */
+    protected String zeroPaddingNumber(long value, int minDigits, int maxDigits)
+    {
+        numberFormat.setMinimumIntegerDigits(minDigits);
+        numberFormat.setMaximumIntegerDigits(maxDigits);
+        return numberFormat.format(value);
+    }
+
+    /**
+     * Format characters that indicate numeric fields.  The character
+     * at index 0 is treated specially.
+     */
+    private static final String NUMERIC_FORMAT_CHARS = "MYyudehHmsSDFwWkK";
+
+    /**
+     * Return true if the given format character, occuring count
+     * times, represents a numeric field.
+     */
+    private static final boolean isNumeric(char formatChar, int count) {
+        int i = NUMERIC_FORMAT_CHARS.indexOf(formatChar);
+        return (i > 0 || (i == 0 && count < 3));
+    }
+
+    /**
+     * Overrides DateFormat
+     * @see DateFormat
+     * @stable ICU 2.0
+     */
+    public void parse(String text, Calendar cal, ParsePosition parsePos)
+    {
+        TimeZone backupTZ = null;
+        Calendar resultCal = null;
+        if (cal != calendar && !cal.getType().equals(calendar.getType())) {
+            // Different calendar type
+            // We use the time/zone from the input calendar, but
+            // do not use the input calendar for field calculation.
+            calendar.setTimeInMillis(cal.getTimeInMillis());
+            backupTZ = calendar.getTimeZone();
+            calendar.setTimeZone(cal.getTimeZone());
+            resultCal = cal;
+            cal = calendar;
+        }
+
+        int pos = parsePos.getIndex();
+        int start = pos;
+
+        // Reset tztype
+        tztype = TZTYPE_UNK;
+        boolean[] ambiguousYear = { false };
+
+        // item index for the first numeric field within a contiguous numeric run
+        int numericFieldStart = -1;
+        // item length for the first numeric field within a contiguous numeric run
+        int numericFieldLength = 0;
+        // start index of numeric text run in the input text
+        int numericStartPos = 0;
+
+        Object[] items = getPatternItems();
+        int i = 0;
+        while (i < items.length) {
+            if (items[i] instanceof PatternItem) {
+                // Handle pattern field
+                PatternItem field = (PatternItem)items[i];
+                if (field.isNumeric) {
+                    // Handle fields within a run of abutting numeric fields.  Take
+                    // the pattern "HHmmss" as an example. We will try to parse
+                    // 2/2/2 characters of the input text, then if that fails,
+                    // 1/2/2.  We only adjust the width of the leftmost field; the
+                    // others remain fixed.  This allows "123456" => 12:34:56, but
+                    // "12345" => 1:23:45.  Likewise, for the pattern "yyyyMMdd" we
+                    // try 4/2/2, 3/2/2, 2/2/2, and finally 1/2/2.
+                    if (numericFieldStart == -1) {
+                        // check if this field is followed by abutting another numeric field
+                        if ((i + 1) < items.length 
+                                && (items[i + 1] instanceof PatternItem)
+                                && ((PatternItem)items[i + 1]).isNumeric) {
+                            // record the first numeric field within a numeric text run
+                            numericFieldStart = i;
+                            numericFieldLength = field.length;
+                            numericStartPos = pos; 
+                        }
+                    }
+                }
+                if (numericFieldStart != -1) {
+                    // Handle a numeric field within abutting numeric fields
+                    int len = field.length;
+                    if (numericFieldStart == i) {
+                        len = numericFieldLength;
+                    }
+
+                    // Parse a numeric field
+                    pos = subParse(text, pos, field.type, len,
+                            true, false, ambiguousYear, cal);
+
+                    if (pos < 0) {
+                        // If the parse fails anywhere in the numeric run, back up to the
+                        // start of the run and use shorter pattern length for the first
+                        // numeric field.
+                        --numericFieldLength;
+                        if (numericFieldLength == 0) {
+                            // can not make shorter any more
+                            parsePos.setIndex(start);
+                            parsePos.setErrorIndex(pos);
+                            if (backupTZ != null) {
+                                calendar.setTimeZone(backupTZ);
+                            }
+                            return;
+                        }
+                        i = numericFieldStart;
+                        pos = numericStartPos;
+                        continue;
+                    }
+
+                } else {
+                    // Handle a non-numeric field or a non-abutting numeric field
+                    numericFieldStart = -1;
+
+                    int s = pos;
+                    pos = subParse(text, pos, field.type, field.length,
+                            false, true, ambiguousYear, cal);
+                    if (pos < 0) {
+                        parsePos.setIndex(start);
+                        parsePos.setErrorIndex(s);
+                        if (backupTZ != null) {
+                            calendar.setTimeZone(backupTZ);
+                        }
+                        return;
+                    }
+                }
+            } else {
+                // Handle literal pattern text literal
+                numericFieldStart = -1;
+
+                String patl = (String)items[i];
+                int plen = patl.length();
+                int tlen = text.length();
+                int idx = 0;
+                while (idx < plen && pos < tlen) {
+                    char pch = patl.charAt(idx);
+                    char ich = text.charAt(pos);
+                    if (UCharacterProperty.isRuleWhiteSpace(pch) && UCharacterProperty.isRuleWhiteSpace(ich)) {
+                        // White space characters found in both patten and input.
+                        // Skip contiguous white spaces.
+                        while ((idx + 1) < plen &&
+                                UCharacterProperty.isRuleWhiteSpace(patl.charAt(idx + 1))) {
+                             ++idx;
+                        }
+                        while ((pos + 1) < tlen &&
+                                UCharacterProperty.isRuleWhiteSpace(text.charAt(pos + 1))) {
+                             ++pos;
+                        }
+                    } else if (pch != ich) {
+                        break;
+                    }
+                    ++idx;
+                    ++pos;
+                }
+                if (idx != plen) {
+                    // Set the position of mismatch
+                    parsePos.setIndex(start);
+                    parsePos.setErrorIndex(pos);
+                    if (backupTZ != null) {
+                        calendar.setTimeZone(backupTZ);
+                    }
+                    return;
+                }
+            }
+            ++i;
+        }
+
+        // At this point the fields of Calendar have been set.  Calendar
+        // will fill in default values for missing fields when the time
+        // is computed.
+
+        parsePos.setIndex(pos);
+
+        // This part is a problem:  When we call parsedDate.after, we compute the time.
+        // Take the date April 3 2004 at 2:30 am.  When this is first set up, the year
+        // will be wrong if we're parsing a 2-digit year pattern.  It will be 1904.
+        // April 3 1904 is a Sunday (unlike 2004) so it is the DST onset day.  2:30 am
+        // is therefore an "impossible" time, since the time goes from 1:59 to 3:00 am
+        // on that day.  It is therefore parsed out to fields as 3:30 am.  Then we
+        // add 100 years, and get April 3 2004 at 3:30 am.  Note that April 3 2004 is
+        // a Saturday, so it can have a 2:30 am -- and it should. [LIU]
+        /*
+          Date parsedDate = cal.getTime();
+          if( ambiguousYear[0] && !parsedDate.after(getDefaultCenturyStart()) ) {
+          cal.add(Calendar.YEAR, 100);
+          parsedDate = cal.getTime();
+          }
+        */
+        // Because of the above condition, save off the fields in case we need to readjust.
+        // The procedure we use here is not particularly efficient, but there is no other
+        // way to do this given the API restrictions present in Calendar.  We minimize
+        // inefficiency by only performing this computation when it might apply, that is,
+        // when the two-digit year is equal to the start year, and thus might fall at the
+        // front or the back of the default century.  This only works because we adjust
+        // the year correctly to start with in other cases -- see subParse().
+        try {
+            if (ambiguousYear[0] || tztype != TZTYPE_UNK) {
+                // We need a copy of the fields, and we need to avoid triggering a call to
+                // complete(), which will recalculate the fields.  Since we can't access
+                // the fields[] array in Calendar, we clone the entire object.  This will
+                // stop working if Calendar.clone() is ever rewritten to call complete().
+                Calendar copy;
+                if (ambiguousYear[0]) { // the two-digit year == the default start year
+                    copy = (Calendar)cal.clone();
+                    Date parsedDate = copy.getTime();
+                    if (parsedDate.before(getDefaultCenturyStart())) {
+                        // We can't use add here because that does a complete() first.
+                        cal.set(Calendar.YEAR, getDefaultCenturyStartYear() + 100);
+                    }
+                }
+                if (tztype != TZTYPE_UNK) {
+                    copy = (Calendar)cal.clone();
+                    TimeZone tz = copy.getTimeZone();
+                    BasicTimeZone btz = null;
+                    if (tz instanceof BasicTimeZone) {
+                        btz = (BasicTimeZone)tz;
+                    }
+
+                    // Get local millis
+                    copy.set(Calendar.ZONE_OFFSET, 0);
+                    copy.set(Calendar.DST_OFFSET, 0);
+                    long localMillis = copy.getTimeInMillis();
+
+                    // Make sure parsed time zone type (Standard or Daylight)
+                    // matches the rule used by the parsed time zone.
+                    int[] offsets = new int[2];
+                    if (btz != null) {
+                        if (tztype == TZTYPE_STD) {
+                            btz.getOffsetFromLocal(localMillis,
+                                    BasicTimeZone.LOCAL_STD, BasicTimeZone.LOCAL_STD, offsets);
+                        } else {
+                            btz.getOffsetFromLocal(localMillis,
+                                    BasicTimeZone.LOCAL_DST, BasicTimeZone.LOCAL_DST, offsets);
+                        }
+                    } else {
+                        // No good way to resolve ambiguous time at transition,
+                        // but following code work in most case.
+                        tz.getOffset(localMillis, true, offsets);
+
+                        if (tztype == TZTYPE_STD && offsets[1] != 0 || tztype == TZTYPE_DST && offsets[1] == 0) {
+                            // Roll back one day and try it again.
+                            // Note: This code assumes 1. timezone transition only happens once within 24 hours at max
+                            // 2. the difference of local offsets at the transition is less than 24 hours.
+                            tz.getOffset(localMillis - (24*60*60*1000), true, offsets);
+                        }
+                    }
+
+                    // Now, compare the results with parsed type, either standard or daylight saving time
+                    int resolvedSavings = offsets[1];
+                    if (tztype == TZTYPE_STD) {
+                        if (offsets[1] != 0) {
+                            // Override DST_OFFSET = 0 in the result calendar
+                            resolvedSavings = 0;
+                        }
+                    } else { // tztype == TZTYPE_DST
+                        if (offsets[1] == 0) {
+                            if (btz != null) {
+                                long time = localMillis + offsets[0];
+                                // We use the nearest daylight saving time rule.
+                                TimeZoneTransition beforeTrs, afterTrs;
+                                long beforeT = time, afterT = time;
+                                int beforeSav = 0, afterSav = 0;
+
+                                // Search for DST rule before or on the time
+                                while (true) {
+                                    beforeTrs = btz.getPreviousTransition(beforeT, true);
+                                    if (beforeTrs == null) {
+                                        break;
+                                    }
+                                    beforeT = beforeTrs.getTime() - 1;
+                                    beforeSav = beforeTrs.getFrom().getDSTSavings();
+                                    if (beforeSav != 0) {
+                                        break;
+                                    }
+                                }
+
+                                // Search for DST rule after the time
+                                while (true) {
+                                    afterTrs = btz.getNextTransition(afterT, false);
+                                    if (afterTrs == null) {
+                                        break;
+                                    }
+                                    afterT = afterTrs.getTime();
+                                    afterSav = afterTrs.getTo().getDSTSavings();
+                                    if (afterSav != 0) {
+                                        break;
+                                    }
+                                }
+
+                                if (beforeTrs != null && afterTrs != null) {
+                                    if (time - beforeT > afterT - time) {
+                                        resolvedSavings = afterSav;
+                                    } else {
+                                        resolvedSavings = beforeSav;
+                                    }
+                                } else if (beforeTrs != null && beforeSav != 0) {
+                                    resolvedSavings = beforeSav;
+                                } else if (afterTrs != null && afterSav != 0) {
+                                    resolvedSavings = afterSav;
+                                } else {
+                                    resolvedSavings = btz.getDSTSavings();
+                                }
+                            } else {
+                                resolvedSavings = tz.getDSTSavings();
+                            }
+                            if (resolvedSavings == 0) {
+                                // Final fallback
+                                resolvedSavings = millisPerHour;
+                            }
+                        }
+                    }
+                    cal.set(Calendar.ZONE_OFFSET, offsets[0]);
+                    cal.set(Calendar.DST_OFFSET, resolvedSavings);
+                }
+            }
+        }
+        // An IllegalArgumentException will be thrown by Calendar.getTime()
+        // if any fields are out of range, e.g., MONTH == 17.
+        catch (IllegalArgumentException e) {
+            parsePos.setErrorIndex(pos);
+            parsePos.setIndex(start);
+            if (backupTZ != null) {
+                calendar.setTimeZone(backupTZ);
+            }
+            return;
+        }
+        // Set the parsed result if local calendar is used
+        // instead of the input calendar
+        if (resultCal != null) {
+            resultCal.setTimeZone(cal.getTimeZone());
+            resultCal.setTimeInMillis(cal.getTimeInMillis());
+        }
+        // Restore the original time zone if required
+        if (backupTZ != null) {
+            calendar.setTimeZone(backupTZ);
+        }
+    }
+
+    /**
+     * Attempt to match the text at a given position against an array of
+     * strings.  Since multiple strings in the array may match (for
+     * example, if the array contains "a", "ab", and "abc", all will match
+     * the input string "abcd") the longest match is returned.  As a side
+     * effect, the given field of <code>cal</code> is set to the index
+     * of the best match, if there is one.
+     * @param text the time text being parsed.
+     * @param start where to start parsing.
+     * @param field the date field being parsed.
+     * @param data the string array to parsed.
+     * @return the new start position if matching succeeded; a negative
+     * number indicating matching failure, otherwise.  As a side effect,
+     * sets the <code>cal</code> field <code>field</code> to the index
+     * of the best match, if matching succeeded.
+     * @stable ICU 2.0
+     */
+    protected int matchString(String text, int start, int field, String[] data, Calendar cal)
+    {
+        int i = 0;
+        int count = data.length;
+
+        if (field == Calendar.DAY_OF_WEEK) i = 1;
+
+        // There may be multiple strings in the data[] array which begin with
+        // the same prefix (e.g., Cerven and Cervenec (June and July) in Czech).
+        // We keep track of the longest match, and return that.  Note that this
+        // unfortunately requires us to test all array elements.
+        int bestMatchLength = 0, bestMatch = -1;
+        for (; i<count; ++i)
+            {
+                int length = data[i].length();
+                // Always compare if we have no match yet; otherwise only compare
+                // against potentially better matches (longer strings).
+                if (length > bestMatchLength &&
+                    text.regionMatches(true, start, data[i], 0, length))
+                    {
+                        bestMatch = i;
+                        bestMatchLength = length;
+                    }
+            }
+        if (bestMatch >= 0)
+            {
+                cal.set(field, bestMatch);
+                return start + bestMatchLength;
+            }
+        return -start;
+    }
+
+    /**
+     * Attempt to match the text at a given position against an array of quarter
+     * strings.  Since multiple strings in the array may match (for
+     * example, if the array contains "a", "ab", and "abc", all will match
+     * the input string "abcd") the longest match is returned.  As a side
+     * effect, the given field of <code>cal</code> is set to the index
+     * of the best match, if there is one.
+     * @param text the time text being parsed.
+     * @param start where to start parsing.
+     * @param field the date field being parsed.
+     * @param data the string array to parsed.
+     * @return the new start position if matching succeeded; a negative
+     * number indicating matching failure, otherwise.  As a side effect,
+     * sets the <code>cal</code> field <code>field</code> to the index
+     * of the best match, if matching succeeded.
+     * @stable ICU 2.0
+     */
+    protected int matchQuarterString(String text, int start, int field, String[] data, Calendar cal)
+    {
+        int i = 0;
+        int count = data.length;
+
+        // There may be multiple strings in the data[] array which begin with
+        // the same prefix (e.g., Cerven and Cervenec (June and July) in Czech).
+        // We keep track of the longest match, and return that.  Note that this
+        // unfortunately requires us to test all array elements.
+        int bestMatchLength = 0, bestMatch = -1;
+        for (; i<count; ++i) {
+            int length = data[i].length();
+            // Always compare if we have no match yet; otherwise only compare
+            // against potentially better matches (longer strings).
+            if (length > bestMatchLength &&
+                text.regionMatches(true, start, data[i], 0, length)) {
+                bestMatch = i;
+                bestMatchLength = length;
+            }
+        }
+        
+        if (bestMatch >= 0) {
+            cal.set(field, bestMatch * 3);
+            return start + bestMatchLength;
+        }
+        
+        return -start;
+    }
+    
+    /**
+     * Protected method that converts one field of the input string into a
+     * numeric field value in <code>cal</code>.  Returns -start (for
+     * ParsePosition) if failed.  Subclasses may override this method to
+     * modify or add parsing capabilities.
+     * @param text the time text to be parsed.
+     * @param start where to start parsing.
+     * @param ch the pattern character for the date field text to be parsed.
+     * @param count the count of a pattern character.
+     * @param obeyCount if true, then the next field directly abuts this one,
+     * and we should use the count to know when to stop parsing.
+     * @param ambiguousYear return parameter; upon return, if ambiguousYear[0]
+     * is true, then a two-digit year was parsed and may need to be readjusted.
+     * @return the new start position if matching succeeded; a negative
+     * number indicating matching failure, otherwise.  As a side effect,
+     * set the appropriate field of <code>cal</code> with the parsed
+     * value.
+     * @stable ICU 2.0
+     */
+    protected int subParse(String text, int start, char ch, int count,
+                           boolean obeyCount, boolean allowNegative,
+                           boolean[] ambiguousYear, Calendar cal)
+    {
+        Number number = null;
+        NumberFormat currentNumberFormat = null;
+        int value = 0;
+        int i;
+        ParsePosition pos = new ParsePosition(0);
+        //int patternCharIndex = DateFormatSymbols.patternChars.indexOf(ch);c
+        int patternCharIndex = -1;
+        if ('A' <= ch && ch <= 'z') {
+            patternCharIndex = PATTERN_CHAR_TO_INDEX[(int)ch - PATTERN_CHAR_BASE];
+        }
+
+        if (patternCharIndex == -1) {
+            return -start;
+        }
+
+        currentNumberFormat = getNumberFormat(ch);
+        int field = PATTERN_INDEX_TO_CALENDAR_FIELD[patternCharIndex];
+
+        // If there are any spaces here, skip over them.  If we hit the end
+        // of the string, then fail.
+        for (;;) {
+            if (start >= text.length()) {
+                return -start;
+            }
+            int c = UTF16.charAt(text, start);
+            if (!UCharacter.isUWhiteSpace(c)) {
+                break;
+            }
+            start += UTF16.getCharCount(c);
+        }
+        pos.setIndex(start);
+
+        // We handle a few special cases here where we need to parse
+        // a number value.  We handle further, more generic cases below.  We need
+        // to handle some of them here because some fields require extra processing on
+        // the parsed value.
+        if (patternCharIndex == 4 /*HOUR_OF_DAY1_FIELD*/ ||
+            patternCharIndex == 15 /*HOUR1_FIELD*/ ||
+            (patternCharIndex == 2 /*MONTH_FIELD*/ && count <= 2) ||
+            patternCharIndex == 1 ||
+            patternCharIndex == 8)
+            {
+                // It would be good to unify this with the obeyCount logic below,
+                // but that's going to be difficult.
+                if (obeyCount)
+                    {
+                        if ((start+count) > text.length()) return -start;
+                        number = parseInt(text, count, pos, allowNegative,currentNumberFormat);
+                    }
+                else number = parseInt(text, pos, allowNegative,currentNumberFormat);
+                if (number == null)
+                    return -start;
+                value = number.intValue();
+            }
+
+        switch (patternCharIndex)
+            {
+            case 0: // 'G' - ERA
+                if (count == 4) {
+                    return matchString(text, start, Calendar.ERA, formatData.eraNames, cal);
+                } else {
+                    return matchString(text, start, Calendar.ERA, formatData.eras, cal);
+                }
+            case 1: // 'y' - YEAR
+                // If there are 3 or more YEAR pattern characters, this indicates
+                // that the year value is to be treated literally, without any
+                // two-digit year adjustments (e.g., from "01" to 2001).  Otherwise
+                // we made adjustments to place the 2-digit year in the proper
+                // century, for parsed strings from "00" to "99".  Any other string
+                // is treated literally:  "2250", "-1", "1", "002".
+                /* 'yy' is the only special case, 'y' is interpreted as number. [Richard/GCL]*/
+                if (count == 2 && (pos.getIndex() - start) == 2
+                    && UCharacter.isDigit(text.charAt(start))
+                    && UCharacter.isDigit(text.charAt(start+1)))
+                    {
+                        // Assume for example that the defaultCenturyStart is 6/18/1903.
+                        // This means that two-digit years will be forced into the range
+                        // 6/18/1903 to 6/17/2003.  As a result, years 00, 01, and 02
+                        // correspond to 2000, 2001, and 2002.  Years 04, 05, etc. correspond
+                        // to 1904, 1905, etc.  If the year is 03, then it is 2003 if the
+                        // other fields specify a date before 6/18, or 1903 if they specify a
+                        // date afterwards.  As a result, 03 is an ambiguous year.  All other
+                        // two-digit years are unambiguous.
+                        int ambiguousTwoDigitYear = getDefaultCenturyStartYear() % 100;
+                        ambiguousYear[0] = value == ambiguousTwoDigitYear;
+                        value += (getDefaultCenturyStartYear()/100)*100 +
+                            (value < ambiguousTwoDigitYear ? 100 : 0);
+                    }
+                cal.set(Calendar.YEAR, value);
+                return pos.getIndex();
+            case 2: // 'M' - MONTH
+                if (count <= 2) // i.e., M or MM.
+                    {
+                        // Don't want to parse the month if it is a string
+                        // while pattern uses numeric style: M or MM.
+                        // [We computed 'value' above.]
+                        cal.set(Calendar.MONTH, value - 1);
+                        return pos.getIndex();
+                    }
+                else
+                    {
+                        // count >= 3 // i.e., MMM or MMMM
+                        // Want to be able to parse both short and long forms.
+                        // Try count == 4 first:
+                        int newStart = matchString(text, start, Calendar.MONTH,
+                                                   formatData.months, cal);
+                        if (newStart > 0) {
+                            return newStart;
+                        } else { // count == 4 failed, now try count == 3
+                            return matchString(text, start, Calendar.MONTH,
+                                               formatData.shortMonths, cal);
+                        }
+                    }
+            case 26: // 'L' - STAND_ALONE_MONTH
+                if (count <= 2) // i.e., M or MM.
+                    {
+                        // Don't want to parse the month if it is a string
+                        // while pattern uses numeric style: M or MM.
+                        // [We computed 'value' above.]
+                        cal.set(Calendar.MONTH, value - 1);
+                        return pos.getIndex();
+                    }
+                else
+                    {
+                        // count >= 3 // i.e., MMM or MMMM
+                        // Want to be able to parse both short and long forms.
+                        // Try count == 4 first:
+                        int newStart = matchString(text, start, Calendar.MONTH,
+                                                   formatData.standaloneMonths, cal);
+                        if (newStart > 0) {
+                            return newStart;
+                        } else { // count == 4 failed, now try count == 3
+                            return matchString(text, start, Calendar.MONTH,
+                                               formatData.standaloneShortMonths, cal);
+                        }
+                    }
+            case 4: // 'k' - HOUR_OF_DAY (1..24)
+                // [We computed 'value' above.]
+                if (value == cal.getMaximum(Calendar.HOUR_OF_DAY)+1) value = 0;
+                cal.set(Calendar.HOUR_OF_DAY, value);
+                return pos.getIndex();
+            case 8: // 'S' - FRACTIONAL_SECOND
+                // Fractional seconds left-justify
+                i = pos.getIndex() - start;
+                if (i < 3) {
+                    while (i < 3) {
+                        value *= 10;
+                        i++;
+                    }
+                } else {
+                    int a = 1;
+                    while (i > 3) {
+                        a *= 10;
+                        i--;
+                    }
+                    value = (value + (a>>1)) / a;
+                }
+                cal.set(Calendar.MILLISECOND, value);
+                return pos.getIndex();
+            case 9: { // 'E' - DAY_OF_WEEK
+                // Want to be able to parse both short and long forms.
+                // Try count == 4 (EEEE) first:
+                int newStart = matchString(text, start, Calendar.DAY_OF_WEEK,
+                                           formatData.weekdays, cal);
+                if (newStart > 0) {
+                    return newStart;
+                } else { // EEEE failed, now try EEE
+                    return matchString(text, start, Calendar.DAY_OF_WEEK,
+                                       formatData.shortWeekdays, cal);
+                }
+            }
+            case 25: { // 'c' - STAND_ALONE_DAY_OF_WEEK
+                // Want to be able to parse both short and long forms.
+                // Try count == 4 (cccc) first:
+                int newStart = matchString(text, start, Calendar.DAY_OF_WEEK,
+                                           formatData.standaloneWeekdays, cal);
+                if (newStart > 0) {
+                    return newStart;
+                } else { // cccc failed, now try ccc
+                    return matchString(text, start, Calendar.DAY_OF_WEEK,
+                                       formatData.standaloneShortWeekdays, cal);
+                }
+            }
+            case 14: // 'a' - AM_PM
+                return matchString(text, start, Calendar.AM_PM, formatData.ampms, cal);
+            case 15: // 'h' - HOUR (1..12)
+                // [We computed 'value' above.]
+                if (value == cal.getLeastMaximum(Calendar.HOUR)+1) value = 0;
+                cal.set(Calendar.HOUR, value);
+                return pos.getIndex();
+            case 17: // 'z' - ZONE_OFFSET
+            case 23: // 'Z' - TIMEZONE_RFC
+            case 24: // 'v' - TIMEZONE_GENERIC
+            case 29: // 'V' - TIMEZONE_SPECIAL
+                {
+                    TimeZone tz = null;
+                    int offset = 0;
+                    boolean parsed = false;
+
+                    // Step 1
+                    // Check if this is a long GMT offset string (either localized or default)
+                    Integer gmtoff = parseGMT(text, pos, currentNumberFormat);
+                    if (gmtoff != null) {
+                        offset = gmtoff.intValue();
+                        parsed = true;
+                    }
+
+                    if (!parsed) {
+                        // Step 2
+                        // Check if this is an RFC822 time zone offset.
+                        // ICU supports the standard RFC822 format [+|-]HHmm
+                        // and its extended form [+|-]HHmmSS.
+                        
+                        do {
+                            int sign = 0;
+                            char signChar = text.charAt(start);
+                            if (signChar == '+') {
+                                sign = 1;
+                            } else if (signChar == '-') {
+                                sign = -1;
+                            } else {
+                                // Not an RFC822 offset string
+                                break;
+                            }
+
+                            // Parse digits
+                            int orgPos = start + 1;
+                            pos.setIndex(orgPos);
+                            number = parseInt(text, 6, pos, false,currentNumberFormat);
+                            int numLen = pos.getIndex() - orgPos;
+                            if (numLen <= 0) {
+                                break;
+                            }
+
+                            // Followings are possible format (excluding sign char)
+                            // HHmmSS
+                            // HmmSS
+                            // HHmm
+                            // Hmm
+                            // HH
+                            // H
+                            int val = number.intValue();
+                            int hour = 0, min = 0, sec = 0;
+                            switch(numLen) {
+                            case 1: // H
+                            case 2: // HH
+                                hour = val;
+                                break;
+                            case 3: // Hmm
+                            case 4: // HHmm
+                                hour = val / 100;
+                                min = val % 100;
+                                break;
+                            case 5: // Hmmss
+                            case 6: // HHmmss
+                                hour = val / 10000;
+                                min = (val % 10000) / 100;
+                                sec = val % 100;
+                                break;
+                            }
+                            if (hour > 23 || min > 59 || sec > 59) {
+                                // Invalid value range
+                                break;
+                            }
+                            offset = (((hour * 60) + min) * 60 + sec) * 1000 * sign;
+                            parsed = true;
+                        } while (false);
+
+                        if (!parsed) {
+                            // Failed to parse.  Reset the position.
+                            pos.setIndex(start);
+                        }
+                    }
+
+                    if (parsed) {
+                        // offset was successfully parsed as either a long GMT string or RFC822 zone offset
+                        // string.  Create normalized zone ID for the offset.
+                        tz = ZoneMeta.getCustomTimeZone(offset);
+                        cal.setTimeZone(tz);
+                        return pos.getIndex();
+                    }
+
+                    // Step 3
+                    // At this point, check for named time zones by looking through
+                    // the locale data from the DateFormatZoneData strings.
+                    // Want to be able to parse both short and long forms.
+                    // optimize for calendar's current time zone
+                    ZoneStringInfo zsinfo = null;
+                    switch (patternCharIndex) {
+                    case 17: // 'z' - ZONE_OFFSET
+                        if (count < 4) {
+                            zsinfo = formatData.getZoneStringFormat().findSpecificShort(text, start);
+                        } else {
+                            zsinfo = formatData.getZoneStringFormat().findSpecificLong(text, start);
+                        }
+                        break;
+                    case 24: // 'v' - TIMEZONE_GENERIC
+                        if (count == 1) {
+                            zsinfo = formatData.getZoneStringFormat().findGenericShort(text, start);
+                        } else if (count == 4) {
+                            zsinfo = formatData.getZoneStringFormat().findGenericLong(text, start);
+                        }
+                        break;
+                    case 29: // 'V' - TIMEZONE_SPECIAL
+                        if (count == 1) {
+                            zsinfo = formatData.getZoneStringFormat().findSpecificShort(text, start);
+                        } else if (count == 4) {
+                            zsinfo = formatData.getZoneStringFormat().findGenericLocation(text, start);
+                        }
+                        break;
+                    }
+                    if (zsinfo != null) {
+                        if (zsinfo.isStandard()) {
+                            tztype = TZTYPE_STD;
+                        } else if (zsinfo.isDaylight()) {
+                            tztype = TZTYPE_DST;
+                        }
+                        tz = TimeZone.getTimeZone(zsinfo.getID());
+                        cal.setTimeZone(tz);
+                        return start + zsinfo.getString().length();
+                    }
+                    // Step 4
+                    // Final attempt - is this standalone GMT/UT/UTC?
+                    int gmtLen = 0;
+                    if (text.regionMatches(true, start, STR_GMT, 0, STR_GMT_LEN)) {
+                        gmtLen = STR_GMT_LEN;
+                    } else if (text.regionMatches(true, start, STR_UTC, 0, STR_UTC_LEN)) {
+                        gmtLen = STR_UTC_LEN;
+                    } else if (text.regionMatches(true, start, STR_UT, 0, STR_UT_LEN)) {
+                        gmtLen = STR_UT_LEN;
+                    }
+                    if (gmtLen > 0) {
+                        tz = TimeZone.getTimeZone("Etc/GMT");
+                        cal.setTimeZone(tz);
+                        return start + gmtLen;
+                    }
+
+                    // complete failure
+                    return -start;
+                }
+
+            case 27: // 'Q' - QUARTER
+                if (count <= 2) // i.e., Q or QQ.
+                {
+                    // Don't want to parse the quarter if it is a string
+                    // while pattern uses numeric style: Q or QQ.
+                    // [We computed 'value' above.]
+                    cal.set(Calendar.MONTH, (value - 1) * 3);
+                    return pos.getIndex();
+                }
+            else
+                {
+                    // count >= 3 // i.e., QQQ or QQQQ
+                    // Want to be able to parse both short and long forms.
+                    // Try count == 4 first:
+                    int newStart = matchQuarterString(text, start, Calendar.MONTH,
+                                               formatData.quarters, cal);
+                    if (newStart > 0) {
+                        return newStart;
+                    } else { // count == 4 failed, now try count == 3
+                        return matchQuarterString(text, start, Calendar.MONTH,
+                                           formatData.shortQuarters, cal);
+                    }
+                }
+                
+            case 28: // 'q' - STANDALONE QUARTER
+                if (count <= 2) // i.e., q or qq.
+                {
+                    // Don't want to parse the quarter if it is a string
+                    // while pattern uses numeric style: q or qq.
+                    // [We computed 'value' above.]
+                    cal.set(Calendar.MONTH, (value - 1) * 3);
+                    return pos.getIndex();
+                }
+            else
+                {
+                    // count >= 3 // i.e., qqq or qqqq
+                    // Want to be able to parse both short and long forms.
+                    // Try count == 4 first:
+                    int newStart = matchQuarterString(text, start, Calendar.MONTH,
+                                               formatData.standaloneQuarters, cal);
+                    if (newStart > 0) {
+                        return newStart;
+                    } else { // count == 4 failed, now try count == 3
+                        return matchQuarterString(text, start, Calendar.MONTH,
+                                           formatData.standaloneShortQuarters, cal);
+                    }
+                }
+
+            default:
+                // case 3: // 'd' - DATE
+                // case 5: // 'H' - HOUR_OF_DAY (0..23)
+                // case 6: // 'm' - MINUTE
+                // case 7: // 's' - SECOND
+                // case 10: // 'D' - DAY_OF_YEAR
+                // case 11: // 'F' - DAY_OF_WEEK_IN_MONTH
+                // case 12: // 'w' - WEEK_OF_YEAR
+                // case 13: // 'W' - WEEK_OF_MONTH
+                // case 16: // 'K' - HOUR (0..11)
+                // case 18: // 'Y' - YEAR_WOY
+                // case 19: // 'e' - DOW_LOCAL
+                // case 20: // 'u' - EXTENDED_YEAR
+                // case 21: // 'g' - JULIAN_DAY
+                // case 22: // 'A' - MILLISECONDS_IN_DAY
+
+                // Handle "generic" fields
+                if (obeyCount)
+                    {
+                        if ((start+count) > text.length()) return -start;
+                        number = parseInt(text, count, pos, allowNegative,currentNumberFormat);
+                    }
+                else number = parseInt(text, pos, allowNegative,currentNumberFormat);
+                if (number != null) {
+                    cal.set(field, number.intValue());
+                    return pos.getIndex();
+                }
+                return -start;
+            }
+    }
+
+    /**
+     * Parse an integer using numberFormat.  This method is semantically
+     * const, but actually may modify fNumberFormat.
+     */
+    private Number parseInt(String text,
+                            ParsePosition pos,
+                            boolean allowNegative,
+                            NumberFormat fmt) {
+        return parseInt(text, -1, pos, allowNegative, fmt);
+    }
+    
+    /**
+     * Parse an integer using numberFormat up to maxDigits.
+     */
+    private Number parseInt(String text,
+                            int maxDigits,
+                            ParsePosition pos,
+                            boolean allowNegative,
+                            NumberFormat fmt) {
+        Number number;
+        int oldPos = pos.getIndex();
+        if (allowNegative) {
+            number = fmt.parse(text, pos);
+        } else {
+            // Invalidate negative numbers
+            if (fmt instanceof DecimalFormat) {
+                String oldPrefix = ((DecimalFormat)fmt).getNegativePrefix();
+                ((DecimalFormat)fmt).setNegativePrefix(SUPPRESS_NEGATIVE_PREFIX);
+                number = fmt.parse(text, pos);
+                ((DecimalFormat)fmt).setNegativePrefix(oldPrefix);
+            } else {
+                boolean dateNumberFormat = (fmt instanceof DateNumberFormat);
+                if (dateNumberFormat) {
+                    ((DateNumberFormat)fmt).setParsePositiveOnly(true);
+                }
+                number = fmt.parse(text, pos);                
+                if (dateNumberFormat) {
+                    ((DateNumberFormat)fmt).setParsePositiveOnly(false);
+                }
+            }
+        }
+        if (maxDigits > 0) {
+            // adjust the result to fit into
+            // the maxDigits and move the position back
+            int nDigits = pos.getIndex() - oldPos;
+            if (nDigits > maxDigits) {
+                double val = number.doubleValue();
+                nDigits -= maxDigits;
+                while (nDigits > 0) {
+                    val /= 10;
+                    nDigits--;
+                }
+                pos.setIndex(oldPos + maxDigits);
+                number = new Integer((int)val);
+            }
+        }
+        return number;
+    }
+
+    
+    /**
+     * Translate a pattern, mapping each character in the from string to the
+     * corresponding character in the to string.
+     */
+    private String translatePattern(String pat, String from, String to) {
+        StringBuffer result = new StringBuffer();
+        boolean inQuote = false;
+        for (int i = 0; i < pat.length(); ++i) {
+            char c = pat.charAt(i);
+            if (inQuote) {
+                if (c == '\'')
+                    inQuote = false;
+            }
+            else {
+                if (c == '\'')
+                    inQuote = true;
+                else if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) {
+                    int ci = from.indexOf(c);
+                    if (ci != -1) {
+                        c = to.charAt(ci);
+                    }
+                    // do not worry on translatepattern if the character is not listed
+                    // we do the validity check elsewhere
+                }
+            }
+            result.append(c);
+        }
+        if (inQuote)
+            throw new IllegalArgumentException("Unfinished quote in pattern");
+        return result.toString();
+    }
+
+    /**
+     * Return a pattern string describing this date format.
+     * @stable ICU 2.0
+     */
+    public String toPattern() {
+        return pattern;
+    }
+
+    /**
+     * Return a localized pattern string describing this date format.
+     * @stable ICU 2.0
+     */
+    public String toLocalizedPattern() {
+        return translatePattern(pattern,
+                                DateFormatSymbols.patternChars,
+                                formatData.localPatternChars);
+    }
+
+    /**
+     * Apply the given unlocalized pattern string to this date format.
+     * @stable ICU 2.0
+     */
+    public void applyPattern(String pat)
+    {
+        this.pattern = pat;
+        setLocale(null, null);
+        // reset parsed pattern items
+        patternItems = null;
+    }
+
+    /**
+     * Apply the given localized pattern string to this date format.
+     * @stable ICU 2.0
+     */
+    public void applyLocalizedPattern(String pat) {
+        this.pattern = translatePattern(pat,
+                                        formatData.localPatternChars,
+                                        DateFormatSymbols.patternChars);
+        setLocale(null, null);
+    }
+
+    /**
+     * Gets the date/time formatting data.
+     * @return a copy of the date-time formatting data associated
+     * with this date-time formatter.
+     * @stable ICU 2.0
+     */
+    public DateFormatSymbols getDateFormatSymbols()
+    {
+        return (DateFormatSymbols)formatData.clone();
+    }
+
+    /**
+     * Allows you to set the date/time formatting data.
+     * @param newFormatSymbols the new symbols
+     * @stable ICU 2.0
+     */
+    public void setDateFormatSymbols(DateFormatSymbols newFormatSymbols)
+    {
+        this.formatData = (DateFormatSymbols)newFormatSymbols.clone();
+        gmtfmtCache = null;
+    }
+
+    /**
+     * Method for subclasses to access the DateFormatSymbols.
+     * @stable ICU 2.0
+     */
+    protected DateFormatSymbols getSymbols() {
+        return formatData;
+    }
+
+    /**
+     * Overrides Cloneable
+     * @stable ICU 2.0
+     */
+    public Object clone() {
+        SimpleDateFormat other = (SimpleDateFormat) super.clone();
+        other.formatData = (DateFormatSymbols) formatData.clone();
+        return other;
+    }
+
+    /**
+     * Override hashCode.
+     * Generates the hash code for the SimpleDateFormat object
+     * @stable ICU 2.0
+     */
+    public int hashCode()
+    {
+        return pattern.hashCode();
+        // just enough fields for a reasonable distribution
+    }
+
+    /**
+     * Override equals.
+     * @stable ICU 2.0
+     */
+    public boolean equals(Object obj)
+    {
+        if (!super.equals(obj)) return false; // super does class check
+        SimpleDateFormat that = (SimpleDateFormat) obj;
+        return (pattern.equals(that.pattern)
+                && formatData.equals(that.formatData));
+    }
+
+    /**
+     * Override writeObject.
+     */
+    private void writeObject(ObjectOutputStream stream) throws IOException{
+        if (defaultCenturyStart == null) {
+            // if defaultCenturyStart is not yet initialized,
+            // calculate and set value before serialization.
+            initializeDefaultCenturyStart(defaultCenturyBase);
+        }
+        stream.defaultWriteObject();
+    }
+    
+    /**
+     * Override readObject.
+     */
+    private void readObject(ObjectInputStream stream)
+        throws IOException, ClassNotFoundException {
+        stream.defaultReadObject();
+        ///CLOVER:OFF
+        // don't have old serial data to test with
+        if (serialVersionOnStream < 1) {
+            // didn't have defaultCenturyStart field
+            defaultCenturyBase = System.currentTimeMillis();
+        }
+        ///CLOVER:ON
+        else {
+            // fill in dependent transient field
+            parseAmbiguousDatesAsAfter(defaultCenturyStart);
+        }
+        serialVersionOnStream = currentSerialVersion;
+        locale = getLocale(ULocale.VALID_LOCALE);
+
+        initLocalZeroPaddingNumberFormat();
+    }
+
+//#if defined(FOUNDATION10) || defined(J2SE13)
+//#else
+    /**
+     * Format the object to an attributed string, and return the corresponding iterator
+     * Overrides superclass method.
+     * 
+     * @param obj The object to format
+     * @return <code>AttributedCharacterIterator</code> describing the formatted value.
+     * 
+     * @stable ICU 3.8
+     */
+    public AttributedCharacterIterator formatToCharacterIterator(Object obj) {
+        Calendar cal = calendar;
+        if (obj instanceof Calendar) {
+            cal = (Calendar)obj;
+        } else if (obj instanceof Date) {
+            calendar.setTime((Date)obj);
+        } else if (obj instanceof Number) {
+            calendar.setTimeInMillis(((Number)obj).longValue());
+        } else { 
+            throw new IllegalArgumentException("Cannot format given Object as a Date");
+        }
+        StringBuffer toAppendTo = new StringBuffer();
+        FieldPosition pos = new FieldPosition(0);
+        List attributes = new LinkedList();
+        format(cal, toAppendTo, pos, attributes);
+
+        AttributedString as = new AttributedString(toAppendTo.toString());
+        
+        // add DateFormat field attributes to the AttributedString
+        for (int i = 0; i < attributes.size(); i++) {
+            FieldPosition fp = (FieldPosition) attributes.get(i);
+            Format.Field attribute = fp.getFieldAttribute();
+            as.addAttribute(attribute, attribute, fp.getBeginIndex(), fp.getEndIndex());
+        }
+        // return the CharacterIterator from AttributedString
+        return as.getIterator();
+    }
+//#endif
+
+
+    /**
+     * Get the locale of this simple date formatter.
+     * It is package accessible. also used in DateIntervalFormat.
+     *
+     * @return   locale in this simple date formatter
+     */
+    ULocale getLocale() 
+    {
+        return locale;
+    }
+
+
+    
+    /**
+     * Check whether the 'field' is smaller than all the fields covered in
+     * pattern, return true if it is.
+     * The sequence of calendar field,
+     * from large to small is: ERA, YEAR, MONTH, DATE, AM_PM, HOUR, MINUTE,...
+     * @param field    the calendar field need to check against
+     * @return         true if the 'field' is smaller than all the fields 
+     *                 covered in pattern. false otherwise.
+     */
+
+    boolean isFieldUnitIgnored(int field) {
+        return isFieldUnitIgnored(pattern, field);
+    }
+
+
+    /*
+     * Check whether the 'field' is smaller than all the fields covered in
+     * pattern, return true if it is.
+     * The sequence of calendar field,
+     * from large to small is: ERA, YEAR, MONTH, DATE, AM_PM, HOUR, MINUTE,...
+     * @param pattern  the pattern to check against
+     * @param field    the calendar field need to check against
+     * @return         true if the 'field' is smaller than all the fields 
+     *                 covered in pattern. false otherwise.
+     * @internal ICU 4.0
+     */
+    static boolean isFieldUnitIgnored(String pattern, int field) {
+        int fieldLevel = CALENDAR_FIELD_TO_LEVEL[field];
+        int level;
+        char ch;
+        boolean inQuote = false;
+        char prevCh = 0;
+        int count = 0;
+    
+        for (int i = 0; i < pattern.length(); ++i) {
+            ch = pattern.charAt(i);
+            if (ch != prevCh && count > 0) {
+                level = PATTERN_CHAR_TO_LEVEL[prevCh - PATTERN_CHAR_BASE];
+                if ( fieldLevel <= level ) {
+                    return false;
+                }
+                count = 0;
+            }
+            if (ch == '\'') {
+                if ((i+1) < pattern.length() && pattern.charAt(i+1) == '\'') {
+                    ++i;
+                } else {
+                    inQuote = ! inQuote;
+                }
+            } 
+            else if ( ! inQuote && ((ch >= 0x0061 /*'a'*/ && ch <= 0x007A /*'z'*/) 
+                        || (ch >= 0x0041 /*'A'*/ && ch <= 0x005A /*'Z'*/))) {
+                prevCh = ch;
+                ++count;
+            }
+        }
+        if ( count > 0 ) {
+            // last item
+            level = PATTERN_CHAR_TO_LEVEL[prevCh - PATTERN_CHAR_BASE];
+                if ( fieldLevel <= level ) {
+                    return false;
+                }
+        }
+        return true;
+    }
+
+
+    /**
+     * Format date interval by algorithm. 
+     * It is supposed to be used only by CLDR survey tool.
+     *
+     * @param fromCalendar      calendar set to the from date in date interval
+     *                          to be formatted into date interval stirng
+     * @param toCalendar        calendar set to the to date in date interval
+     *                          to be formatted into date interval stirng
+     * @param appendTo          Output parameter to receive result.
+     *                          Result is appended to existing contents.
+     * @param pos               On input: an alignment field, if desired.
+     *                          On output: the offsets of the alignment field.
+     * @exception IllegalArgumentException when there is non-recognized
+     *                                     pattern letter
+     * @return                  Reference to 'appendTo' parameter.
+     * @internal ICU 4.0
+     */
+    public final StringBuffer intervalFormatByAlgorithm(Calendar fromCalendar,
+                                                        Calendar toCalendar,
+                                                        StringBuffer appendTo,
+                                                        FieldPosition pos)
+                              throws IllegalArgumentException
+    {
+        // not support different calendar types and time zones
+        if ( !fromCalendar.isEquivalentTo(toCalendar) ) {
+            throw new IllegalArgumentException("can not format on two different calendars");
+        }
+     
+        Object[] items = getPatternItems();
+        int diffBegin = -1;
+        int diffEnd = -1;
+
+        /* look for different formatting string range */
+        // look for start of difference
+        try {
+            for (int i = 0; i < items.length; i++) {
+                if ( diffCalFieldValue(fromCalendar, toCalendar, items, i) ) {
+                    diffBegin = i;
+                    break;
+                }
+            } 
+        
+            if ( diffBegin == -1 ) { 
+                // no difference, single date format
+                return format(fromCalendar, appendTo, pos);
+            }
+    
+            // look for end of difference
+            for (int i = items.length-1; i >= diffBegin; i--) {
+                if ( diffCalFieldValue(fromCalendar, toCalendar, items, i) ) {
+                    diffEnd = i;
+                    break;
+                }
+            }
+        } catch ( IllegalArgumentException e ) {
+            throw new IllegalArgumentException(e.toString());
+        }
+
+        // full range is different
+        if ( diffBegin == 0 && diffEnd == items.length-1 ) {
+            format(fromCalendar, appendTo, pos);
+            appendTo.append(" \u2013 "); // default separator
+            format(toCalendar, appendTo, pos);
+            return appendTo;
+        }
+
+
+        /* search for largest calendar field within the different range */
+        int highestLevel = 1000;
+        for (int i = diffBegin; i <= diffEnd; i++) {
+            if ( items[i] instanceof String) {
+                continue;
+            } 
+            PatternItem item = (PatternItem)items[i];
+            char ch = item.type; 
+            int patternCharIndex = -1;
+            if ('A' <= ch && ch <= 'z') {
+                patternCharIndex = PATTERN_CHAR_TO_LEVEL[(int)ch - PATTERN_CHAR_BASE];
+            }
+    
+            if (patternCharIndex == -1) {
+                throw new IllegalArgumentException("Illegal pattern character " +
+                                                   "'" + ch + "' in \"" +
+                                                   new String(pattern) + '"');
+            }
+    
+            if ( patternCharIndex < highestLevel ) {
+                highestLevel = patternCharIndex;
+            }
+        }
+
+        /* re-calculate diff range, including those calendar field which
+           is in lower level than the largest calendar field covered
+           in diff range calculated. */
+        try {
+            for (int i = 0; i < diffBegin; i++) {
+                if ( lowerLevel(items, i, highestLevel) ) {
+                    diffBegin = i;
+                    break;
+                }
+            }
+    
+    
+            for (int i = items.length-1; i > diffEnd; i--) {
+                if ( lowerLevel(items, i, highestLevel) ) {
+                    diffEnd = i;
+                    break;
+                }
+            }
+        } catch ( IllegalArgumentException e ) {
+            throw new IllegalArgumentException(e.toString());
+        }
+
+
+        // full range is different
+        if ( diffBegin == 0 && diffEnd == items.length-1 ) {
+            format(fromCalendar, appendTo, pos);
+            appendTo.append(" \u2013 "); // default separator
+            format(toCalendar, appendTo, pos);
+            return appendTo;
+        }
+
+
+        // formatting
+        // Initialize
+        pos.setBeginIndex(0);
+        pos.setEndIndex(0);
+
+        // formatting date 1
+        for (int i = 0; i <= diffEnd; i++) {
+            if (items[i] instanceof String) {
+                appendTo.append((String)items[i]);
+            } else {
+                PatternItem item = (PatternItem)items[i];
+                if (useFastFormat) {
+                    subFormat(appendTo, item.type, item.length, appendTo.length(), pos, fromCalendar);
+                } else {
+                    appendTo.append(subFormat(item.type, item.length, appendTo.length(), pos, formatData, fromCalendar));
+                }
+            }
+        }
+
+        appendTo.append(" \u2013 "); // default separator
+
+        // formatting date 2
+        for (int i = diffBegin; i < items.length; i++) {
+            if (items[i] instanceof String) {
+                appendTo.append((String)items[i]);
+            } else {
+                PatternItem item = (PatternItem)items[i];
+                if (useFastFormat) {
+                    subFormat(appendTo, item.type, item.length, appendTo.length(), pos, toCalendar);
+                } else {
+                    appendTo.append(subFormat(item.type, item.length, appendTo.length(), pos, formatData, toCalendar));
+                }
+            }
+        }
+        return appendTo;
+    }
+
+
+    /**
+     * check whether the i-th item in 2 calendar is in different value.
+     *
+     * It is supposed to be used only by CLDR survey tool.
+     * It is used by intervalFormatByAlgorithm().
+     *
+     * @param fromCalendar   one calendar
+     * @param toCalendar     the other calendar
+     * @param items          pattern items
+     * @param i              the i-th item in pattern items
+     * @exception IllegalArgumentException when there is non-recognized
+     *                                     pattern letter
+     * @return               true is i-th item in 2 calendar is in different 
+     *                       value, false otherwise.
+     */
+    private boolean diffCalFieldValue(Calendar fromCalendar,
+                                      Calendar toCalendar,
+                                      Object[] items,
+                                      int i) throws IllegalArgumentException {
+        if ( items[i] instanceof String) {
+            return false;
+        } 
+        PatternItem item = (PatternItem)items[i];
+        char ch = item.type; 
+        int patternCharIndex = -1;
+        if ('A' <= ch && ch <= 'z') {
+            patternCharIndex = PATTERN_CHAR_TO_INDEX[(int)ch - PATTERN_CHAR_BASE];
+        }
+   
+        if (patternCharIndex == -1) {
+            throw new IllegalArgumentException("Illegal pattern character " +
+                                               "'" + ch + "' in \"" +
+                                               new String(pattern) + '"');
+        }
+  
+        final int field = PATTERN_INDEX_TO_CALENDAR_FIELD[patternCharIndex];
+        int value = fromCalendar.get(field);
+        int value_2 = toCalendar.get(field);
+        if ( value != value_2 ) {
+            return true;
+        }
+        return false;
+    }
+
+
+    /**
+     * check whether the i-th item's level is lower than the input 'level'
+     *
+     * It is supposed to be used only by CLDR survey tool.
+     * It is used by intervalFormatByAlgorithm().
+     *
+     * @param items  the pattern items
+     * @param i      the i-th item in pattern items
+     * @param level  the level with which the i-th pattern item compared to
+     * @exception IllegalArgumentException when there is non-recognized
+     *                                     pattern letter
+     * @return       true if i-th pattern item is lower than 'level',
+     *               false otherwise
+     */
+    private boolean lowerLevel(Object[] items, int i, int level) 
+                    throws IllegalArgumentException {
+        if ( items[i] instanceof String) {
+            return false;
+        } 
+        PatternItem item = (PatternItem)items[i];
+        char ch = item.type; 
+        int patternCharIndex = -1;
+        if ('A' <= ch && ch <= 'z') {
+            patternCharIndex = PATTERN_CHAR_TO_LEVEL[(int)ch - PATTERN_CHAR_BASE];
+        }
+    
+        if (patternCharIndex == -1) {
+            throw new IllegalArgumentException("Illegal pattern character " +
+                                               "'" + ch + "' in \"" +
+                                               new String(pattern) + '"');
+        }
+    
+        if ( patternCharIndex >= level ) {
+            return true;
+        }    
+        return false;
+    }
+
+    protected NumberFormat getNumberFormat(char ch) {
+
+       Character ovrField;
+       ovrField = new Character(ch);
+       if (overrideMap != null && overrideMap.containsKey(ovrField)) {
+           String nsName = overrideMap.get(ovrField).toString();
+           NumberFormat nf = (NumberFormat)numberFormatters.get(nsName);
+           return nf;
+       } else {
+           return numberFormat;
+       }
+    }
+
+    private void initNumberFormatters(ULocale loc) {
+
+       numberFormatters = new HashMap();
+       overrideMap = new HashMap();
+       processOverrideString(loc,override); 
+
+    }
+
+    private void processOverrideString(ULocale loc, String str) {
+
+        if ( str == null || str.length() == 0 )
+            return;
+
+        int start = 0;
+        int end;
+        String nsName;
+        Character ovrField;
+        boolean moreToProcess = true;
+        boolean fullOverride;
+
+        while (moreToProcess) {
+            int delimiterPosition = str.indexOf(";",start);
+            if (delimiterPosition == -1) {
+                moreToProcess = false;
+                end = str.length();
+            } else {
+                end = delimiterPosition;
+            }
+
+            String currentString = str.substring(start,end);
+            int equalSignPosition = currentString.indexOf("=");
+            if (equalSignPosition == -1) { // Simple override string such as "hebrew"
+               nsName = currentString;
+               fullOverride = true;
+            } else { // Field specific override string such as "y=hebrew"
+               nsName = currentString.substring(equalSignPosition+1);
+               ovrField = new Character(currentString.charAt(0));
+               overrideMap.put(ovrField,nsName);
+               fullOverride = false;
+            }
+
+            ULocale ovrLoc = new ULocale(loc.getBaseName()+"@numbers="+nsName);
+            NumberFormat nf = NumberFormat.createInstance(ovrLoc,NumberFormat.NUMBERSTYLE);
+            
+            if (fullOverride) {
+                setNumberFormat(nf);
+            } else {
+                // Since one or more of the override number formatters might be complex,
+                // we can't rely on the fast numfmt where we have a partial field override.
+                useLocalZeroPaddingNumberFormat = false;
+            }
+
+            if (!numberFormatters.containsKey(nsName)) {
+                  numberFormatters.put(nsName,nf);
+            }
+
+            start = delimiterPosition + 1;
+
+        }    
+    }
+}