home *** CD-ROM | disk | FTP | other *** search
/ Chip 1998 November / Chip_1998-11_cd.bin / tema / Cafe / main.bin / SimpleDateFormat.java < prev    next >
Text File  |  1997-10-01  |  51KB  |  1,240 lines

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