home *** CD-ROM | disk | FTP | other *** search
/ Java 1.2 How-To / JavaHowTo.iso / 3rdParty / jbuilder / unsupported / JDK1.2beta3 / SOURCE / SRC.ZIP / java / text / SimpleDateFormat.java < prev    next >
Encoding:
Java Source  |  1998-03-20  |  53.0 KB  |  1,308 lines

  1. /*
  2.  * @(#)SimpleDateFormat.java    1.33 98/03/18
  3.  *
  4.  * (C) Copyright Taligent, Inc. 1996 - All Rights Reserved
  5.  * (C) Copyright IBM Corp. 1996 - All Rights Reserved
  6.  *
  7.  * Portions copyright (c) 1996-1998 Sun Microsystems, Inc. All Rights Reserved.
  8.  *
  9.  *   The original version of this source code and documentation is copyrighted
  10.  * and owned by Taligent, Inc., a wholly-owned subsidiary of IBM. These
  11.  * materials are provided under terms of a License Agreement between Taligent
  12.  * and Sun. This technology is protected by multiple US and International
  13.  * patents. This notice and attribution to Taligent may not be removed.
  14.  *   Taligent is a registered trademark of Taligent, Inc.
  15.  *
  16.  * Permission to use, copy, modify, and distribute this software
  17.  * and its documentation for NON-COMMERCIAL purposes and without
  18.  * fee is hereby granted provided that this copyright notice
  19.  * appears in all copies. Please refer to the file "copyright.html"
  20.  * for further important copyright and licensing information.
  21.  *
  22.  * SUN MAKES NO REPRESENTATIONS OR WARRANTIES ABOUT THE SUITABILITY OF
  23.  * THE SOFTWARE, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
  24.  * TO THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
  25.  * PARTICULAR PURPOSE, OR NON-INFRINGEMENT. SUN SHALL NOT BE LIABLE FOR
  26.  * ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR
  27.  * DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES.
  28.  *
  29.  */
  30.  
  31. package java.text;
  32. import java.util.TimeZone;
  33. import java.util.Calendar;
  34. import java.util.Date;
  35. import java.util.Locale;
  36. import java.util.ResourceBundle;
  37. import java.util.SimpleTimeZone;
  38. import java.util.GregorianCalendar;
  39. import java.io.ObjectInputStream;
  40. import java.io.IOException;
  41. import java.lang.ClassNotFoundException;
  42. import java.util.Hashtable;
  43.  
  44. /**
  45.  * <code>SimpleDateFormat</code> is a concrete class for formatting and
  46.  * parsing dates in a locale-sensitive manner. It allows for formatting
  47.  * (date -> text), parsing (text -> date), and normalization.
  48.  *
  49.  * <p>
  50.  * <code>SimpleDateFormat</code> allows you to start by choosing
  51.  * any user-defined patterns for date-time formatting. However, you
  52.  * are encouraged to create a date-time formatter with either
  53.  * <code>getTimeInstance</code>, <code>getDateInstance</code>, or
  54.  * <code>getDateTimeInstance</code> in <code>DateFormat</code>. Each
  55.  * of these class methods can return a date/time formatter initialized
  56.  * with a default format pattern. You may modify the format pattern
  57.  * using the <code>applyPattern</code> methods as desired.
  58.  * For more information on using these methods, see
  59.  * <a href="java.text.DateFormat.html"><code>DateFormat</code></a>.
  60.  *
  61.  * <p>
  62.  * <strong>Time Format Syntax:</strong>
  63.  * <p>
  64.  * To specify the time format use a <em>time pattern</em> string.
  65.  * In this pattern, all ASCII letters are reserved as pattern letters,
  66.  * which are defined as the following:
  67.  * <blockquote>
  68.  * <pre>
  69.  * Symbol   Meaning                 Presentation        Example
  70.  * ------   -------                 ------------        -------
  71.  * G        era designator          (Text)              AD
  72.  * y        year                    (Number)            1996
  73.  * M        month in year           (Text & Number)     July & 07
  74.  * d        day in month            (Number)            10
  75.  * h        hour in am/pm (1~12)    (Number)            12
  76.  * H        hour in day (0~23)      (Number)            0
  77.  * m        minute in hour          (Number)            30
  78.  * s        second in minute        (Number)            55
  79.  * S        millisecond             (Number)            978
  80.  * E        day in week             (Text)              Tuesday
  81.  * D        day in year             (Number)            189
  82.  * F        day of week in month    (Number)            2 (2nd Wed in July)
  83.  * w        week in year            (Number)            27
  84.  * W        week in month           (Number)            2
  85.  * a        am/pm marker            (Text)              PM
  86.  * k        hour in day (1~24)      (Number)            24
  87.  * K        hour in am/pm (0~11)    (Number)            0
  88.  * z        time zone               (Text)              Pacific Standard Time
  89.  * '        escape for text         (Delimiter)
  90.  * ''       single quote            (Literal)           '
  91.  * </pre>
  92.  * </blockquote>
  93.  * The count of pattern letters determine the format.
  94.  * <p>
  95.  * <strong>(Text)</strong>: 4 or more pattern letters--use full form,
  96.  * < 4--use short or abbreviated form if one exists.
  97.  * <p>
  98.  * <strong>(Number)</strong>: the minimum number of digits. Shorter
  99.  * numbers are zero-padded to this amount. Year is handled specially;
  100.  * that is, if the count of 'y' is 2, the Year will be truncated to 2 digits.
  101.  * <p>
  102.  * <strong>(Text & Number)</strong>: 3 or over, use text, otherwise use number.
  103.  * <p>
  104.  * Any characters in the pattern that are not in the ranges of ['a'..'z']
  105.  * and ['A'..'Z'] will be treated as quoted text. For instance, characters
  106.  * like ':', '.', ' ', '#' and '@' will appear in the resulting time text
  107.  * even they are not embraced within single quotes.
  108.  * <p>
  109.  * A pattern containing any invalid pattern letter will result in a thrown
  110.  * exception during formatting or parsing.
  111.  *
  112.  * <p>
  113.  * <strong>Examples Using the US Locale:</strong>
  114.  * <blockquote>
  115.  * <pre>
  116.  * Format Pattern                         Result
  117.  * --------------                         -------
  118.  * "yyyy.MM.dd G 'at' hh:mm:ss z"    ->>  1996.07.10 AD at 15:08:56 PDT
  119.  * "EEE, MMM d, ''yy"                ->>  Wed, July 10, '96
  120.  * "h:mm a"                          ->>  12:08 PM
  121.  * "hh 'o''clock' a, zzzz"           ->>  12 o'clock PM, Pacific Daylight Time
  122.  * "K:mm a, z"                       ->>  0:00 PM, PST
  123.  * "yyyyy.MMMMM.dd GGG hh:mm aaa"    ->>  1996.July.10 AD 12:08 PM
  124.  * </pre>
  125.  * </blockquote>
  126.  * <strong>Code Sample:</strong>
  127.  * <blockquote>
  128.  * <pre>
  129.  * SimpleTimeZone pdt = new SimpleTimeZone(-8 * 60 * 60 * 1000, "PST");
  130.  * pdt.setStartRule(DateFields.APRIL, 1, DateFields.SUNDAY, 2*60*60*1000);
  131.  * pdt.setEndRule(DateFields.OCTOBER, -1, DateFields.SUNDAY, 2*60*60*1000);
  132.  * <br>
  133.  * // Format the current time.
  134.  * SimpleDateFormat formatter
  135.  *     = new SimpleDateFormat ("yyyy.MM.dd G 'at' hh:mm:ss a zzz");
  136.  * Date currentTime_1 = new Date();
  137.  * String dateString = formatter.format(currentTime_1);
  138.  * <br>
  139.  * // Parse the previous string back into a Date.
  140.  * ParsePosition pos = new ParsePosition(0);
  141.  * Date currentTime_2 = formatter.parse(dateString, pos);
  142.  * </pre>
  143.  * </blockquote>
  144.  * In the example, the time value <code>currentTime_2</code> obtained from
  145.  * parsing will be equal to <code>currentTime_1</code>. However, they may not be
  146.  * equal if the am/pm marker 'a' is left out from the format pattern while
  147.  * the "hour in am/pm" pattern symbol is used. This information loss can
  148.  * happen when formatting the time in PM.
  149.  *
  150.  * <p>
  151.  * When parsing a date string using the abbreviated year pattern,
  152.  * SimpleDateFormat must interpret the abbreviated year
  153.  * relative to some century.  It does this by adjusting dates to be
  154.  * within 80 years before and 20 years after the time the SimpleDateFormat
  155.  * instance is created. For example, using a pattern of MM/dd/yy and a
  156.  * SimpleDateFormat instance created on Jan 1, 1997,  the string
  157.  * "01/11/12" would be interpreted as Jan 11, 2012 while the string "05/04/64"
  158.  * would be interpreted as May 4, 1964.
  159.  *
  160.  * <p>
  161.  * For time zones that have no names, use strings GMT+hours:minutes or
  162.  * GMT-hours:minutes.
  163.  *
  164.  * <p>
  165.  * The calendar defines what is the first day of the week, the first week
  166.  * of the year, whether hours are zero based or not (0 vs 12 or 24), and the
  167.  * time zone. There is one common decimal format to handle all the numbers;
  168.  * the digit count is handled programmatically according to the pattern.
  169.  *
  170.  * @see          java.util.Calendar
  171.  * @see          java.util.GregorianCalendar
  172.  * @see          java.util.TimeZone
  173.  * @see          DateFormat
  174.  * @see          DateFormatSymbols
  175.  * @see          DecimalFormat
  176.  * @version      1.29 01/15/98
  177.  * @author       Mark Davis, Chen-Lieh Huang, Alan Liu
  178.  */
  179. public class SimpleDateFormat extends DateFormat {
  180.  
  181.     // the official serial version ID which says cryptically
  182.     // which version we're compatible with
  183.     static final long serialVersionUID = 4774881970558875024L;
  184.  
  185.     // the internal serial version which says which version was written
  186.     // - 0 (default) for version up to JDK 1.1.3
  187.     // - 1 for version from JDK 1.1.4, which includes a new field
  188.     static final int currentSerialVersion = 1;
  189.     private int serialVersionOnStream = currentSerialVersion;
  190.  
  191.     private String pattern;
  192.     private DateFormatSymbols formatData;
  193.  
  194.     // if dates have ambiguous years, we map them into the century starting
  195.     // at defaultCenturyStart, which may be any date.
  196.     private Date defaultCenturyStart; // field new in JDK 1.1.4
  197.     transient private int defaultCenturyStartYear;
  198.  
  199.     private static final int millisPerHour = 60 * 60 * 1000;
  200.     private static final int millisPerMinute = 60 * 1000;
  201.  
  202.     // For time zones that have no names, use strings GMT+minutes and
  203.     // GMT-minutes. For instance, in France the time zone is GMT+60.
  204.     private static final String GMT_PLUS = "GMT+";
  205.     private static final String GMT_MINUS = "GMT-";
  206.     private static final String GMT = "GMT";
  207.  
  208.     /**
  209.      * Cache to hold the DateTimePatterns of a Locale.
  210.      */
  211.     private static Hashtable cachedLocaleData = new Hashtable(3);
  212.  
  213.     /**
  214.      * Construct a SimpleDateFormat using the default pattern for the default
  215.      * locale.  <b>Note:</b> Not all locales support SimpleDateFormat; for full
  216.      * generality, use the factory methods in the DateFormat class.
  217.      *
  218.      * @see java.text.DateFormat
  219.      */
  220.     public SimpleDateFormat() {
  221.         this(SHORT, SHORT + 4, Locale.getDefault());
  222.     }
  223.  
  224.     /**
  225.      * Construct a SimpleDateFormat using the given pattern in the default
  226.      * locale.  <b>Note:</b> Not all locales support SimpleDateFormat; for full
  227.      * generality, use the factory methods in the DateFormat class.
  228.      */
  229.     public SimpleDateFormat(String pattern)
  230.     {
  231.         this(pattern, Locale.getDefault());
  232.     }
  233.  
  234.     /**
  235.      * Construct a SimpleDateFormat using the given pattern and locale.
  236.      * <b>Note:</b> Not all locales support SimpleDateFormat; for full
  237.      * generality, use the factory methods in the DateFormat class.
  238.      */
  239.     public SimpleDateFormat(String pattern, Locale loc)
  240.     {
  241.         this.pattern = pattern;
  242.         this.formatData = new DateFormatSymbols(loc);
  243.         initialize(loc);
  244.     }
  245.  
  246.     /**
  247.      * Construct a SimpleDateFormat using the given pattern and
  248.      * locale-specific symbol data.
  249.      */
  250.     public SimpleDateFormat(String pattern, DateFormatSymbols formatData)
  251.     {
  252.         this.pattern = pattern;
  253.         this.formatData = (DateFormatSymbols) formatData.clone();
  254.         initialize(Locale.getDefault());
  255.     }
  256.  
  257.     /* Package-private, called by DateFormat factory methods */
  258.     SimpleDateFormat(int timeStyle, int dateStyle, Locale loc) {
  259.     /* try the cache first */
  260.     String[] dateTimePatterns = (String[]) cachedLocaleData.get(loc);
  261.     if (dateTimePatterns == null) { /* cache miss */
  262.         ResourceBundle r = ResourceBundle.getBundle
  263.         ("java.text.resources.LocaleElements", loc);
  264.         dateTimePatterns = r.getStringArray("DateTimePatterns");
  265.         /* update cache */
  266.         cachedLocaleData.put(loc, dateTimePatterns);
  267.     }
  268.  
  269.         formatData = new DateFormatSymbols(loc);
  270.         if ((timeStyle >= 0) && (dateStyle >= 0)) {
  271.             Object[] dateTimeArgs = {dateTimePatterns[timeStyle],
  272.                                      dateTimePatterns[dateStyle]};
  273.             pattern = MessageFormat.format(dateTimePatterns[8], dateTimeArgs);
  274.         }
  275.         else if (timeStyle >= 0)
  276.             pattern = dateTimePatterns[timeStyle];
  277.         else if (dateStyle >= 0)
  278.             pattern = dateTimePatterns[dateStyle];
  279.         else
  280.             throw new IllegalArgumentException("No date or time style specified");
  281.  
  282.         initialize(loc);
  283.     }
  284.  
  285.     /* Initialize calendar and numberFormat fields */
  286.     private void initialize(Locale loc) {
  287.         // The format object must be constructed using the symbols for this zone.
  288.         // However, the calendar should use the current default TimeZone.
  289.         // If this is not contained in the locale zone strings, then the zone
  290.         // will be formatted using generic GMT+/-H:MM nomenclature.
  291.         calendar = Calendar.getInstance(TimeZone.getDefault(), loc);
  292.         numberFormat = NumberFormat.getInstance(loc);
  293.         numberFormat.setGroupingUsed(false);
  294.         if (numberFormat instanceof DecimalFormat)
  295.             ((DecimalFormat)numberFormat).setDecimalSeparatorAlwaysShown(false);
  296.         numberFormat.setParseIntegerOnly(true); /* So that dd.MM.yy can be parsed */
  297.         numberFormat.setMinimumFractionDigits(0); // To prevent "Jan 1.00, 1997.00"
  298.  
  299.         initializeDefaultCentury();
  300.     }
  301.  
  302.     /* Initialize the fields we use to disambiguate ambiguous years. Separate
  303.      * so we can call it from readObject().
  304.      */
  305.     private void initializeDefaultCentury() {
  306.         calendar.setTime( new Date() );
  307.         calendar.add( Calendar.YEAR, -80 );
  308.         parseAmbiguousDatesAsAfter(calendar.getTime());
  309.     }
  310.  
  311.     /* Define one-century window into which to disambiguate dates using
  312.      * two-digit years. Make public in JDK 1.2.
  313.      */
  314.     private void parseAmbiguousDatesAsAfter(Date startDate) {
  315.         defaultCenturyStart = startDate;
  316.         calendar.setTime(startDate);
  317.         defaultCenturyStartYear = calendar.get(Calendar.YEAR);
  318.     }
  319.  
  320.     /**
  321.      * Sets the 100-year period 2-digit years will be interpreted as being in
  322.      * to begin on the date the user specifies.
  323.      */
  324. // function made package private pending API-change approval
  325.     /*public*/ void set2DigitStartDate(Date startDate) {
  326.         parseAmbiguousDatesAsAfter(startDate);
  327.     }
  328.  
  329.     /**
  330.      * Returns the beginning date of the 100-year period 2-digit years are interpreted
  331.      * as being within.
  332.      */
  333. // function made package private pending API-change approval
  334.     /*public*/ Date get2DigitStartDate() {
  335.         return defaultCenturyStart;
  336.     }
  337.  
  338.     /**
  339.      * Overrides DateFormat
  340.      * <p>Formats a date or time, which is the standard millis
  341.      * since January 1, 1970, 00:00:00 GMT.
  342.      * <p>Example: using the US locale:
  343.      * "yyyy.MM.dd G 'at' HH:mm:ss zzz" ->> 1996.07.10 AD at 15:08:56 PDT
  344.      * @param date the date-time value to be formatted into a date-time string.
  345.      * @param toAppendTo where the new date-time text is to be appended.
  346.      * @param pos the formatting position. On input: an alignment field,
  347.      * if desired. On output: the offsets of the alignment field.
  348.      * @return the formatted date-time string.
  349.      * @see java.util.DateFormat
  350.      */
  351.     public StringBuffer format(Date date, StringBuffer toAppendTo,
  352.                                FieldPosition pos)
  353.     {
  354.         // Initialize
  355.         pos.beginIndex = pos.endIndex = 0;
  356.  
  357.         // Convert input date to time field list
  358.         calendar.setTime(date);
  359.  
  360.         boolean inQuote = false; // inQuote set true when hits 1st single quote
  361.         char prevCh = 0;
  362.         int count = 0;  // number of time pattern characters repeated
  363.         int interQuoteCount = 1; // Number of characters between quotes
  364.         for (int i=0; i<pattern.length(); ++i)
  365.         {
  366.             char ch = pattern.charAt(i);
  367.             if (inQuote)
  368.             {
  369.                 if (ch == '\'')
  370.                 {
  371.                     // ends with 2nd single quote
  372.                     inQuote = false;
  373.                     if (count == 0)
  374.                         toAppendTo.append(ch);  // two consecutive quotes outside a quote: ''
  375.                     else count = 0;
  376.                     interQuoteCount = 0;
  377.                 }
  378.                 else
  379.                 {
  380.                     toAppendTo.append(ch);
  381.                     count++;
  382.                 }
  383.             }
  384.             else // !inQuote
  385.             {
  386.                 if (ch == '\'')
  387.                 {
  388.                     inQuote = true;
  389.                     if (count > 0) // handle cases like: yyyy'....
  390.                     {
  391.                         toAppendTo.append(subFormat(prevCh, count,
  392.                                                     toAppendTo.length(),
  393.                                                     pos));
  394.                         count = 0;
  395.                         prevCh = 0;
  396.                     }
  397.  
  398.                     // We count characters between quotes so we can recognize
  399.                     // two single quotes inside a quote.  Example: 'o''clock'.
  400.                     if (interQuoteCount == 0)
  401.                     {
  402.                         toAppendTo.append(ch);
  403.                         count = 1; // Make it look like we never left.
  404.                     }
  405.                 }
  406.                 else if (ch >= 'a' && ch <= 'z' || ch >= 'A' && ch <= 'Z')
  407.                 {
  408.                     // ch is a date-time pattern
  409.                     if (ch != prevCh && count > 0) //handle cases: eg, yyyyMMdd
  410.                     {
  411.                         toAppendTo.append(subFormat(prevCh, count,
  412.                                                     toAppendTo.length(),
  413.                                                     pos));
  414.                         prevCh = ch;
  415.                         count = 1;
  416.                     }
  417.                     else
  418.                     {
  419.                         if (ch != prevCh)
  420.                             prevCh = ch;
  421.                         count++;
  422.                     }
  423.                 }
  424.                 else if (count > 0) // handle cases like: MM-dd-yy or HH:mm:ss
  425.                 {
  426.                     toAppendTo.append(subFormat(prevCh, count,
  427.                                                 toAppendTo.length(),
  428.                                                 pos));
  429.                     toAppendTo.append(ch);
  430.                     prevCh = 0;
  431.                     count = 0;
  432.                 }
  433.                 else // any other unquoted characters
  434.                     toAppendTo.append(ch);
  435.  
  436.                 ++interQuoteCount;
  437.             }
  438.         }
  439.         // Format the last item in the pattern
  440.         if (count > 0)
  441.         {
  442.             toAppendTo.append(subFormat(prevCh, count,
  443.                                         toAppendTo.length(), pos));
  444.         }
  445.         return toAppendTo;
  446.     }
  447.  
  448.     // Map index into pattern character string to Calendar field number
  449.     private static final int[] PATTERN_INDEX_TO_CALENDAR_FIELD =
  450.     {
  451.         Calendar.ERA, Calendar.YEAR, Calendar.MONTH, Calendar.DATE,
  452.         Calendar.HOUR_OF_DAY, Calendar.HOUR_OF_DAY, Calendar.MINUTE,
  453.         Calendar.SECOND, Calendar.MILLISECOND, Calendar.DAY_OF_WEEK,
  454.         Calendar.DAY_OF_YEAR, Calendar.DAY_OF_WEEK_IN_MONTH,
  455.         Calendar.WEEK_OF_YEAR, Calendar.WEEK_OF_MONTH,
  456.         Calendar.AM_PM, Calendar.HOUR, Calendar.HOUR, Calendar.ZONE_OFFSET
  457.     };
  458.  
  459.     // Map index into pattern character string to DateFormat field number
  460.     private static final int[] PATTERN_INDEX_TO_DATE_FORMAT_FIELD = {
  461.         DateFormat.ERA_FIELD, DateFormat.YEAR_FIELD, DateFormat.MONTH_FIELD,
  462.         DateFormat.DATE_FIELD, DateFormat.HOUR_OF_DAY1_FIELD,
  463.         DateFormat.HOUR_OF_DAY0_FIELD, DateFormat.MINUTE_FIELD,
  464.         DateFormat.SECOND_FIELD, DateFormat.MILLISECOND_FIELD,
  465.         DateFormat.DAY_OF_WEEK_FIELD, DateFormat.DAY_OF_YEAR_FIELD,
  466.         DateFormat.DAY_OF_WEEK_IN_MONTH_FIELD, DateFormat.WEEK_OF_YEAR_FIELD,
  467.         DateFormat.WEEK_OF_MONTH_FIELD, DateFormat.AM_PM_FIELD,
  468.         DateFormat.HOUR1_FIELD, DateFormat.HOUR0_FIELD,
  469.         DateFormat.TIMEZONE_FIELD,
  470.     };
  471.  
  472.     // Private member function that does the real date/time formatting.
  473.     private String subFormat(char ch, int count, int beginOffset,
  474.                              FieldPosition pos)
  475.          throws IllegalArgumentException
  476.     {
  477.         int     patternCharIndex = -1;
  478.         int     maxIntCount = 10;
  479.         String  current = "";
  480.  
  481.         if ((patternCharIndex=formatData.patternChars.indexOf(ch)) == -1)
  482.             throw new IllegalArgumentException("Illegal pattern character " +
  483.                                                "'" + ch + "'");
  484.  
  485.         int field = PATTERN_INDEX_TO_CALENDAR_FIELD[patternCharIndex];
  486.         int value = calendar.get(field);
  487.  
  488.         switch (patternCharIndex) {
  489.         case 0: // 'G' - ERA
  490.             current = formatData.eras[value];
  491.             break;
  492.         case 1: // 'y' - YEAR
  493.             if (count >= 4)
  494.                 //                current = zeroPaddingNumber(value, 4, count);
  495.                 current = zeroPaddingNumber(value, 4, maxIntCount);
  496.             else // count < 4
  497.                 current = zeroPaddingNumber(value, 2, 2); // clip 1996 to 96
  498.             break;
  499.         case 2: // 'M' - MONTH
  500.             if (count >= 4)
  501.                 current = formatData.months[value];
  502.             else if (count == 3)
  503.                 current = formatData.shortMonths[value];
  504.             else
  505.                 current = zeroPaddingNumber(value+1, count, maxIntCount);
  506.             break;
  507.         case 4: // 'k' - HOUR_OF_DAY: 1-based.  eg, 23:59 + 1 hour =>> 24:59
  508.             if (value == 0)
  509.                 current = zeroPaddingNumber(
  510.                                             calendar.getMaximum(Calendar.HOUR_OF_DAY)+1,
  511.                                             count, maxIntCount);
  512.             else
  513.                 current = zeroPaddingNumber(value, count, maxIntCount);
  514.             break;
  515.         case 8: // 'S' - MILLISECOND
  516.             if (count > 3)
  517.                 count = 3;
  518.             else if (count == 2)
  519.                 value = value / 10;
  520.             else if (count == 1)
  521.                 value = value / 100;
  522.             current = zeroPaddingNumber(value, count, maxIntCount);
  523.             break;
  524.         case 9: // 'E' - DAY_OF_WEEK
  525.             if (count >= 4)
  526.                 current = formatData.weekdays[value];
  527.             else // count < 4, use abbreviated form if exists
  528.                 current = formatData.shortWeekdays[value];
  529.             break;
  530.         case 14:    // 'a' - AM_PM
  531.             current = formatData.ampms[value];
  532.             break;
  533.         case 15: // 'h' - HOUR:1-based.  eg, 11PM + 1 hour =>> 12 AM
  534.             if (value == 0)
  535.                 current = zeroPaddingNumber(
  536.                                             calendar.getLeastMaximum(Calendar.HOUR)+1,
  537.                                             count, maxIntCount);
  538.             else
  539.                 current = zeroPaddingNumber(value, count, maxIntCount);
  540.             break;
  541.         case 17: // 'z' - ZONE_OFFSET
  542.             int zoneIndex
  543.                 = formatData.getZoneIndex (calendar.getTimeZone().getID());
  544.             if (zoneIndex == -1)
  545.             {
  546.                 // For time zones that have no names, use strings
  547.                 // GMT+hours:minutes and GMT-hours:minutes.
  548.                 // For instance, France time zone uses GMT+01:00.
  549.                 StringBuffer zoneString = new StringBuffer();
  550.  
  551.                 value = calendar.get(Calendar.ZONE_OFFSET) +
  552.                     calendar.get(Calendar.DST_OFFSET);
  553.  
  554.                 if (value < 0)
  555.                 {
  556.                     zoneString.append(GMT_MINUS);
  557.                     value = -value; // suppress the '-' sign for text display.
  558.                 }
  559.                 else
  560.                     zoneString.append(GMT_PLUS);
  561.                 zoneString.append(
  562.                                   zeroPaddingNumber((int)(value/millisPerHour), 2, 2));
  563.                 zoneString.append(':');
  564.                 zoneString.append(
  565.                                   zeroPaddingNumber(
  566.                                                     (int)((value%millisPerHour)/millisPerMinute), 2, 2));
  567.                 current = zoneString.toString();
  568.             }
  569.             else if (calendar.get(Calendar.DST_OFFSET) != 0)
  570.             {
  571.                 if (count >= 4)
  572.                     current = formatData.zoneStrings[zoneIndex][3];
  573.                 else
  574.                     // count < 4, use abbreviated form if exists
  575.                     current = formatData.zoneStrings[zoneIndex][4];
  576.             }
  577.             else
  578.             {
  579.                 if (count >= 4)
  580.                     current = formatData.zoneStrings[zoneIndex][1];
  581.                 else
  582.                     current = formatData.zoneStrings[zoneIndex][2];
  583.             }
  584.             break;
  585.         default:
  586.             // case 3: // 'd' - DATE
  587.             // case 5: // 'H' - HOUR_OF_DAY:0-based.  eg, 23:59 + 1 hour =>> 00:59
  588.             // case 6: // 'm' - MINUTE
  589.             // case 7: // 's' - SECOND
  590.             // case 10: // 'D' - DAY_OF_YEAR
  591.             // case 11: // 'F' - DAY_OF_WEEK_IN_MONTH
  592.             // case 12: // 'w' - WEEK_OF_YEAR
  593.             // case 13: // 'W' - WEEK_OF_MONTH
  594.             // case 16: // 'K' - HOUR: 0-based.  eg, 11PM + 1 hour =>> 0 AM
  595.             current = zeroPaddingNumber(value, count, maxIntCount);
  596.             break;
  597.         } // switch (patternCharIndex)
  598.  
  599.         if (pos.field == PATTERN_INDEX_TO_DATE_FORMAT_FIELD[patternCharIndex]) {
  600.             // set for the first occurence only.
  601.             if (pos.beginIndex == 0 && pos.endIndex == 0) {
  602.                 pos.beginIndex = beginOffset;
  603.                 pos.endIndex = beginOffset + current.length();
  604.             }
  605.         }
  606.  
  607.         return current;
  608.     }
  609.  
  610.  
  611.     // Pad the shorter numbers up to maxCount digits.
  612.     private String zeroPaddingNumber(long value, int minDigits, int maxDigits)
  613.     {
  614.         numberFormat.setMinimumIntegerDigits(minDigits);
  615.         numberFormat.setMaximumIntegerDigits(maxDigits);
  616.         return numberFormat.format(value);
  617.     }
  618.  
  619.  
  620.     /**
  621.      * Overrides DateFormat
  622.      * @see java.util.DateFormat
  623.      */
  624.     public Date parse(String text, ParsePosition pos)
  625.     {
  626.         int start = pos.index;
  627.         int oldStart = start;
  628.         boolean[] ambiguousYear = {false};
  629.  
  630.         calendar.clear(); // Clears all the time fields
  631.  
  632.         boolean inQuote = false; // inQuote set true when hits 1st single quote
  633.         char prevCh = 0;
  634.         int count = 0;
  635.         int interQuoteCount = 1; // Number of chars between quotes
  636.  
  637.         for (int i=0; i<pattern.length(); ++i)
  638.         {
  639.             char ch = pattern.charAt(i);
  640.  
  641.             if (inQuote)
  642.             {
  643.                 if (ch == '\'')
  644.                 {
  645.                     // ends with 2nd single quote
  646.                     inQuote = false;
  647.                     // two consecutive quotes outside a quote means we have
  648.                     // a quote literal we need to match.
  649.                     if (count == 0)
  650.                     {
  651.                         if (ch != text.charAt(start))
  652.                         {
  653.                             pos.index = oldStart;
  654.                             pos.errorIndex = start;
  655.                             return null;
  656.                         }
  657.                         ++start;
  658.                     }
  659.                     count = 0;
  660.                     interQuoteCount = 0;
  661.                 }
  662.                 else
  663.                 {
  664.                     // pattern uses text following from 1st single quote.
  665.                     if (ch != text.charAt(start)) {
  666.                         // Check for cases like: 'at' in pattern vs "xt"
  667.                         // in time text, where 'a' doesn't match with 'x'.
  668.                         // If fail to match, return null.
  669.                         pos.index = oldStart; // left unchanged
  670.                         pos.errorIndex = start;
  671.                         return null;
  672.                     }
  673.                     ++count;
  674.                     ++start;
  675.                 }
  676.             }
  677.             else    // !inQuote
  678.             {
  679.                 if (ch == '\'')
  680.                 {
  681.                     inQuote = true;
  682.                     if (count > 0) // handle cases like: e'at'
  683.                     {
  684.                         int startOffset = start;
  685.                         start=subParse(text, start, prevCh, count,
  686.                                        false, ambiguousYear);
  687.                         if ( start<0 ) {
  688.                             pos.errorIndex = startOffset;
  689.                             pos.index = oldStart;
  690.                             return null;
  691.                         }
  692.                         count = 0;
  693.                     }
  694.  
  695.                     if (interQuoteCount == 0)
  696.                     {
  697.                         // This indicates two consecutive quotes inside a quote,
  698.                         // for example, 'o''clock'.  We need to parse this as
  699.                         // representing a single quote within the quote.
  700.                         int startOffset = start;
  701.                         if (ch != text.charAt(start))
  702.                         {
  703.                             pos.errorIndex = startOffset;
  704.                             pos.index = oldStart;
  705.                             return null;
  706.                         }
  707.                         ++start;
  708.                         count = 1; // Make it look like we never left
  709.                     }
  710.                 }
  711.                 else if (ch >= 'a' && ch <= 'z' || ch >= 'A' && ch <= 'Z')
  712.                 {
  713.                     // ch is a date-time pattern
  714.                     if (ch != prevCh && count > 0) // e.g., yyyyMMdd
  715.                     {
  716.                         int startOffset = start;
  717.                         // This is the only case where we pass in 'true' for
  718.                         // obeyCount.  That's because the next field directly
  719.                         // abuts this one, so we have to use the count to know when
  720.                         // to stop parsing. [LIU]
  721.                         start = subParse(text, start, prevCh, count, true,
  722.                                          ambiguousYear);
  723.                         if (start < 0) {
  724.                             pos.errorIndex = startOffset;
  725.                             pos.index = oldStart;
  726.                             return null;
  727.                         }
  728.                         prevCh = ch;
  729.                         count = 1;
  730.                     }
  731.                     else
  732.                     {
  733.                         if (ch != prevCh)
  734.                             prevCh = ch;
  735.                         count++;
  736.                     }
  737.                 }
  738.                 else if (count > 0)
  739.                 {
  740.                     // handle cases like: MM-dd-yy, HH:mm:ss, or yyyy MM dd,
  741.                     // where ch = '-', ':', or ' ', repectively.
  742.                     int startOffset = start;
  743.                     start=subParse(text, start, prevCh, count,
  744.                                    false, ambiguousYear);
  745.                     if ( start < 0 ) {
  746.                         pos.errorIndex = startOffset;
  747.                         pos.index = oldStart;
  748.                         return null;
  749.                     }
  750.                     if (start >= text.length() || ch != text.charAt(start)) {
  751.                         // handle cases like: 'MMMM dd' in pattern vs. "janx20"
  752.                         // in time text, where ' ' doesn't match with 'x'.
  753.                         pos.errorIndex = start;
  754.                         pos.index = oldStart;
  755.                         return null;
  756.                     }
  757.                     start++;
  758.                     count = 0;
  759.                     prevCh = 0;
  760.                 }
  761.                 else // any other unquoted characters
  762.                 {
  763.                     if (ch != text.charAt(start)) {
  764.                         // handle cases like: 'MMMM   dd' in pattern vs.
  765.                         // "jan,,,20" in time text, where "   " doesn't
  766.                         // match with ",,,".
  767.                         pos.errorIndex = start;
  768.                         pos.index = oldStart;
  769.                         return null;
  770.                     }
  771.                     start++;
  772.                 }
  773.  
  774.                 ++interQuoteCount;
  775.             }
  776.         }
  777.         // Parse the last item in the pattern
  778.         if (count > 0)
  779.         {
  780.             int startOffset = start;
  781.             start=subParse(text, start, prevCh, count,
  782.                            false, ambiguousYear);
  783.             if ( start < 0 ) {
  784.                 pos.index = oldStart;
  785.                 pos.errorIndex = startOffset;
  786.                 return null;
  787.             }
  788.         }
  789.  
  790.         // At this point the fields of Calendar have been set.  Calendar
  791.         // will fill in default values for missing fields when the time
  792.         // is computed.
  793.  
  794.         pos.index = start;
  795.  
  796.         // This part is a problem:  When we call parsedDate.after, we compute the time.
  797.         // Take the date April 3 2004 at 2:30 am.  When this is first set up, the year
  798.         // will be wrong if we're parsing a 2-digit year pattern.  It will be 1904.
  799.         // April 3 1904 is a Sunday (unlike 2004) so it is the DST onset day.  2:30 am
  800.         // is therefore an "impossible" time, since the time goes from 1:59 to 3:00 am
  801.         // on that day.  It is therefore parsed out to fields as 3:30 am.  Then we
  802.         // add 100 years, and get April 3 2004 at 3:30 am.  Note that April 3 2004 is
  803.         // a Saturday, so it can have a 2:30 am -- and it should. [LIU]
  804.         /*
  805.         Date parsedDate = calendar.getTime();
  806.         if( ambiguousYear[0] && !parsedDate.after(defaultCenturyStart) ) {
  807.             calendar.add(Calendar.YEAR, 100);
  808.             parsedDate = calendar.getTime();
  809.         }
  810.         */
  811.         // Because of the above condition, save off the fields in case we need to readjust.
  812.         // The procedure we use here is not particularly efficient, but there is no other
  813.         // way to do this given the API restrictions present in Calendar.  We minimize
  814.         // inefficiency by only performing this computation when it might apply, that is,
  815.         // when the two-digit year is equal to the start year, and thus might fall at the
  816.         // front or the back of the default century.  This only works because we adjust
  817.         // the year correctly to start with in other cases -- see subParse().
  818.         Date parsedDate;
  819.         try {
  820.             if (ambiguousYear[0]) // If this is true then the two-digit year == the default start year
  821.             {
  822.                 // We need a copy of the fields, and we need to avoid triggering a call to
  823.                 // complete(), which will recalculate the fields.  Since we can't access
  824.                 // the fields[] array in Calendar, we clone the entire object.  This will
  825.                 // stop working if Calendar.clone() is ever rewritten to call complete().
  826.                 Calendar savedCalendar = (Calendar)calendar.clone();
  827.                 parsedDate = calendar.getTime();
  828.                 if (parsedDate.before(defaultCenturyStart))
  829.                 {
  830.                     // We can't use add here because that does a complete() first.
  831.                     savedCalendar.set(Calendar.YEAR, defaultCenturyStartYear + 100);
  832.                     parsedDate = savedCalendar.getTime();
  833.                 }
  834.             }
  835.             else parsedDate = calendar.getTime();
  836.         }
  837.         // An IllegalArgumentException will be thrown by Calendar.getTime()
  838.         // if any fields are out of range, e.g., MONTH == 17.
  839.         catch (IllegalArgumentException e) {
  840.             pos.errorIndex = start;
  841.             pos.index = oldStart;
  842.             return null;
  843.         }
  844.  
  845.         return parsedDate;
  846.     }
  847.  
  848.     /**
  849.      * Private code-size reduction function used by subParse.
  850.      * @param text the time text being parsed.
  851.      * @param start where to start parsing.
  852.      * @param field the date field being parsed.
  853.      * @param data the string array to parsed.
  854.      * @return the new start position if matching succeeded; a negative number
  855.      * indicating matching failure, otherwise.
  856.      */
  857.     private int matchString(String text, int start, int field, String[] data)
  858.     {
  859.         int i = 0;
  860.         int count = data.length;
  861.  
  862.         if (field == Calendar.DAY_OF_WEEK) i = 1;
  863.  
  864.         // There may be multiple strings in the data[] array which begin with
  865.         // the same prefix (e.g., Cerven and Cervenec (June and July) in Czech).
  866.         // We keep track of the longest match, and return that.  Note that this
  867.         // unfortunately requires us to test all array elements.
  868.         int bestMatchLength = 0, bestMatch = -1;
  869.         for (; i<count; ++i)
  870.         {
  871.             int length = data[i].length();
  872.             // Always compare if we have no match yet; otherwise only compare
  873.             // against potentially better matches (longer strings).
  874.             if (length > bestMatchLength &&
  875.                 text.regionMatches(true, start, data[i], 0, length))
  876.             {
  877.                 bestMatch = i;
  878.                 bestMatchLength = length;
  879.             }
  880.         }
  881.         if (bestMatch >= 0)
  882.         {
  883.             calendar.set(field, bestMatch);
  884.             return start + bestMatchLength;
  885.         }
  886.         return -start;
  887.     }
  888.  
  889.     /**
  890.      * Private member function that converts the parsed date strings into
  891.      * timeFields. Returns -start (for ParsePosition) if failed.
  892.      * @param text the time text to be parsed.
  893.      * @param start where to start parsing.
  894.      * @param ch the pattern character for the date field text to be parsed.
  895.      * @param count the count of a pattern character.
  896.      * @param obeyCount if true, then the next field directly abuts this one,
  897.      * and we should use the count to know when to stop parsing.
  898.      * @param ambiguousYear return parameter; upon return, if ambiguousYear[0]
  899.      * is true, then a two-digit year was parsed and may need to be readjusted.
  900.      * @return the new start position if matching succeeded; a negative number
  901.      * indicating matching failure, otherwise.
  902.      */
  903.     private int subParse(String text, int start, char ch, int count,
  904.                          boolean obeyCount, boolean[] ambiguousYear)
  905.     {
  906.         Number number;
  907.         int value = 0;
  908.         int i;
  909.         ParsePosition pos = new ParsePosition(0);
  910.         int patternCharIndex = -1;
  911.  
  912.         if ((patternCharIndex=formatData.patternChars.indexOf(ch)) == -1)
  913.             return -start;
  914.  
  915.         pos.index = start;
  916.  
  917.         int field = PATTERN_INDEX_TO_CALENDAR_FIELD[patternCharIndex];
  918.  
  919.         // If there are any spaces here, skip over them.  If we hit the end
  920.         // of the string, then fail.
  921.         for (;;) {
  922.             if (pos.index >= text.length()) return -start;
  923.             char c = text.charAt(pos.index);
  924.             if (c != ' ' && c != '\t') break;
  925.             ++pos.index;
  926.         }
  927.  
  928.         // We handle a few special cases here where we need to parse
  929.         // a number value.  We handle further, more generic cases below.  We need
  930.         // to handle some of them here because some fields require extra processing on
  931.         // the parsed value.
  932.         if (patternCharIndex == 4 /*HOUR_OF_DAY1_FIELD*/ ||
  933.             patternCharIndex == 15 /*HOUR1_FIELD*/ ||
  934.             (patternCharIndex == 2 /*MONTH_FIELD*/ && count <= 2) ||
  935.             patternCharIndex == 1 /*YEAR*/)
  936.         {
  937.             // It would be good to unify this with the obeyCount logic below,
  938.             // but that's going to be difficult.
  939.             if (obeyCount)
  940.             {
  941.                 if ((start+count) > text.length()) return -start;
  942.                 number = numberFormat.parse(text.substring(0, start+count), pos);
  943.             }
  944.             else number = numberFormat.parse(text, pos);
  945.             if (number == null) return -start;
  946.             value = number.intValue();
  947.         }
  948.  
  949.         switch (patternCharIndex)
  950.         {
  951.         case 0: // 'G' - ERA
  952.             return matchString(text, start, Calendar.ERA, formatData.eras);
  953.         case 1: // 'y' - YEAR
  954.             // If there are 4 or more YEAR pattern characters, this indicates
  955.             // that the year value is to be treated literally, without any
  956.             // two-digit year adjustments (e.g., from "01" to 2001).  Otherwise
  957.             // we made adjustments to place the 2-digit year in the proper
  958.             // century -- unless the given year itself is more than two characters.
  959.             if (count <= 2 && (pos.index - start) <= 2)
  960.             {
  961.                 // Assume for example that the defaultCenturyStart is 6/18/1903.
  962.                 // This means that two-digit years will be forced into the range
  963.                 // 6/18/1903 to 6/17/2003.  As a result, years 00, 01, and 02
  964.                 // correspond to 2000, 2001, and 2002.  Years 04, 05, etc. correspond
  965.                 // to 1904, 1905, etc.  If the year is 03, then it is 2003 if the
  966.                 // other fields specify a date before 6/18, or 1903 if they specify a
  967.                 // date afterwards.  As a result, 03 is an ambiguous year.  All other
  968.                 // two-digit years are unambiguous.
  969.                 int ambiguousTwoDigitYear = defaultCenturyStartYear % 100;
  970.                 ambiguousYear[0] = value == ambiguousTwoDigitYear;
  971.                 value += (defaultCenturyStartYear/100)*100 +
  972.                     (value < ambiguousTwoDigitYear ? 100 : 0);
  973.             }
  974.             calendar.set(Calendar.YEAR, value);
  975.             return pos.index;
  976.         case 2: // 'M' - MONTH
  977.             if (count <= 2) // i.e., M or MM.
  978.             {
  979.                 // Don't want to parse the month if it is a string
  980.                 // while pattern uses numeric style: M or MM.
  981.                 // [We computed 'value' above.]
  982.                 calendar.set(Calendar.MONTH, value - 1);
  983.                 return pos.index;
  984.             }
  985.             else
  986.             {
  987.                 // count >= 3 // i.e., MMM or MMMM
  988.                 // Want to be able to parse both short and long forms.
  989.                 // Try count == 4 first:
  990.                 int newStart = 0;
  991.                 if ((newStart=matchString(text, start, Calendar.MONTH,
  992.                                           formatData.months)) > 0)
  993.                     return newStart;
  994.                 else // count == 4 failed, now try count == 3
  995.                     return matchString(text, start, Calendar.MONTH,
  996.                                        formatData.shortMonths);
  997.             }
  998.         case 4: // 'k' - HOUR_OF_DAY: 1-based.  eg, 23:59 + 1 hour =>> 24:59
  999.             // [We computed 'value' above.]
  1000.             if (value == calendar.getMaximum(Calendar.HOUR_OF_DAY)+1) value = 0;
  1001.             calendar.set(Calendar.HOUR_OF_DAY, value);
  1002.             return pos.index;
  1003.         case 9: { // 'E' - DAY_OF_WEEK
  1004.             // Want to be able to parse both short and long forms.
  1005.             // Try count == 4 (DDDD) first:
  1006.             int newStart = 0;
  1007.             if ((newStart=matchString(text, start, Calendar.DAY_OF_WEEK,
  1008.                                       formatData.weekdays)) > 0)
  1009.                 return newStart;
  1010.             else // DDDD failed, now try DDD
  1011.                 return matchString(text, start, Calendar.DAY_OF_WEEK,
  1012.                                    formatData.shortWeekdays);
  1013.         }
  1014.         case 14:    // 'a' - AM_PM
  1015.             return matchString(text, start, Calendar.AM_PM, formatData.ampms);
  1016.         case 15: // 'h' - HOUR:1-based.  eg, 11PM + 1 hour =>> 12 AM
  1017.             // [We computed 'value' above.]
  1018.             if (value == calendar.getLeastMaximum(Calendar.HOUR)+1) value = 0;
  1019.             calendar.set(Calendar.HOUR, value);
  1020.             return pos.index;
  1021.         case 17: // 'z' - ZONE_OFFSET
  1022.             // First try to parse generic forms such as GMT-07:00. Do this first
  1023.             // in case localized DateFormatZoneData contains the string "GMT"
  1024.             // for a zone; in that case, we don't want to match the first three
  1025.             // characters of GMT+/-HH:MM etc.
  1026.             {
  1027.                 int sign = 0;
  1028.                 int offset;
  1029.  
  1030.                 // For time zones that have no known names, look for strings
  1031.                 // of the form:
  1032.                 //    GMT[+-]hours:minutes or
  1033.                 //    GMT[+-]hhmm or
  1034.                 //    GMT.
  1035.                 if (text.regionMatches(true,start, GMT, 0, GMT.length()))
  1036.                 {
  1037.                     calendar.set(Calendar.DST_OFFSET, 0);
  1038.  
  1039.                     pos.index = start + GMT.length();
  1040.  
  1041.                     if( text.charAt(pos.index) == '+' )
  1042.                         sign = 1;
  1043.                     else if( text.charAt(pos.index) == '-' )
  1044.                         sign = -1;
  1045.                     else {
  1046.                         calendar.set(Calendar.ZONE_OFFSET, 0 );
  1047.                         return pos.index;
  1048.                     }
  1049.  
  1050.                     // Look for hours:minutes or hhmm.
  1051.                     pos.index++;
  1052.                     Number tzNumber = numberFormat.parse(text, pos);
  1053.                     if( tzNumber == null ) {
  1054.                         return -start;
  1055.                     }
  1056.                     if( text.charAt(pos.index) == ':' ) {
  1057.                         // This is the hours:minutes case
  1058.                         offset = tzNumber.intValue() * 60;
  1059.                         pos.index++;
  1060.                         tzNumber = numberFormat.parse(text, pos);
  1061.                         if( tzNumber == null ) {
  1062.                             return -start;
  1063.                         }
  1064.                         offset += tzNumber.intValue();
  1065.                     }
  1066.                     else {
  1067.                         // This is the hhmm case.
  1068.                         offset = tzNumber.intValue();
  1069.                         if( offset < 24 )
  1070.                             offset *= 60;
  1071.                         else
  1072.                             offset = offset % 100 + offset / 100 * 60;
  1073.                     }
  1074.  
  1075.                     // Fall through for final processing below of 'offset' and 'sign'.
  1076.                 }
  1077.                 else {
  1078.                     // At this point, check for named time zones by looking through
  1079.                     // the locale data from the DateFormatZoneData strings.
  1080.                     // Want to be able to parse both short and long forms.
  1081.                     for (i=0; i<formatData.zoneStrings.length; i++)
  1082.                     {
  1083.                         // Checking long and short zones [1 & 2],
  1084.                         // and long and short daylight [3 & 4].
  1085.                         int j = 1;
  1086.                         for (; j <= 4; ++j)
  1087.                         {
  1088.                             if (text.regionMatches(true, start,
  1089.                                                    formatData.zoneStrings[i][j], 0,
  1090.                                                    formatData.zoneStrings[i][j].length()))
  1091.                                 break;
  1092.                         }
  1093.                         if (j <= 4)
  1094.                         {
  1095.                             TimeZone tz = TimeZone.getTimeZone(formatData.zoneStrings[i][0]);
  1096.                             calendar.set(Calendar.ZONE_OFFSET, tz.getRawOffset());
  1097.                             // Must call set() with something -- TODO -- Fix this to
  1098.                             // use the correct DST SAVINGS for the zone.
  1099.                             calendar.set(Calendar.DST_OFFSET, j >= 3 ? millisPerHour : 0);
  1100.                             return (start + formatData.zoneStrings[i][j].length());
  1101.                         }
  1102.                     }
  1103.  
  1104.                     // As a last resort, look for numeric timezones of the form
  1105.                     // [+-]hhmm as specified by RFC 822.  This code is actually
  1106.                     // a little more permissive than RFC 822.  It will try to do
  1107.                     // its best with numbers that aren't strictly 4 digits long.
  1108.                     DecimalFormat fmt = new DecimalFormat("+####;-####");
  1109.                     fmt.setParseIntegerOnly(true);
  1110.                     Number tzNumber = fmt.parse( text, pos );
  1111.                     if( tzNumber == null ) {
  1112.                         return -start;   // Wasn't actually a number.
  1113.                     }
  1114.                     offset = tzNumber.intValue();
  1115.                     sign = 1;
  1116.                     if( offset < 0 ) {
  1117.                         sign = -1;
  1118.                         offset = -offset;
  1119.                     }
  1120.                     if( offset < 24 )
  1121.                         offset = offset * 60;
  1122.                     else
  1123.                         offset = offset % 100 + offset / 100 * 60;
  1124.  
  1125.                     // Fall through for final processing below of 'offset' and 'sign'.
  1126.                 }
  1127.  
  1128.                 // Do the final processing for both of the above cases.  We only
  1129.                 // arrive here if the form GMT+/-... or an RFC 822 form was seen.
  1130.                 if (sign != 0)
  1131.                 {
  1132.                     offset *= millisPerMinute * sign;
  1133.  
  1134.                     if (calendar.getTimeZone().useDaylightTime())
  1135.                     {
  1136.                         calendar.set(Calendar.DST_OFFSET, millisPerHour);
  1137.                         offset -= millisPerHour;
  1138.                     }
  1139.                     calendar.set(Calendar.ZONE_OFFSET, offset);
  1140.  
  1141.                     return pos.index;
  1142.                 }
  1143.             }
  1144.  
  1145.             // All efforts to parse a zone failed.
  1146.             return -start;
  1147.  
  1148.         default:
  1149.             // case 3: // 'd' - DATE
  1150.             // case 5: // 'H' - HOUR_OF_DAY:0-based.  eg, 23:59 + 1 hour =>> 00:59
  1151.             // case 6: // 'm' - MINUTE
  1152.             // case 7: // 's' - SECOND
  1153.             // case 8: // 'S' - MILLISECOND
  1154.             // case 10: // 'D' - DAY_OF_YEAR
  1155.             // case 11: // 'F' - DAY_OF_WEEK_IN_MONTH
  1156.             // case 12: // 'w' - WEEK_OF_YEAR
  1157.             // case 13: // 'W' - WEEK_OF_MONTH
  1158.             // case 16: // 'K' - HOUR: 0-based.  eg, 11PM + 1 hour =>> 0 AM
  1159.  
  1160.             // Handle "generic" fields
  1161.             if (obeyCount)
  1162.             {
  1163.                 if ((start+count) > text.length()) return -start;
  1164.                 number = numberFormat.parse(text.substring(0, start+count), pos);
  1165.             }
  1166.             else number = numberFormat.parse(text, pos);
  1167.             if (number != null)
  1168.             {
  1169.                 calendar.set(field, number.intValue());
  1170.                 return pos.index;
  1171.             }
  1172.             return -start;
  1173.         }
  1174.     }
  1175.  
  1176.  
  1177.     /**
  1178.      * Translate a pattern, mapping each character in the from string to the
  1179.      * corresponding character in the to string.
  1180.      */
  1181.     private String translatePattern(String pattern, String from, String to) {
  1182.         StringBuffer result = new StringBuffer();
  1183.         boolean inQuote = false;
  1184.         for (int i = 0; i < pattern.length(); ++i) {
  1185.             char c = pattern.charAt(i);
  1186.             if (inQuote) {
  1187.                 if (c == '\'')
  1188.                     inQuote = false;
  1189.             }
  1190.             else {
  1191.                 if (c == '\'')
  1192.                     inQuote = true;
  1193.                 else if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) {
  1194.                     int ci = from.indexOf(c);
  1195.                     if (ci == -1)
  1196.                         throw new IllegalArgumentException("Illegal pattern " +
  1197.                                                            " character '" +
  1198.                                                            c + "'");
  1199.                     c = to.charAt(ci);
  1200.                 }
  1201.             }
  1202.             result.append(c);
  1203.         }
  1204.         if (inQuote)
  1205.             throw new IllegalArgumentException("Unfinished quote in pattern");
  1206.         return result.toString();
  1207.     }
  1208.  
  1209.     /**
  1210.      * Return a pattern string describing this date format.
  1211.      */
  1212.     public String toPattern() {
  1213.         return pattern;
  1214.     }
  1215.  
  1216.     /**
  1217.      * Return a localized pattern string describing this date format.
  1218.      */
  1219.     public String toLocalizedPattern() {
  1220.         return translatePattern(pattern,
  1221.                                 formatData.patternChars,
  1222.                                 formatData.localPatternChars);
  1223.     }
  1224.  
  1225.     /**
  1226.      * Apply the given unlocalized pattern string to this date format.
  1227.      */
  1228.     public void applyPattern (String pattern)
  1229.     {
  1230.         this.pattern = pattern;
  1231.     }
  1232.  
  1233.     /**
  1234.      * Apply the given localized pattern string to this date format.
  1235.      */
  1236.     public void applyLocalizedPattern(String pattern) {
  1237.         this.pattern = translatePattern(pattern,
  1238.                                         formatData.localPatternChars,
  1239.                                         formatData.patternChars);
  1240.     }
  1241.  
  1242.     /**
  1243.      * Gets the date/time formatting data.
  1244.      * @return a copy of the date-time formatting data associated
  1245.      * with this date-time formatter.
  1246.      */
  1247.     public DateFormatSymbols getDateFormatSymbols()
  1248.     {
  1249.         return (DateFormatSymbols)formatData.clone();
  1250.     }
  1251.  
  1252.     /**
  1253.      * Allows you to set the date/time formatting data.
  1254.      * @param newFormatData the given date-time formatting data.
  1255.      */
  1256.     public void setDateFormatSymbols(DateFormatSymbols newFormatSymbols)
  1257.     {
  1258.         this.formatData = (DateFormatSymbols)newFormatSymbols.clone();
  1259.     }
  1260.  
  1261.     /**
  1262.      * Overrides Cloneable
  1263.      */
  1264.     public Object clone() {
  1265.         SimpleDateFormat other = (SimpleDateFormat) super.clone();
  1266.         other.formatData = (DateFormatSymbols) formatData.clone();
  1267.         return other;
  1268.     }
  1269.  
  1270.     /**
  1271.      * Override hashCode.
  1272.      * Generates the hash code for the SimpleDateFormat object
  1273.      */
  1274.     public int hashCode()
  1275.     {
  1276.         return pattern.hashCode();
  1277.         // just enough fields for a reasonable distribution
  1278.     }
  1279.  
  1280.     /**
  1281.      * Override equals.
  1282.      */
  1283.     public boolean equals(Object obj)
  1284.     {
  1285.         if (!super.equals(obj)) return false; // super does class check
  1286.         SimpleDateFormat that = (SimpleDateFormat) obj;
  1287.         return (pattern.equals(that.pattern)
  1288.                 && formatData.equals(that.formatData));
  1289.     }
  1290.  
  1291.     /**
  1292.      * Override readObject.
  1293.      */
  1294.     private void readObject(ObjectInputStream stream)
  1295.          throws IOException, ClassNotFoundException {
  1296.              stream.defaultReadObject();
  1297.              if (serialVersionOnStream < 1) {
  1298.                  // didn't have defaultCenturyStart field
  1299.                  initializeDefaultCentury();
  1300.              }
  1301.              else {
  1302.                  // fill in dependent transient field
  1303.                  parseAmbiguousDatesAsAfter(defaultCenturyStart);
  1304.              }
  1305.              serialVersionOnStream = currentSerialVersion;
  1306.     }
  1307. }
  1308.