rt/emul/compact/src/main/java/java/text/SimpleDateFormat.java
branchjdk7-b147
changeset 1334 588d5bf7a560
child 1339 8cc04f85a683
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/rt/emul/compact/src/main/java/java/text/SimpleDateFormat.java	Thu Oct 03 15:40:35 2013 +0200
     1.3 @@ -0,0 +1,2350 @@
     1.4 +/*
     1.5 + * Copyright (c) 1996, 2011, Oracle and/or its affiliates. All rights reserved.
     1.6 + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
     1.7 + *
     1.8 + * This code is free software; you can redistribute it and/or modify it
     1.9 + * under the terms of the GNU General Public License version 2 only, as
    1.10 + * published by the Free Software Foundation.  Oracle designates this
    1.11 + * particular file as subject to the "Classpath" exception as provided
    1.12 + * by Oracle in the LICENSE file that accompanied this code.
    1.13 + *
    1.14 + * This code is distributed in the hope that it will be useful, but WITHOUT
    1.15 + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
    1.16 + * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
    1.17 + * version 2 for more details (a copy is included in the LICENSE file that
    1.18 + * accompanied this code).
    1.19 + *
    1.20 + * You should have received a copy of the GNU General Public License version
    1.21 + * 2 along with this work; if not, write to the Free Software Foundation,
    1.22 + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
    1.23 + *
    1.24 + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
    1.25 + * or visit www.oracle.com if you need additional information or have any
    1.26 + * questions.
    1.27 + */
    1.28 +
    1.29 +/*
    1.30 + * (C) Copyright Taligent, Inc. 1996 - All Rights Reserved
    1.31 + * (C) Copyright IBM Corp. 1996-1998 - All Rights Reserved
    1.32 + *
    1.33 + *   The original version of this source code and documentation is copyrighted
    1.34 + * and owned by Taligent, Inc., a wholly-owned subsidiary of IBM. These
    1.35 + * materials are provided under terms of a License Agreement between Taligent
    1.36 + * and Sun. This technology is protected by multiple US and International
    1.37 + * patents. This notice and attribution to Taligent may not be removed.
    1.38 + *   Taligent is a registered trademark of Taligent, Inc.
    1.39 + *
    1.40 + */
    1.41 +
    1.42 +package java.text;
    1.43 +
    1.44 +import java.io.IOException;
    1.45 +import java.io.InvalidObjectException;
    1.46 +import java.io.ObjectInputStream;
    1.47 +import java.util.Calendar;
    1.48 +import java.util.Date;
    1.49 +import java.util.GregorianCalendar;
    1.50 +import java.util.Locale;
    1.51 +import java.util.Map;
    1.52 +import java.util.MissingResourceException;
    1.53 +import java.util.ResourceBundle;
    1.54 +import java.util.SimpleTimeZone;
    1.55 +import java.util.TimeZone;
    1.56 +import java.util.concurrent.ConcurrentHashMap;
    1.57 +import java.util.concurrent.ConcurrentMap;
    1.58 +import sun.util.calendar.CalendarUtils;
    1.59 +import sun.util.calendar.ZoneInfoFile;
    1.60 +import sun.util.resources.LocaleData;
    1.61 +
    1.62 +import static java.text.DateFormatSymbols.*;
    1.63 +
    1.64 +/**
    1.65 + * <code>SimpleDateFormat</code> is a concrete class for formatting and
    1.66 + * parsing dates in a locale-sensitive manner. It allows for formatting
    1.67 + * (date -> text), parsing (text -> date), and normalization.
    1.68 + *
    1.69 + * <p>
    1.70 + * <code>SimpleDateFormat</code> allows you to start by choosing
    1.71 + * any user-defined patterns for date-time formatting. However, you
    1.72 + * are encouraged to create a date-time formatter with either
    1.73 + * <code>getTimeInstance</code>, <code>getDateInstance</code>, or
    1.74 + * <code>getDateTimeInstance</code> in <code>DateFormat</code>. Each
    1.75 + * of these class methods can return a date/time formatter initialized
    1.76 + * with a default format pattern. You may modify the format pattern
    1.77 + * using the <code>applyPattern</code> methods as desired.
    1.78 + * For more information on using these methods, see
    1.79 + * {@link DateFormat}.
    1.80 + *
    1.81 + * <h4>Date and Time Patterns</h4>
    1.82 + * <p>
    1.83 + * Date and time formats are specified by <em>date and time pattern</em>
    1.84 + * strings.
    1.85 + * Within date and time pattern strings, unquoted letters from
    1.86 + * <code>'A'</code> to <code>'Z'</code> and from <code>'a'</code> to
    1.87 + * <code>'z'</code> are interpreted as pattern letters representing the
    1.88 + * components of a date or time string.
    1.89 + * Text can be quoted using single quotes (<code>'</code>) to avoid
    1.90 + * interpretation.
    1.91 + * <code>"''"</code> represents a single quote.
    1.92 + * All other characters are not interpreted; they're simply copied into the
    1.93 + * output string during formatting or matched against the input string
    1.94 + * during parsing.
    1.95 + * <p>
    1.96 + * The following pattern letters are defined (all other characters from
    1.97 + * <code>'A'</code> to <code>'Z'</code> and from <code>'a'</code> to
    1.98 + * <code>'z'</code> are reserved):
    1.99 + * <blockquote>
   1.100 + * <table border=0 cellspacing=3 cellpadding=0 summary="Chart shows pattern letters, date/time component, presentation, and examples.">
   1.101 + *     <tr bgcolor="#ccccff">
   1.102 + *         <th align=left>Letter
   1.103 + *         <th align=left>Date or Time Component
   1.104 + *         <th align=left>Presentation
   1.105 + *         <th align=left>Examples
   1.106 + *     <tr>
   1.107 + *         <td><code>G</code>
   1.108 + *         <td>Era designator
   1.109 + *         <td><a href="#text">Text</a>
   1.110 + *         <td><code>AD</code>
   1.111 + *     <tr bgcolor="#eeeeff">
   1.112 + *         <td><code>y</code>
   1.113 + *         <td>Year
   1.114 + *         <td><a href="#year">Year</a>
   1.115 + *         <td><code>1996</code>; <code>96</code>
   1.116 + *     <tr>
   1.117 + *         <td><code>Y</code>
   1.118 + *         <td>Week year
   1.119 + *         <td><a href="#year">Year</a>
   1.120 + *         <td><code>2009</code>; <code>09</code>
   1.121 + *     <tr bgcolor="#eeeeff">
   1.122 + *         <td><code>M</code>
   1.123 + *         <td>Month in year
   1.124 + *         <td><a href="#month">Month</a>
   1.125 + *         <td><code>July</code>; <code>Jul</code>; <code>07</code>
   1.126 + *     <tr>
   1.127 + *         <td><code>w</code>
   1.128 + *         <td>Week in year
   1.129 + *         <td><a href="#number">Number</a>
   1.130 + *         <td><code>27</code>
   1.131 + *     <tr bgcolor="#eeeeff">
   1.132 + *         <td><code>W</code>
   1.133 + *         <td>Week in month
   1.134 + *         <td><a href="#number">Number</a>
   1.135 + *         <td><code>2</code>
   1.136 + *     <tr>
   1.137 + *         <td><code>D</code>
   1.138 + *         <td>Day in year
   1.139 + *         <td><a href="#number">Number</a>
   1.140 + *         <td><code>189</code>
   1.141 + *     <tr bgcolor="#eeeeff">
   1.142 + *         <td><code>d</code>
   1.143 + *         <td>Day in month
   1.144 + *         <td><a href="#number">Number</a>
   1.145 + *         <td><code>10</code>
   1.146 + *     <tr>
   1.147 + *         <td><code>F</code>
   1.148 + *         <td>Day of week in month
   1.149 + *         <td><a href="#number">Number</a>
   1.150 + *         <td><code>2</code>
   1.151 + *     <tr bgcolor="#eeeeff">
   1.152 + *         <td><code>E</code>
   1.153 + *         <td>Day name in week
   1.154 + *         <td><a href="#text">Text</a>
   1.155 + *         <td><code>Tuesday</code>; <code>Tue</code>
   1.156 + *     <tr>
   1.157 + *         <td><code>u</code>
   1.158 + *         <td>Day number of week (1 = Monday, ..., 7 = Sunday)
   1.159 + *         <td><a href="#number">Number</a>
   1.160 + *         <td><code>1</code>
   1.161 + *     <tr bgcolor="#eeeeff">
   1.162 + *         <td><code>a</code>
   1.163 + *         <td>Am/pm marker
   1.164 + *         <td><a href="#text">Text</a>
   1.165 + *         <td><code>PM</code>
   1.166 + *     <tr>
   1.167 + *         <td><code>H</code>
   1.168 + *         <td>Hour in day (0-23)
   1.169 + *         <td><a href="#number">Number</a>
   1.170 + *         <td><code>0</code>
   1.171 + *     <tr bgcolor="#eeeeff">
   1.172 + *         <td><code>k</code>
   1.173 + *         <td>Hour in day (1-24)
   1.174 + *         <td><a href="#number">Number</a>
   1.175 + *         <td><code>24</code>
   1.176 + *     <tr>
   1.177 + *         <td><code>K</code>
   1.178 + *         <td>Hour in am/pm (0-11)
   1.179 + *         <td><a href="#number">Number</a>
   1.180 + *         <td><code>0</code>
   1.181 + *     <tr bgcolor="#eeeeff">
   1.182 + *         <td><code>h</code>
   1.183 + *         <td>Hour in am/pm (1-12)
   1.184 + *         <td><a href="#number">Number</a>
   1.185 + *         <td><code>12</code>
   1.186 + *     <tr>
   1.187 + *         <td><code>m</code>
   1.188 + *         <td>Minute in hour
   1.189 + *         <td><a href="#number">Number</a>
   1.190 + *         <td><code>30</code>
   1.191 + *     <tr bgcolor="#eeeeff">
   1.192 + *         <td><code>s</code>
   1.193 + *         <td>Second in minute
   1.194 + *         <td><a href="#number">Number</a>
   1.195 + *         <td><code>55</code>
   1.196 + *     <tr>
   1.197 + *         <td><code>S</code>
   1.198 + *         <td>Millisecond
   1.199 + *         <td><a href="#number">Number</a>
   1.200 + *         <td><code>978</code>
   1.201 + *     <tr bgcolor="#eeeeff">
   1.202 + *         <td><code>z</code>
   1.203 + *         <td>Time zone
   1.204 + *         <td><a href="#timezone">General time zone</a>
   1.205 + *         <td><code>Pacific Standard Time</code>; <code>PST</code>; <code>GMT-08:00</code>
   1.206 + *     <tr>
   1.207 + *         <td><code>Z</code>
   1.208 + *         <td>Time zone
   1.209 + *         <td><a href="#rfc822timezone">RFC 822 time zone</a>
   1.210 + *         <td><code>-0800</code>
   1.211 + *     <tr bgcolor="#eeeeff">
   1.212 + *         <td><code>X</code>
   1.213 + *         <td>Time zone
   1.214 + *         <td><a href="#iso8601timezone">ISO 8601 time zone</a>
   1.215 + *         <td><code>-08</code>; <code>-0800</code>;  <code>-08:00</code>
   1.216 + * </table>
   1.217 + * </blockquote>
   1.218 + * Pattern letters are usually repeated, as their number determines the
   1.219 + * exact presentation:
   1.220 + * <ul>
   1.221 + * <li><strong><a name="text">Text:</a></strong>
   1.222 + *     For formatting, if the number of pattern letters is 4 or more,
   1.223 + *     the full form is used; otherwise a short or abbreviated form
   1.224 + *     is used if available.
   1.225 + *     For parsing, both forms are accepted, independent of the number
   1.226 + *     of pattern letters.<br><br></li>
   1.227 + * <li><strong><a name="number">Number:</a></strong>
   1.228 + *     For formatting, the number of pattern letters is the minimum
   1.229 + *     number of digits, and shorter numbers are zero-padded to this amount.
   1.230 + *     For parsing, the number of pattern letters is ignored unless
   1.231 + *     it's needed to separate two adjacent fields.<br><br></li>
   1.232 + * <li><strong><a name="year">Year:</a></strong>
   1.233 + *     If the formatter's {@link #getCalendar() Calendar} is the Gregorian
   1.234 + *     calendar, the following rules are applied.<br>
   1.235 + *     <ul>
   1.236 + *     <li>For formatting, if the number of pattern letters is 2, the year
   1.237 + *         is truncated to 2 digits; otherwise it is interpreted as a
   1.238 + *         <a href="#number">number</a>.
   1.239 + *     <li>For parsing, if the number of pattern letters is more than 2,
   1.240 + *         the year is interpreted literally, regardless of the number of
   1.241 + *         digits. So using the pattern "MM/dd/yyyy", "01/11/12" parses to
   1.242 + *         Jan 11, 12 A.D.
   1.243 + *     <li>For parsing with the abbreviated year pattern ("y" or "yy"),
   1.244 + *         <code>SimpleDateFormat</code> must interpret the abbreviated year
   1.245 + *         relative to some century.  It does this by adjusting dates to be
   1.246 + *         within 80 years before and 20 years after the time the <code>SimpleDateFormat</code>
   1.247 + *         instance is created. For example, using a pattern of "MM/dd/yy" and a
   1.248 + *         <code>SimpleDateFormat</code> instance created on Jan 1, 1997,  the string
   1.249 + *         "01/11/12" would be interpreted as Jan 11, 2012 while the string "05/04/64"
   1.250 + *         would be interpreted as May 4, 1964.
   1.251 + *         During parsing, only strings consisting of exactly two digits, as defined by
   1.252 + *         {@link Character#isDigit(char)}, will be parsed into the default century.
   1.253 + *         Any other numeric string, such as a one digit string, a three or more digit
   1.254 + *         string, or a two digit string that isn't all digits (for example, "-1"), is
   1.255 + *         interpreted literally.  So "01/02/3" or "01/02/003" are parsed, using the
   1.256 + *         same pattern, as Jan 2, 3 AD.  Likewise, "01/02/-3" is parsed as Jan 2, 4 BC.
   1.257 + *     </ul>
   1.258 + *     Otherwise, calendar system specific forms are applied.
   1.259 + *     For both formatting and parsing, if the number of pattern
   1.260 + *     letters is 4 or more, a calendar specific {@linkplain
   1.261 + *     Calendar#LONG long form} is used. Otherwise, a calendar
   1.262 + *     specific {@linkplain Calendar#SHORT short or abbreviated form}
   1.263 + *     is used.<br>
   1.264 + *     <br>
   1.265 + *     If week year {@code 'Y'} is specified and the {@linkplain
   1.266 + *     #getCalendar() calendar} doesn't support any <a
   1.267 + *     href="../util/GregorianCalendar.html#week_year"> week
   1.268 + *     years</a>, the calendar year ({@code 'y'}) is used instead. The
   1.269 + *     support of week years can be tested with a call to {@link
   1.270 + *     DateFormat#getCalendar() getCalendar()}.{@link
   1.271 + *     java.util.Calendar#isWeekDateSupported()
   1.272 + *     isWeekDateSupported()}.<br><br></li>
   1.273 + * <li><strong><a name="month">Month:</a></strong>
   1.274 + *     If the number of pattern letters is 3 or more, the month is
   1.275 + *     interpreted as <a href="#text">text</a>; otherwise,
   1.276 + *     it is interpreted as a <a href="#number">number</a>.<br><br></li>
   1.277 + * <li><strong><a name="timezone">General time zone:</a></strong>
   1.278 + *     Time zones are interpreted as <a href="#text">text</a> if they have
   1.279 + *     names. For time zones representing a GMT offset value, the
   1.280 + *     following syntax is used:
   1.281 + *     <pre>
   1.282 + *     <a name="GMTOffsetTimeZone"><i>GMTOffsetTimeZone:</i></a>
   1.283 + *             <code>GMT</code> <i>Sign</i> <i>Hours</i> <code>:</code> <i>Minutes</i>
   1.284 + *     <i>Sign:</i> one of
   1.285 + *             <code>+ -</code>
   1.286 + *     <i>Hours:</i>
   1.287 + *             <i>Digit</i>
   1.288 + *             <i>Digit</i> <i>Digit</i>
   1.289 + *     <i>Minutes:</i>
   1.290 + *             <i>Digit</i> <i>Digit</i>
   1.291 + *     <i>Digit:</i> one of
   1.292 + *             <code>0 1 2 3 4 5 6 7 8 9</code></pre>
   1.293 + *     <i>Hours</i> must be between 0 and 23, and <i>Minutes</i> must be between
   1.294 + *     00 and 59. The format is locale independent and digits must be taken
   1.295 + *     from the Basic Latin block of the Unicode standard.
   1.296 + *     <p>For parsing, <a href="#rfc822timezone">RFC 822 time zones</a> are also
   1.297 + *     accepted.<br><br></li>
   1.298 + * <li><strong><a name="rfc822timezone">RFC 822 time zone:</a></strong>
   1.299 + *     For formatting, the RFC 822 4-digit time zone format is used:
   1.300 + *
   1.301 + *     <pre>
   1.302 + *     <i>RFC822TimeZone:</i>
   1.303 + *             <i>Sign</i> <i>TwoDigitHours</i> <i>Minutes</i>
   1.304 + *     <i>TwoDigitHours:</i>
   1.305 + *             <i>Digit Digit</i></pre>
   1.306 + *     <i>TwoDigitHours</i> must be between 00 and 23. Other definitions
   1.307 + *     are as for <a href="#timezone">general time zones</a>.
   1.308 + *
   1.309 + *     <p>For parsing, <a href="#timezone">general time zones</a> are also
   1.310 + *     accepted.
   1.311 + * <li><strong><a name="iso8601timezone">ISO 8601 Time zone:</a></strong>
   1.312 + *     The number of pattern letters designates the format for both formatting
   1.313 + *     and parsing as follows:
   1.314 + *     <pre>
   1.315 + *     <i>ISO8601TimeZone:</i>
   1.316 + *             <i>OneLetterISO8601TimeZone</i>
   1.317 + *             <i>TwoLetterISO8601TimeZone</i>
   1.318 + *             <i>ThreeLetterISO8601TimeZone</i>
   1.319 + *     <i>OneLetterISO8601TimeZone:</i>
   1.320 + *             <i>Sign</i> <i>TwoDigitHours</i>
   1.321 + *             {@code Z}
   1.322 + *     <i>TwoLetterISO8601TimeZone:</i>
   1.323 + *             <i>Sign</i> <i>TwoDigitHours</i> <i>Minutes</i>
   1.324 + *             {@code Z}
   1.325 + *     <i>ThreeLetterISO8601TimeZone:</i>
   1.326 + *             <i>Sign</i> <i>TwoDigitHours</i> {@code :} <i>Minutes</i>
   1.327 + *             {@code Z}</pre>
   1.328 + *     Other definitions are as for <a href="#timezone">general time zones</a> or
   1.329 + *     <a href="#rfc822timezone">RFC 822 time zones</a>.
   1.330 + *
   1.331 + *     <p>For formatting, if the offset value from GMT is 0, {@code "Z"} is
   1.332 + *     produced. If the number of pattern letters is 1, any fraction of an hour
   1.333 + *     is ignored. For example, if the pattern is {@code "X"} and the time zone is
   1.334 + *     {@code "GMT+05:30"}, {@code "+05"} is produced.
   1.335 + *
   1.336 + *     <p>For parsing, {@code "Z"} is parsed as the UTC time zone designator.
   1.337 + *     <a href="#timezone">General time zones</a> are <em>not</em> accepted.
   1.338 + *
   1.339 + *     <p>If the number of pattern letters is 4 or more, {@link
   1.340 + *     IllegalArgumentException} is thrown when constructing a {@code
   1.341 + *     SimpleDateFormat} or {@linkplain #applyPattern(String) applying a
   1.342 + *     pattern}.
   1.343 + * </ul>
   1.344 + * <code>SimpleDateFormat</code> also supports <em>localized date and time
   1.345 + * pattern</em> strings. In these strings, the pattern letters described above
   1.346 + * may be replaced with other, locale dependent, pattern letters.
   1.347 + * <code>SimpleDateFormat</code> does not deal with the localization of text
   1.348 + * other than the pattern letters; that's up to the client of the class.
   1.349 + * <p>
   1.350 + *
   1.351 + * <h4>Examples</h4>
   1.352 + *
   1.353 + * The following examples show how date and time patterns are interpreted in
   1.354 + * the U.S. locale. The given date and time are 2001-07-04 12:08:56 local time
   1.355 + * in the U.S. Pacific Time time zone.
   1.356 + * <blockquote>
   1.357 + * <table border=0 cellspacing=3 cellpadding=0 summary="Examples of date and time patterns interpreted in the U.S. locale">
   1.358 + *     <tr bgcolor="#ccccff">
   1.359 + *         <th align=left>Date and Time Pattern
   1.360 + *         <th align=left>Result
   1.361 + *     <tr>
   1.362 + *         <td><code>"yyyy.MM.dd G 'at' HH:mm:ss z"</code>
   1.363 + *         <td><code>2001.07.04 AD at 12:08:56 PDT</code>
   1.364 + *     <tr bgcolor="#eeeeff">
   1.365 + *         <td><code>"EEE, MMM d, ''yy"</code>
   1.366 + *         <td><code>Wed, Jul 4, '01</code>
   1.367 + *     <tr>
   1.368 + *         <td><code>"h:mm a"</code>
   1.369 + *         <td><code>12:08 PM</code>
   1.370 + *     <tr bgcolor="#eeeeff">
   1.371 + *         <td><code>"hh 'o''clock' a, zzzz"</code>
   1.372 + *         <td><code>12 o'clock PM, Pacific Daylight Time</code>
   1.373 + *     <tr>
   1.374 + *         <td><code>"K:mm a, z"</code>
   1.375 + *         <td><code>0:08 PM, PDT</code>
   1.376 + *     <tr bgcolor="#eeeeff">
   1.377 + *         <td><code>"yyyyy.MMMMM.dd GGG hh:mm aaa"</code>
   1.378 + *         <td><code>02001.July.04 AD 12:08 PM</code>
   1.379 + *     <tr>
   1.380 + *         <td><code>"EEE, d MMM yyyy HH:mm:ss Z"</code>
   1.381 + *         <td><code>Wed, 4 Jul 2001 12:08:56 -0700</code>
   1.382 + *     <tr bgcolor="#eeeeff">
   1.383 + *         <td><code>"yyMMddHHmmssZ"</code>
   1.384 + *         <td><code>010704120856-0700</code>
   1.385 + *     <tr>
   1.386 + *         <td><code>"yyyy-MM-dd'T'HH:mm:ss.SSSZ"</code>
   1.387 + *         <td><code>2001-07-04T12:08:56.235-0700</code>
   1.388 + *     <tr bgcolor="#eeeeff">
   1.389 + *         <td><code>"yyyy-MM-dd'T'HH:mm:ss.SSSXXX"</code>
   1.390 + *         <td><code>2001-07-04T12:08:56.235-07:00</code>
   1.391 + *     <tr>
   1.392 + *         <td><code>"YYYY-'W'ww-u"</code>
   1.393 + *         <td><code>2001-W27-3</code>
   1.394 + * </table>
   1.395 + * </blockquote>
   1.396 + *
   1.397 + * <h4><a name="synchronization">Synchronization</a></h4>
   1.398 + *
   1.399 + * <p>
   1.400 + * Date formats are not synchronized.
   1.401 + * It is recommended to create separate format instances for each thread.
   1.402 + * If multiple threads access a format concurrently, it must be synchronized
   1.403 + * externally.
   1.404 + *
   1.405 + * @see          <a href="http://java.sun.com/docs/books/tutorial/i18n/format/simpleDateFormat.html">Java Tutorial</a>
   1.406 + * @see          java.util.Calendar
   1.407 + * @see          java.util.TimeZone
   1.408 + * @see          DateFormat
   1.409 + * @see          DateFormatSymbols
   1.410 + * @author       Mark Davis, Chen-Lieh Huang, Alan Liu
   1.411 + */
   1.412 +public class SimpleDateFormat extends DateFormat {
   1.413 +
   1.414 +    // the official serial version ID which says cryptically
   1.415 +    // which version we're compatible with
   1.416 +    static final long serialVersionUID = 4774881970558875024L;
   1.417 +
   1.418 +    // the internal serial version which says which version was written
   1.419 +    // - 0 (default) for version up to JDK 1.1.3
   1.420 +    // - 1 for version from JDK 1.1.4, which includes a new field
   1.421 +    static final int currentSerialVersion = 1;
   1.422 +
   1.423 +    /**
   1.424 +     * The version of the serialized data on the stream.  Possible values:
   1.425 +     * <ul>
   1.426 +     * <li><b>0</b> or not present on stream: JDK 1.1.3.  This version
   1.427 +     * has no <code>defaultCenturyStart</code> on stream.
   1.428 +     * <li><b>1</b> JDK 1.1.4 or later.  This version adds
   1.429 +     * <code>defaultCenturyStart</code>.
   1.430 +     * </ul>
   1.431 +     * When streaming out this class, the most recent format
   1.432 +     * and the highest allowable <code>serialVersionOnStream</code>
   1.433 +     * is written.
   1.434 +     * @serial
   1.435 +     * @since JDK1.1.4
   1.436 +     */
   1.437 +    private int serialVersionOnStream = currentSerialVersion;
   1.438 +
   1.439 +    /**
   1.440 +     * The pattern string of this formatter.  This is always a non-localized
   1.441 +     * pattern.  May not be null.  See class documentation for details.
   1.442 +     * @serial
   1.443 +     */
   1.444 +    private String pattern;
   1.445 +
   1.446 +    /**
   1.447 +     * Saved numberFormat and pattern.
   1.448 +     * @see SimpleDateFormat#checkNegativeNumberExpression
   1.449 +     */
   1.450 +    transient private NumberFormat originalNumberFormat;
   1.451 +    transient private String originalNumberPattern;
   1.452 +
   1.453 +    /**
   1.454 +     * The minus sign to be used with format and parse.
   1.455 +     */
   1.456 +    transient private char minusSign = '-';
   1.457 +
   1.458 +    /**
   1.459 +     * True when a negative sign follows a number.
   1.460 +     * (True as default in Arabic.)
   1.461 +     */
   1.462 +    transient private boolean hasFollowingMinusSign = false;
   1.463 +
   1.464 +    /**
   1.465 +     * The compiled pattern.
   1.466 +     */
   1.467 +    transient private char[] compiledPattern;
   1.468 +
   1.469 +    /**
   1.470 +     * Tags for the compiled pattern.
   1.471 +     */
   1.472 +    private final static int TAG_QUOTE_ASCII_CHAR       = 100;
   1.473 +    private final static int TAG_QUOTE_CHARS            = 101;
   1.474 +
   1.475 +    /**
   1.476 +     * Locale dependent digit zero.
   1.477 +     * @see #zeroPaddingNumber
   1.478 +     * @see java.text.DecimalFormatSymbols#getZeroDigit
   1.479 +     */
   1.480 +    transient private char zeroDigit;
   1.481 +
   1.482 +    /**
   1.483 +     * The symbols used by this formatter for week names, month names,
   1.484 +     * etc.  May not be null.
   1.485 +     * @serial
   1.486 +     * @see java.text.DateFormatSymbols
   1.487 +     */
   1.488 +    private DateFormatSymbols formatData;
   1.489 +
   1.490 +    /**
   1.491 +     * We map dates with two-digit years into the century starting at
   1.492 +     * <code>defaultCenturyStart</code>, which may be any date.  May
   1.493 +     * not be null.
   1.494 +     * @serial
   1.495 +     * @since JDK1.1.4
   1.496 +     */
   1.497 +    private Date defaultCenturyStart;
   1.498 +
   1.499 +    transient private int defaultCenturyStartYear;
   1.500 +
   1.501 +    private static final int MILLIS_PER_MINUTE = 60 * 1000;
   1.502 +
   1.503 +    // For time zones that have no names, use strings GMT+minutes and
   1.504 +    // GMT-minutes. For instance, in France the time zone is GMT+60.
   1.505 +    private static final String GMT = "GMT";
   1.506 +
   1.507 +    /**
   1.508 +     * Cache to hold the DateTimePatterns of a Locale.
   1.509 +     */
   1.510 +    private static final ConcurrentMap<Locale, String[]> cachedLocaleData
   1.511 +        = new ConcurrentHashMap<Locale, String[]>(3);
   1.512 +
   1.513 +    /**
   1.514 +     * Cache NumberFormat instances with Locale key.
   1.515 +     */
   1.516 +    private static final ConcurrentMap<Locale, NumberFormat> cachedNumberFormatData
   1.517 +        = new ConcurrentHashMap<Locale, NumberFormat>(3);
   1.518 +
   1.519 +    /**
   1.520 +     * The Locale used to instantiate this
   1.521 +     * <code>SimpleDateFormat</code>. The value may be null if this object
   1.522 +     * has been created by an older <code>SimpleDateFormat</code> and
   1.523 +     * deserialized.
   1.524 +     *
   1.525 +     * @serial
   1.526 +     * @since 1.6
   1.527 +     */
   1.528 +    private Locale locale;
   1.529 +
   1.530 +    /**
   1.531 +     * Indicates whether this <code>SimpleDateFormat</code> should use
   1.532 +     * the DateFormatSymbols. If true, the format and parse methods
   1.533 +     * use the DateFormatSymbols values. If false, the format and
   1.534 +     * parse methods call Calendar.getDisplayName or
   1.535 +     * Calendar.getDisplayNames.
   1.536 +     */
   1.537 +    transient boolean useDateFormatSymbols;
   1.538 +
   1.539 +    /**
   1.540 +     * Constructs a <code>SimpleDateFormat</code> using the default pattern and
   1.541 +     * date format symbols for the default locale.
   1.542 +     * <b>Note:</b> This constructor may not support all locales.
   1.543 +     * For full coverage, use the factory methods in the {@link DateFormat}
   1.544 +     * class.
   1.545 +     */
   1.546 +    public SimpleDateFormat() {
   1.547 +        this(SHORT, SHORT, Locale.getDefault(Locale.Category.FORMAT));
   1.548 +    }
   1.549 +
   1.550 +    /**
   1.551 +     * Constructs a <code>SimpleDateFormat</code> using the given pattern and
   1.552 +     * the default date format symbols for the default locale.
   1.553 +     * <b>Note:</b> This constructor may not support all locales.
   1.554 +     * For full coverage, use the factory methods in the {@link DateFormat}
   1.555 +     * class.
   1.556 +     *
   1.557 +     * @param pattern the pattern describing the date and time format
   1.558 +     * @exception NullPointerException if the given pattern is null
   1.559 +     * @exception IllegalArgumentException if the given pattern is invalid
   1.560 +     */
   1.561 +    public SimpleDateFormat(String pattern)
   1.562 +    {
   1.563 +        this(pattern, Locale.getDefault(Locale.Category.FORMAT));
   1.564 +    }
   1.565 +
   1.566 +    /**
   1.567 +     * Constructs a <code>SimpleDateFormat</code> using the given pattern and
   1.568 +     * the default date format symbols for the given locale.
   1.569 +     * <b>Note:</b> This constructor may not support all locales.
   1.570 +     * For full coverage, use the factory methods in the {@link DateFormat}
   1.571 +     * class.
   1.572 +     *
   1.573 +     * @param pattern the pattern describing the date and time format
   1.574 +     * @param locale the locale whose date format symbols should be used
   1.575 +     * @exception NullPointerException if the given pattern or locale is null
   1.576 +     * @exception IllegalArgumentException if the given pattern is invalid
   1.577 +     */
   1.578 +    public SimpleDateFormat(String pattern, Locale locale)
   1.579 +    {
   1.580 +        if (pattern == null || locale == null) {
   1.581 +            throw new NullPointerException();
   1.582 +        }
   1.583 +
   1.584 +        initializeCalendar(locale);
   1.585 +        this.pattern = pattern;
   1.586 +        this.formatData = DateFormatSymbols.getInstanceRef(locale);
   1.587 +        this.locale = locale;
   1.588 +        initialize(locale);
   1.589 +    }
   1.590 +
   1.591 +    /**
   1.592 +     * Constructs a <code>SimpleDateFormat</code> using the given pattern and
   1.593 +     * date format symbols.
   1.594 +     *
   1.595 +     * @param pattern the pattern describing the date and time format
   1.596 +     * @param formatSymbols the date format symbols to be used for formatting
   1.597 +     * @exception NullPointerException if the given pattern or formatSymbols is null
   1.598 +     * @exception IllegalArgumentException if the given pattern is invalid
   1.599 +     */
   1.600 +    public SimpleDateFormat(String pattern, DateFormatSymbols formatSymbols)
   1.601 +    {
   1.602 +        if (pattern == null || formatSymbols == null) {
   1.603 +            throw new NullPointerException();
   1.604 +        }
   1.605 +
   1.606 +        this.pattern = pattern;
   1.607 +        this.formatData = (DateFormatSymbols) formatSymbols.clone();
   1.608 +        this.locale = Locale.getDefault(Locale.Category.FORMAT);
   1.609 +        initializeCalendar(this.locale);
   1.610 +        initialize(this.locale);
   1.611 +        useDateFormatSymbols = true;
   1.612 +    }
   1.613 +
   1.614 +    /* Package-private, called by DateFormat factory methods */
   1.615 +    SimpleDateFormat(int timeStyle, int dateStyle, Locale loc) {
   1.616 +        if (loc == null) {
   1.617 +            throw new NullPointerException();
   1.618 +        }
   1.619 +
   1.620 +        this.locale = loc;
   1.621 +        // initialize calendar and related fields
   1.622 +        initializeCalendar(loc);
   1.623 +
   1.624 +        /* try the cache first */
   1.625 +        String[] dateTimePatterns = cachedLocaleData.get(loc);
   1.626 +        if (dateTimePatterns == null) { /* cache miss */
   1.627 +            ResourceBundle r = LocaleData.getDateFormatData(loc);
   1.628 +            if (!isGregorianCalendar()) {
   1.629 +                try {
   1.630 +                    dateTimePatterns = r.getStringArray(getCalendarName() + ".DateTimePatterns");
   1.631 +                } catch (MissingResourceException e) {
   1.632 +                }
   1.633 +            }
   1.634 +            if (dateTimePatterns == null) {
   1.635 +                dateTimePatterns = r.getStringArray("DateTimePatterns");
   1.636 +            }
   1.637 +            /* update cache */
   1.638 +            cachedLocaleData.putIfAbsent(loc, dateTimePatterns);
   1.639 +        }
   1.640 +        formatData = DateFormatSymbols.getInstanceRef(loc);
   1.641 +        if ((timeStyle >= 0) && (dateStyle >= 0)) {
   1.642 +            Object[] dateTimeArgs = {dateTimePatterns[timeStyle],
   1.643 +                                     dateTimePatterns[dateStyle + 4]};
   1.644 +            pattern = MessageFormat.format(dateTimePatterns[8], dateTimeArgs);
   1.645 +        }
   1.646 +        else if (timeStyle >= 0) {
   1.647 +            pattern = dateTimePatterns[timeStyle];
   1.648 +        }
   1.649 +        else if (dateStyle >= 0) {
   1.650 +            pattern = dateTimePatterns[dateStyle + 4];
   1.651 +        }
   1.652 +        else {
   1.653 +            throw new IllegalArgumentException("No date or time style specified");
   1.654 +        }
   1.655 +
   1.656 +        initialize(loc);
   1.657 +    }
   1.658 +
   1.659 +    /* Initialize compiledPattern and numberFormat fields */
   1.660 +    private void initialize(Locale loc) {
   1.661 +        // Verify and compile the given pattern.
   1.662 +        compiledPattern = compile(pattern);
   1.663 +
   1.664 +        /* try the cache first */
   1.665 +        numberFormat = cachedNumberFormatData.get(loc);
   1.666 +        if (numberFormat == null) { /* cache miss */
   1.667 +            numberFormat = NumberFormat.getIntegerInstance(loc);
   1.668 +            numberFormat.setGroupingUsed(false);
   1.669 +
   1.670 +            /* update cache */
   1.671 +            cachedNumberFormatData.putIfAbsent(loc, numberFormat);
   1.672 +        }
   1.673 +        numberFormat = (NumberFormat) numberFormat.clone();
   1.674 +
   1.675 +        initializeDefaultCentury();
   1.676 +    }
   1.677 +
   1.678 +    private void initializeCalendar(Locale loc) {
   1.679 +        if (calendar == null) {
   1.680 +            assert loc != null;
   1.681 +            // The format object must be constructed using the symbols for this zone.
   1.682 +            // However, the calendar should use the current default TimeZone.
   1.683 +            // If this is not contained in the locale zone strings, then the zone
   1.684 +            // will be formatted using generic GMT+/-H:MM nomenclature.
   1.685 +            calendar = Calendar.getInstance(TimeZone.getDefault(), loc);
   1.686 +        }
   1.687 +    }
   1.688 +
   1.689 +    /**
   1.690 +     * Returns the compiled form of the given pattern. The syntax of
   1.691 +     * the compiled pattern is:
   1.692 +     * <blockquote>
   1.693 +     * CompiledPattern:
   1.694 +     *     EntryList
   1.695 +     * EntryList:
   1.696 +     *     Entry
   1.697 +     *     EntryList Entry
   1.698 +     * Entry:
   1.699 +     *     TagField
   1.700 +     *     TagField data
   1.701 +     * TagField:
   1.702 +     *     Tag Length
   1.703 +     *     TaggedData
   1.704 +     * Tag:
   1.705 +     *     pattern_char_index
   1.706 +     *     TAG_QUOTE_CHARS
   1.707 +     * Length:
   1.708 +     *     short_length
   1.709 +     *     long_length
   1.710 +     * TaggedData:
   1.711 +     *     TAG_QUOTE_ASCII_CHAR ascii_char
   1.712 +     *
   1.713 +     * </blockquote>
   1.714 +     *
   1.715 +     * where `short_length' is an 8-bit unsigned integer between 0 and
   1.716 +     * 254.  `long_length' is a sequence of an 8-bit integer 255 and a
   1.717 +     * 32-bit signed integer value which is split into upper and lower
   1.718 +     * 16-bit fields in two char's. `pattern_char_index' is an 8-bit
   1.719 +     * integer between 0 and 18. `ascii_char' is an 7-bit ASCII
   1.720 +     * character value. `data' depends on its Tag value.
   1.721 +     * <p>
   1.722 +     * If Length is short_length, Tag and short_length are packed in a
   1.723 +     * single char, as illustrated below.
   1.724 +     * <blockquote>
   1.725 +     *     char[0] = (Tag << 8) | short_length;
   1.726 +     * </blockquote>
   1.727 +     *
   1.728 +     * If Length is long_length, Tag and 255 are packed in the first
   1.729 +     * char and a 32-bit integer, as illustrated below.
   1.730 +     * <blockquote>
   1.731 +     *     char[0] = (Tag << 8) | 255;
   1.732 +     *     char[1] = (char) (long_length >>> 16);
   1.733 +     *     char[2] = (char) (long_length & 0xffff);
   1.734 +     * </blockquote>
   1.735 +     * <p>
   1.736 +     * If Tag is a pattern_char_index, its Length is the number of
   1.737 +     * pattern characters. For example, if the given pattern is
   1.738 +     * "yyyy", Tag is 1 and Length is 4, followed by no data.
   1.739 +     * <p>
   1.740 +     * If Tag is TAG_QUOTE_CHARS, its Length is the number of char's
   1.741 +     * following the TagField. For example, if the given pattern is
   1.742 +     * "'o''clock'", Length is 7 followed by a char sequence of
   1.743 +     * <code>o&nbs;'&nbs;c&nbs;l&nbs;o&nbs;c&nbs;k</code>.
   1.744 +     * <p>
   1.745 +     * TAG_QUOTE_ASCII_CHAR is a special tag and has an ASCII
   1.746 +     * character in place of Length. For example, if the given pattern
   1.747 +     * is "'o'", the TaggedData entry is
   1.748 +     * <code>((TAG_QUOTE_ASCII_CHAR&nbs;<<&nbs;8)&nbs;|&nbs;'o')</code>.
   1.749 +     *
   1.750 +     * @exception NullPointerException if the given pattern is null
   1.751 +     * @exception IllegalArgumentException if the given pattern is invalid
   1.752 +     */
   1.753 +    private char[] compile(String pattern) {
   1.754 +        int length = pattern.length();
   1.755 +        boolean inQuote = false;
   1.756 +        StringBuilder compiledPattern = new StringBuilder(length * 2);
   1.757 +        StringBuilder tmpBuffer = null;
   1.758 +        int count = 0;
   1.759 +        int lastTag = -1;
   1.760 +
   1.761 +        for (int i = 0; i < length; i++) {
   1.762 +            char c = pattern.charAt(i);
   1.763 +
   1.764 +            if (c == '\'') {
   1.765 +                // '' is treated as a single quote regardless of being
   1.766 +                // in a quoted section.
   1.767 +                if ((i + 1) < length) {
   1.768 +                    c = pattern.charAt(i + 1);
   1.769 +                    if (c == '\'') {
   1.770 +                        i++;
   1.771 +                        if (count != 0) {
   1.772 +                            encode(lastTag, count, compiledPattern);
   1.773 +                            lastTag = -1;
   1.774 +                            count = 0;
   1.775 +                        }
   1.776 +                        if (inQuote) {
   1.777 +                            tmpBuffer.append(c);
   1.778 +                        } else {
   1.779 +                            compiledPattern.append((char)(TAG_QUOTE_ASCII_CHAR << 8 | c));
   1.780 +                        }
   1.781 +                        continue;
   1.782 +                    }
   1.783 +                }
   1.784 +                if (!inQuote) {
   1.785 +                    if (count != 0) {
   1.786 +                        encode(lastTag, count, compiledPattern);
   1.787 +                        lastTag = -1;
   1.788 +                        count = 0;
   1.789 +                    }
   1.790 +                    if (tmpBuffer == null) {
   1.791 +                        tmpBuffer = new StringBuilder(length);
   1.792 +                    } else {
   1.793 +                        tmpBuffer.setLength(0);
   1.794 +                    }
   1.795 +                    inQuote = true;
   1.796 +                } else {
   1.797 +                    int len = tmpBuffer.length();
   1.798 +                    if (len == 1) {
   1.799 +                        char ch = tmpBuffer.charAt(0);
   1.800 +                        if (ch < 128) {
   1.801 +                            compiledPattern.append((char)(TAG_QUOTE_ASCII_CHAR << 8 | ch));
   1.802 +                        } else {
   1.803 +                            compiledPattern.append((char)(TAG_QUOTE_CHARS << 8 | 1));
   1.804 +                            compiledPattern.append(ch);
   1.805 +                        }
   1.806 +                    } else {
   1.807 +                        encode(TAG_QUOTE_CHARS, len, compiledPattern);
   1.808 +                        compiledPattern.append(tmpBuffer);
   1.809 +                    }
   1.810 +                    inQuote = false;
   1.811 +                }
   1.812 +                continue;
   1.813 +            }
   1.814 +            if (inQuote) {
   1.815 +                tmpBuffer.append(c);
   1.816 +                continue;
   1.817 +            }
   1.818 +            if (!(c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z')) {
   1.819 +                if (count != 0) {
   1.820 +                    encode(lastTag, count, compiledPattern);
   1.821 +                    lastTag = -1;
   1.822 +                    count = 0;
   1.823 +                }
   1.824 +                if (c < 128) {
   1.825 +                    // In most cases, c would be a delimiter, such as ':'.
   1.826 +                    compiledPattern.append((char)(TAG_QUOTE_ASCII_CHAR << 8 | c));
   1.827 +                } else {
   1.828 +                    // Take any contiguous non-ASCII alphabet characters and
   1.829 +                    // put them in a single TAG_QUOTE_CHARS.
   1.830 +                    int j;
   1.831 +                    for (j = i + 1; j < length; j++) {
   1.832 +                        char d = pattern.charAt(j);
   1.833 +                        if (d == '\'' || (d >= 'a' && d <= 'z' || d >= 'A' && d <= 'Z')) {
   1.834 +                            break;
   1.835 +                        }
   1.836 +                    }
   1.837 +                    compiledPattern.append((char)(TAG_QUOTE_CHARS << 8 | (j - i)));
   1.838 +                    for (; i < j; i++) {
   1.839 +                        compiledPattern.append(pattern.charAt(i));
   1.840 +                    }
   1.841 +                    i--;
   1.842 +                }
   1.843 +                continue;
   1.844 +            }
   1.845 +
   1.846 +            int tag;
   1.847 +            if ((tag = DateFormatSymbols.patternChars.indexOf(c)) == -1) {
   1.848 +                throw new IllegalArgumentException("Illegal pattern character " +
   1.849 +                                                   "'" + c + "'");
   1.850 +            }
   1.851 +            if (lastTag == -1 || lastTag == tag) {
   1.852 +                lastTag = tag;
   1.853 +                count++;
   1.854 +                continue;
   1.855 +            }
   1.856 +            encode(lastTag, count, compiledPattern);
   1.857 +            lastTag = tag;
   1.858 +            count = 1;
   1.859 +        }
   1.860 +
   1.861 +        if (inQuote) {
   1.862 +            throw new IllegalArgumentException("Unterminated quote");
   1.863 +        }
   1.864 +
   1.865 +        if (count != 0) {
   1.866 +            encode(lastTag, count, compiledPattern);
   1.867 +        }
   1.868 +
   1.869 +        // Copy the compiled pattern to a char array
   1.870 +        int len = compiledPattern.length();
   1.871 +        char[] r = new char[len];
   1.872 +        compiledPattern.getChars(0, len, r, 0);
   1.873 +        return r;
   1.874 +    }
   1.875 +
   1.876 +    /**
   1.877 +     * Encodes the given tag and length and puts encoded char(s) into buffer.
   1.878 +     */
   1.879 +    private static final void encode(int tag, int length, StringBuilder buffer) {
   1.880 +        if (tag == PATTERN_ISO_ZONE && length >= 4) {
   1.881 +            throw new IllegalArgumentException("invalid ISO 8601 format: length=" + length);
   1.882 +        }
   1.883 +        if (length < 255) {
   1.884 +            buffer.append((char)(tag << 8 | length));
   1.885 +        } else {
   1.886 +            buffer.append((char)((tag << 8) | 0xff));
   1.887 +            buffer.append((char)(length >>> 16));
   1.888 +            buffer.append((char)(length & 0xffff));
   1.889 +        }
   1.890 +    }
   1.891 +
   1.892 +    /* Initialize the fields we use to disambiguate ambiguous years. Separate
   1.893 +     * so we can call it from readObject().
   1.894 +     */
   1.895 +    private void initializeDefaultCentury() {
   1.896 +        calendar.setTimeInMillis(System.currentTimeMillis());
   1.897 +        calendar.add( Calendar.YEAR, -80 );
   1.898 +        parseAmbiguousDatesAsAfter(calendar.getTime());
   1.899 +    }
   1.900 +
   1.901 +    /* Define one-century window into which to disambiguate dates using
   1.902 +     * two-digit years.
   1.903 +     */
   1.904 +    private void parseAmbiguousDatesAsAfter(Date startDate) {
   1.905 +        defaultCenturyStart = startDate;
   1.906 +        calendar.setTime(startDate);
   1.907 +        defaultCenturyStartYear = calendar.get(Calendar.YEAR);
   1.908 +    }
   1.909 +
   1.910 +    /**
   1.911 +     * Sets the 100-year period 2-digit years will be interpreted as being in
   1.912 +     * to begin on the date the user specifies.
   1.913 +     *
   1.914 +     * @param startDate During parsing, two digit years will be placed in the range
   1.915 +     * <code>startDate</code> to <code>startDate + 100 years</code>.
   1.916 +     * @see #get2DigitYearStart
   1.917 +     * @since 1.2
   1.918 +     */
   1.919 +    public void set2DigitYearStart(Date startDate) {
   1.920 +        parseAmbiguousDatesAsAfter(new Date(startDate.getTime()));
   1.921 +    }
   1.922 +
   1.923 +    /**
   1.924 +     * Returns the beginning date of the 100-year period 2-digit years are interpreted
   1.925 +     * as being within.
   1.926 +     *
   1.927 +     * @return the start of the 100-year period into which two digit years are
   1.928 +     * parsed
   1.929 +     * @see #set2DigitYearStart
   1.930 +     * @since 1.2
   1.931 +     */
   1.932 +    public Date get2DigitYearStart() {
   1.933 +        return (Date) defaultCenturyStart.clone();
   1.934 +    }
   1.935 +
   1.936 +    /**
   1.937 +     * Formats the given <code>Date</code> into a date/time string and appends
   1.938 +     * the result to the given <code>StringBuffer</code>.
   1.939 +     *
   1.940 +     * @param date the date-time value to be formatted into a date-time string.
   1.941 +     * @param toAppendTo where the new date-time text is to be appended.
   1.942 +     * @param pos the formatting position. On input: an alignment field,
   1.943 +     * if desired. On output: the offsets of the alignment field.
   1.944 +     * @return the formatted date-time string.
   1.945 +     * @exception NullPointerException if the given {@code date} is {@code null}.
   1.946 +     */
   1.947 +    public StringBuffer format(Date date, StringBuffer toAppendTo,
   1.948 +                               FieldPosition pos)
   1.949 +    {
   1.950 +        pos.beginIndex = pos.endIndex = 0;
   1.951 +        return format(date, toAppendTo, pos.getFieldDelegate());
   1.952 +    }
   1.953 +
   1.954 +    // Called from Format after creating a FieldDelegate
   1.955 +    private StringBuffer format(Date date, StringBuffer toAppendTo,
   1.956 +                                FieldDelegate delegate) {
   1.957 +        // Convert input date to time field list
   1.958 +        calendar.setTime(date);
   1.959 +
   1.960 +        boolean useDateFormatSymbols = useDateFormatSymbols();
   1.961 +
   1.962 +        for (int i = 0; i < compiledPattern.length; ) {
   1.963 +            int tag = compiledPattern[i] >>> 8;
   1.964 +            int count = compiledPattern[i++] & 0xff;
   1.965 +            if (count == 255) {
   1.966 +                count = compiledPattern[i++] << 16;
   1.967 +                count |= compiledPattern[i++];
   1.968 +            }
   1.969 +
   1.970 +            switch (tag) {
   1.971 +            case TAG_QUOTE_ASCII_CHAR:
   1.972 +                toAppendTo.append((char)count);
   1.973 +                break;
   1.974 +
   1.975 +            case TAG_QUOTE_CHARS:
   1.976 +                toAppendTo.append(compiledPattern, i, count);
   1.977 +                i += count;
   1.978 +                break;
   1.979 +
   1.980 +            default:
   1.981 +                subFormat(tag, count, delegate, toAppendTo, useDateFormatSymbols);
   1.982 +                break;
   1.983 +            }
   1.984 +        }
   1.985 +        return toAppendTo;
   1.986 +    }
   1.987 +
   1.988 +    /**
   1.989 +     * Formats an Object producing an <code>AttributedCharacterIterator</code>.
   1.990 +     * You can use the returned <code>AttributedCharacterIterator</code>
   1.991 +     * to build the resulting String, as well as to determine information
   1.992 +     * about the resulting String.
   1.993 +     * <p>
   1.994 +     * Each attribute key of the AttributedCharacterIterator will be of type
   1.995 +     * <code>DateFormat.Field</code>, with the corresponding attribute value
   1.996 +     * being the same as the attribute key.
   1.997 +     *
   1.998 +     * @exception NullPointerException if obj is null.
   1.999 +     * @exception IllegalArgumentException if the Format cannot format the
  1.1000 +     *            given object, or if the Format's pattern string is invalid.
  1.1001 +     * @param obj The object to format
  1.1002 +     * @return AttributedCharacterIterator describing the formatted value.
  1.1003 +     * @since 1.4
  1.1004 +     */
  1.1005 +    public AttributedCharacterIterator formatToCharacterIterator(Object obj) {
  1.1006 +        StringBuffer sb = new StringBuffer();
  1.1007 +        CharacterIteratorFieldDelegate delegate = new
  1.1008 +                         CharacterIteratorFieldDelegate();
  1.1009 +
  1.1010 +        if (obj instanceof Date) {
  1.1011 +            format((Date)obj, sb, delegate);
  1.1012 +        }
  1.1013 +        else if (obj instanceof Number) {
  1.1014 +            format(new Date(((Number)obj).longValue()), sb, delegate);
  1.1015 +        }
  1.1016 +        else if (obj == null) {
  1.1017 +            throw new NullPointerException(
  1.1018 +                   "formatToCharacterIterator must be passed non-null object");
  1.1019 +        }
  1.1020 +        else {
  1.1021 +            throw new IllegalArgumentException(
  1.1022 +                             "Cannot format given Object as a Date");
  1.1023 +        }
  1.1024 +        return delegate.getIterator(sb.toString());
  1.1025 +    }
  1.1026 +
  1.1027 +    // Map index into pattern character string to Calendar field number
  1.1028 +    private static final int[] PATTERN_INDEX_TO_CALENDAR_FIELD =
  1.1029 +    {
  1.1030 +        Calendar.ERA, Calendar.YEAR, Calendar.MONTH, Calendar.DATE,
  1.1031 +        Calendar.HOUR_OF_DAY, Calendar.HOUR_OF_DAY, Calendar.MINUTE,
  1.1032 +        Calendar.SECOND, Calendar.MILLISECOND, Calendar.DAY_OF_WEEK,
  1.1033 +        Calendar.DAY_OF_YEAR, Calendar.DAY_OF_WEEK_IN_MONTH,
  1.1034 +        Calendar.WEEK_OF_YEAR, Calendar.WEEK_OF_MONTH,
  1.1035 +        Calendar.AM_PM, Calendar.HOUR, Calendar.HOUR, Calendar.ZONE_OFFSET,
  1.1036 +        Calendar.ZONE_OFFSET,
  1.1037 +        // Pseudo Calendar fields
  1.1038 +        CalendarBuilder.WEEK_YEAR,
  1.1039 +        CalendarBuilder.ISO_DAY_OF_WEEK,
  1.1040 +        Calendar.ZONE_OFFSET
  1.1041 +    };
  1.1042 +
  1.1043 +    // Map index into pattern character string to DateFormat field number
  1.1044 +    private static final int[] PATTERN_INDEX_TO_DATE_FORMAT_FIELD = {
  1.1045 +        DateFormat.ERA_FIELD, DateFormat.YEAR_FIELD, DateFormat.MONTH_FIELD,
  1.1046 +        DateFormat.DATE_FIELD, DateFormat.HOUR_OF_DAY1_FIELD,
  1.1047 +        DateFormat.HOUR_OF_DAY0_FIELD, DateFormat.MINUTE_FIELD,
  1.1048 +        DateFormat.SECOND_FIELD, DateFormat.MILLISECOND_FIELD,
  1.1049 +        DateFormat.DAY_OF_WEEK_FIELD, DateFormat.DAY_OF_YEAR_FIELD,
  1.1050 +        DateFormat.DAY_OF_WEEK_IN_MONTH_FIELD, DateFormat.WEEK_OF_YEAR_FIELD,
  1.1051 +        DateFormat.WEEK_OF_MONTH_FIELD, DateFormat.AM_PM_FIELD,
  1.1052 +        DateFormat.HOUR1_FIELD, DateFormat.HOUR0_FIELD,
  1.1053 +        DateFormat.TIMEZONE_FIELD, DateFormat.TIMEZONE_FIELD,
  1.1054 +        DateFormat.YEAR_FIELD, DateFormat.DAY_OF_WEEK_FIELD,
  1.1055 +        DateFormat.TIMEZONE_FIELD
  1.1056 +    };
  1.1057 +
  1.1058 +    // Maps from DecimalFormatSymbols index to Field constant
  1.1059 +    private static final Field[] PATTERN_INDEX_TO_DATE_FORMAT_FIELD_ID = {
  1.1060 +        Field.ERA, Field.YEAR, Field.MONTH, Field.DAY_OF_MONTH,
  1.1061 +        Field.HOUR_OF_DAY1, Field.HOUR_OF_DAY0, Field.MINUTE,
  1.1062 +        Field.SECOND, Field.MILLISECOND, Field.DAY_OF_WEEK,
  1.1063 +        Field.DAY_OF_YEAR, Field.DAY_OF_WEEK_IN_MONTH,
  1.1064 +        Field.WEEK_OF_YEAR, Field.WEEK_OF_MONTH,
  1.1065 +        Field.AM_PM, Field.HOUR1, Field.HOUR0, Field.TIME_ZONE,
  1.1066 +        Field.TIME_ZONE,
  1.1067 +        Field.YEAR, Field.DAY_OF_WEEK,
  1.1068 +        Field.TIME_ZONE
  1.1069 +    };
  1.1070 +
  1.1071 +    /**
  1.1072 +     * Private member function that does the real date/time formatting.
  1.1073 +     */
  1.1074 +    private void subFormat(int patternCharIndex, int count,
  1.1075 +                           FieldDelegate delegate, StringBuffer buffer,
  1.1076 +                           boolean useDateFormatSymbols)
  1.1077 +    {
  1.1078 +        int     maxIntCount = Integer.MAX_VALUE;
  1.1079 +        String  current = null;
  1.1080 +        int     beginOffset = buffer.length();
  1.1081 +
  1.1082 +        int field = PATTERN_INDEX_TO_CALENDAR_FIELD[patternCharIndex];
  1.1083 +        int value;
  1.1084 +        if (field == CalendarBuilder.WEEK_YEAR) {
  1.1085 +            if (calendar.isWeekDateSupported()) {
  1.1086 +                value = calendar.getWeekYear();
  1.1087 +            } else {
  1.1088 +                // use calendar year 'y' instead
  1.1089 +                patternCharIndex = PATTERN_YEAR;
  1.1090 +                field = PATTERN_INDEX_TO_CALENDAR_FIELD[patternCharIndex];
  1.1091 +                value = calendar.get(field);
  1.1092 +            }
  1.1093 +        } else if (field == CalendarBuilder.ISO_DAY_OF_WEEK) {
  1.1094 +            value = CalendarBuilder.toISODayOfWeek(calendar.get(Calendar.DAY_OF_WEEK));
  1.1095 +        } else {
  1.1096 +            value = calendar.get(field);
  1.1097 +        }
  1.1098 +
  1.1099 +        int style = (count >= 4) ? Calendar.LONG : Calendar.SHORT;
  1.1100 +        if (!useDateFormatSymbols && field != CalendarBuilder.ISO_DAY_OF_WEEK) {
  1.1101 +            current = calendar.getDisplayName(field, style, locale);
  1.1102 +        }
  1.1103 +
  1.1104 +        // Note: zeroPaddingNumber() assumes that maxDigits is either
  1.1105 +        // 2 or maxIntCount. If we make any changes to this,
  1.1106 +        // zeroPaddingNumber() must be fixed.
  1.1107 +
  1.1108 +        switch (patternCharIndex) {
  1.1109 +        case PATTERN_ERA: // 'G'
  1.1110 +            if (useDateFormatSymbols) {
  1.1111 +                String[] eras = formatData.getEras();
  1.1112 +                if (value < eras.length)
  1.1113 +                    current = eras[value];
  1.1114 +            }
  1.1115 +            if (current == null)
  1.1116 +                current = "";
  1.1117 +            break;
  1.1118 +
  1.1119 +        case PATTERN_WEEK_YEAR: // 'Y'
  1.1120 +        case PATTERN_YEAR:      // 'y'
  1.1121 +            if (calendar instanceof GregorianCalendar) {
  1.1122 +                if (count != 2)
  1.1123 +                    zeroPaddingNumber(value, count, maxIntCount, buffer);
  1.1124 +                else // count == 2
  1.1125 +                    zeroPaddingNumber(value, 2, 2, buffer); // clip 1996 to 96
  1.1126 +            } else {
  1.1127 +                if (current == null) {
  1.1128 +                    zeroPaddingNumber(value, style == Calendar.LONG ? 1 : count,
  1.1129 +                                      maxIntCount, buffer);
  1.1130 +                }
  1.1131 +            }
  1.1132 +            break;
  1.1133 +
  1.1134 +        case PATTERN_MONTH: // 'M'
  1.1135 +            if (useDateFormatSymbols) {
  1.1136 +                String[] months;
  1.1137 +                if (count >= 4) {
  1.1138 +                    months = formatData.getMonths();
  1.1139 +                    current = months[value];
  1.1140 +                } else if (count == 3) {
  1.1141 +                    months = formatData.getShortMonths();
  1.1142 +                    current = months[value];
  1.1143 +                }
  1.1144 +            } else {
  1.1145 +                if (count < 3) {
  1.1146 +                    current = null;
  1.1147 +                }
  1.1148 +            }
  1.1149 +            if (current == null) {
  1.1150 +                zeroPaddingNumber(value+1, count, maxIntCount, buffer);
  1.1151 +            }
  1.1152 +            break;
  1.1153 +
  1.1154 +        case PATTERN_HOUR_OF_DAY1: // 'k' 1-based.  eg, 23:59 + 1 hour =>> 24:59
  1.1155 +            if (current == null) {
  1.1156 +                if (value == 0)
  1.1157 +                    zeroPaddingNumber(calendar.getMaximum(Calendar.HOUR_OF_DAY)+1,
  1.1158 +                                      count, maxIntCount, buffer);
  1.1159 +                else
  1.1160 +                    zeroPaddingNumber(value, count, maxIntCount, buffer);
  1.1161 +            }
  1.1162 +            break;
  1.1163 +
  1.1164 +        case PATTERN_DAY_OF_WEEK: // 'E'
  1.1165 +            if (useDateFormatSymbols) {
  1.1166 +                String[] weekdays;
  1.1167 +                if (count >= 4) {
  1.1168 +                    weekdays = formatData.getWeekdays();
  1.1169 +                    current = weekdays[value];
  1.1170 +                } else { // count < 4, use abbreviated form if exists
  1.1171 +                    weekdays = formatData.getShortWeekdays();
  1.1172 +                    current = weekdays[value];
  1.1173 +                }
  1.1174 +            }
  1.1175 +            break;
  1.1176 +
  1.1177 +        case PATTERN_AM_PM:    // 'a'
  1.1178 +            if (useDateFormatSymbols) {
  1.1179 +                String[] ampm = formatData.getAmPmStrings();
  1.1180 +                current = ampm[value];
  1.1181 +            }
  1.1182 +            break;
  1.1183 +
  1.1184 +        case PATTERN_HOUR1:    // 'h' 1-based.  eg, 11PM + 1 hour =>> 12 AM
  1.1185 +            if (current == null) {
  1.1186 +                if (value == 0)
  1.1187 +                    zeroPaddingNumber(calendar.getLeastMaximum(Calendar.HOUR)+1,
  1.1188 +                                      count, maxIntCount, buffer);
  1.1189 +                else
  1.1190 +                    zeroPaddingNumber(value, count, maxIntCount, buffer);
  1.1191 +            }
  1.1192 +            break;
  1.1193 +
  1.1194 +        case PATTERN_ZONE_NAME: // 'z'
  1.1195 +            if (current == null) {
  1.1196 +                if (formatData.locale == null || formatData.isZoneStringsSet) {
  1.1197 +                    int zoneIndex =
  1.1198 +                        formatData.getZoneIndex(calendar.getTimeZone().getID());
  1.1199 +                    if (zoneIndex == -1) {
  1.1200 +                        value = calendar.get(Calendar.ZONE_OFFSET) +
  1.1201 +                            calendar.get(Calendar.DST_OFFSET);
  1.1202 +                        buffer.append(ZoneInfoFile.toCustomID(value));
  1.1203 +                    } else {
  1.1204 +                        int index = (calendar.get(Calendar.DST_OFFSET) == 0) ? 1: 3;
  1.1205 +                        if (count < 4) {
  1.1206 +                            // Use the short name
  1.1207 +                            index++;
  1.1208 +                        }
  1.1209 +                        String[][] zoneStrings = formatData.getZoneStringsWrapper();
  1.1210 +                        buffer.append(zoneStrings[zoneIndex][index]);
  1.1211 +                    }
  1.1212 +                } else {
  1.1213 +                    TimeZone tz = calendar.getTimeZone();
  1.1214 +                    boolean daylight = (calendar.get(Calendar.DST_OFFSET) != 0);
  1.1215 +                    int tzstyle = (count < 4 ? TimeZone.SHORT : TimeZone.LONG);
  1.1216 +                    buffer.append(tz.getDisplayName(daylight, tzstyle, formatData.locale));
  1.1217 +                }
  1.1218 +            }
  1.1219 +            break;
  1.1220 +
  1.1221 +        case PATTERN_ZONE_VALUE: // 'Z' ("-/+hhmm" form)
  1.1222 +            value = (calendar.get(Calendar.ZONE_OFFSET) +
  1.1223 +                     calendar.get(Calendar.DST_OFFSET)) / 60000;
  1.1224 +
  1.1225 +            int width = 4;
  1.1226 +            if (value >= 0) {
  1.1227 +                buffer.append('+');
  1.1228 +            } else {
  1.1229 +                width++;
  1.1230 +            }
  1.1231 +
  1.1232 +            int num = (value / 60) * 100 + (value % 60);
  1.1233 +            CalendarUtils.sprintf0d(buffer, num, width);
  1.1234 +            break;
  1.1235 +
  1.1236 +        case PATTERN_ISO_ZONE:   // 'X'
  1.1237 +            value = calendar.get(Calendar.ZONE_OFFSET)
  1.1238 +                    + calendar.get(Calendar.DST_OFFSET);
  1.1239 +
  1.1240 +            if (value == 0) {
  1.1241 +                buffer.append('Z');
  1.1242 +                break;
  1.1243 +            }
  1.1244 +
  1.1245 +            value /=  60000;
  1.1246 +            if (value >= 0) {
  1.1247 +                buffer.append('+');
  1.1248 +            } else {
  1.1249 +                buffer.append('-');
  1.1250 +                value = -value;
  1.1251 +            }
  1.1252 +
  1.1253 +            CalendarUtils.sprintf0d(buffer, value / 60, 2);
  1.1254 +            if (count == 1) {
  1.1255 +                break;
  1.1256 +            }
  1.1257 +
  1.1258 +            if (count == 3) {
  1.1259 +                buffer.append(':');
  1.1260 +            }
  1.1261 +            CalendarUtils.sprintf0d(buffer, value % 60, 2);
  1.1262 +            break;
  1.1263 +
  1.1264 +        default:
  1.1265 +     // case PATTERN_DAY_OF_MONTH:         // 'd'
  1.1266 +     // case PATTERN_HOUR_OF_DAY0:         // 'H' 0-based.  eg, 23:59 + 1 hour =>> 00:59
  1.1267 +     // case PATTERN_MINUTE:               // 'm'
  1.1268 +     // case PATTERN_SECOND:               // 's'
  1.1269 +     // case PATTERN_MILLISECOND:          // 'S'
  1.1270 +     // case PATTERN_DAY_OF_YEAR:          // 'D'
  1.1271 +     // case PATTERN_DAY_OF_WEEK_IN_MONTH: // 'F'
  1.1272 +     // case PATTERN_WEEK_OF_YEAR:         // 'w'
  1.1273 +     // case PATTERN_WEEK_OF_MONTH:        // 'W'
  1.1274 +     // case PATTERN_HOUR0:                // 'K' eg, 11PM + 1 hour =>> 0 AM
  1.1275 +     // case PATTERN_ISO_DAY_OF_WEEK:      // 'u' pseudo field, Monday = 1, ..., Sunday = 7
  1.1276 +            if (current == null) {
  1.1277 +                zeroPaddingNumber(value, count, maxIntCount, buffer);
  1.1278 +            }
  1.1279 +            break;
  1.1280 +        } // switch (patternCharIndex)
  1.1281 +
  1.1282 +        if (current != null) {
  1.1283 +            buffer.append(current);
  1.1284 +        }
  1.1285 +
  1.1286 +        int fieldID = PATTERN_INDEX_TO_DATE_FORMAT_FIELD[patternCharIndex];
  1.1287 +        Field f = PATTERN_INDEX_TO_DATE_FORMAT_FIELD_ID[patternCharIndex];
  1.1288 +
  1.1289 +        delegate.formatted(fieldID, f, f, beginOffset, buffer.length(), buffer);
  1.1290 +    }
  1.1291 +
  1.1292 +    /**
  1.1293 +     * Formats a number with the specified minimum and maximum number of digits.
  1.1294 +     */
  1.1295 +    private final void zeroPaddingNumber(int value, int minDigits, int maxDigits, StringBuffer buffer)
  1.1296 +    {
  1.1297 +        // Optimization for 1, 2 and 4 digit numbers. This should
  1.1298 +        // cover most cases of formatting date/time related items.
  1.1299 +        // Note: This optimization code assumes that maxDigits is
  1.1300 +        // either 2 or Integer.MAX_VALUE (maxIntCount in format()).
  1.1301 +        try {
  1.1302 +            if (zeroDigit == 0) {
  1.1303 +                zeroDigit = ((DecimalFormat)numberFormat).getDecimalFormatSymbols().getZeroDigit();
  1.1304 +            }
  1.1305 +            if (value >= 0) {
  1.1306 +                if (value < 100 && minDigits >= 1 && minDigits <= 2) {
  1.1307 +                    if (value < 10) {
  1.1308 +                        if (minDigits == 2) {
  1.1309 +                            buffer.append(zeroDigit);
  1.1310 +                        }
  1.1311 +                        buffer.append((char)(zeroDigit + value));
  1.1312 +                    } else {
  1.1313 +                        buffer.append((char)(zeroDigit + value / 10));
  1.1314 +                        buffer.append((char)(zeroDigit + value % 10));
  1.1315 +                    }
  1.1316 +                    return;
  1.1317 +                } else if (value >= 1000 && value < 10000) {
  1.1318 +                    if (minDigits == 4) {
  1.1319 +                        buffer.append((char)(zeroDigit + value / 1000));
  1.1320 +                        value %= 1000;
  1.1321 +                        buffer.append((char)(zeroDigit + value / 100));
  1.1322 +                        value %= 100;
  1.1323 +                        buffer.append((char)(zeroDigit + value / 10));
  1.1324 +                        buffer.append((char)(zeroDigit + value % 10));
  1.1325 +                        return;
  1.1326 +                    }
  1.1327 +                    if (minDigits == 2 && maxDigits == 2) {
  1.1328 +                        zeroPaddingNumber(value % 100, 2, 2, buffer);
  1.1329 +                        return;
  1.1330 +                    }
  1.1331 +                }
  1.1332 +            }
  1.1333 +        } catch (Exception e) {
  1.1334 +        }
  1.1335 +
  1.1336 +        numberFormat.setMinimumIntegerDigits(minDigits);
  1.1337 +        numberFormat.setMaximumIntegerDigits(maxDigits);
  1.1338 +        numberFormat.format((long)value, buffer, DontCareFieldPosition.INSTANCE);
  1.1339 +    }
  1.1340 +
  1.1341 +
  1.1342 +    /**
  1.1343 +     * Parses text from a string to produce a <code>Date</code>.
  1.1344 +     * <p>
  1.1345 +     * The method attempts to parse text starting at the index given by
  1.1346 +     * <code>pos</code>.
  1.1347 +     * If parsing succeeds, then the index of <code>pos</code> is updated
  1.1348 +     * to the index after the last character used (parsing does not necessarily
  1.1349 +     * use all characters up to the end of the string), and the parsed
  1.1350 +     * date is returned. The updated <code>pos</code> can be used to
  1.1351 +     * indicate the starting point for the next call to this method.
  1.1352 +     * If an error occurs, then the index of <code>pos</code> is not
  1.1353 +     * changed, the error index of <code>pos</code> is set to the index of
  1.1354 +     * the character where the error occurred, and null is returned.
  1.1355 +     *
  1.1356 +     * <p>This parsing operation uses the {@link DateFormat#calendar
  1.1357 +     * calendar} to produce a {@code Date}. All of the {@code
  1.1358 +     * calendar}'s date-time fields are {@linkplain Calendar#clear()
  1.1359 +     * cleared} before parsing, and the {@code calendar}'s default
  1.1360 +     * values of the date-time fields are used for any missing
  1.1361 +     * date-time information. For example, the year value of the
  1.1362 +     * parsed {@code Date} is 1970 with {@link GregorianCalendar} if
  1.1363 +     * no year value is given from the parsing operation.  The {@code
  1.1364 +     * TimeZone} value may be overwritten, depending on the given
  1.1365 +     * pattern and the time zone value in {@code text}. Any {@code
  1.1366 +     * TimeZone} value that has previously been set by a call to
  1.1367 +     * {@link #setTimeZone(java.util.TimeZone) setTimeZone} may need
  1.1368 +     * to be restored for further operations.
  1.1369 +     *
  1.1370 +     * @param text  A <code>String</code>, part of which should be parsed.
  1.1371 +     * @param pos   A <code>ParsePosition</code> object with index and error
  1.1372 +     *              index information as described above.
  1.1373 +     * @return A <code>Date</code> parsed from the string. In case of
  1.1374 +     *         error, returns null.
  1.1375 +     * @exception NullPointerException if <code>text</code> or <code>pos</code> is null.
  1.1376 +     */
  1.1377 +    public Date parse(String text, ParsePosition pos)
  1.1378 +    {
  1.1379 +        checkNegativeNumberExpression();
  1.1380 +
  1.1381 +        int start = pos.index;
  1.1382 +        int oldStart = start;
  1.1383 +        int textLength = text.length();
  1.1384 +
  1.1385 +        boolean[] ambiguousYear = {false};
  1.1386 +
  1.1387 +        CalendarBuilder calb = new CalendarBuilder();
  1.1388 +
  1.1389 +        for (int i = 0; i < compiledPattern.length; ) {
  1.1390 +            int tag = compiledPattern[i] >>> 8;
  1.1391 +            int count = compiledPattern[i++] & 0xff;
  1.1392 +            if (count == 255) {
  1.1393 +                count = compiledPattern[i++] << 16;
  1.1394 +                count |= compiledPattern[i++];
  1.1395 +            }
  1.1396 +
  1.1397 +            switch (tag) {
  1.1398 +            case TAG_QUOTE_ASCII_CHAR:
  1.1399 +                if (start >= textLength || text.charAt(start) != (char)count) {
  1.1400 +                    pos.index = oldStart;
  1.1401 +                    pos.errorIndex = start;
  1.1402 +                    return null;
  1.1403 +                }
  1.1404 +                start++;
  1.1405 +                break;
  1.1406 +
  1.1407 +            case TAG_QUOTE_CHARS:
  1.1408 +                while (count-- > 0) {
  1.1409 +                    if (start >= textLength || text.charAt(start) != compiledPattern[i++]) {
  1.1410 +                        pos.index = oldStart;
  1.1411 +                        pos.errorIndex = start;
  1.1412 +                        return null;
  1.1413 +                    }
  1.1414 +                    start++;
  1.1415 +                }
  1.1416 +                break;
  1.1417 +
  1.1418 +            default:
  1.1419 +                // Peek the next pattern to determine if we need to
  1.1420 +                // obey the number of pattern letters for
  1.1421 +                // parsing. It's required when parsing contiguous
  1.1422 +                // digit text (e.g., "20010704") with a pattern which
  1.1423 +                // has no delimiters between fields, like "yyyyMMdd".
  1.1424 +                boolean obeyCount = false;
  1.1425 +
  1.1426 +                // In Arabic, a minus sign for a negative number is put after
  1.1427 +                // the number. Even in another locale, a minus sign can be
  1.1428 +                // put after a number using DateFormat.setNumberFormat().
  1.1429 +                // If both the minus sign and the field-delimiter are '-',
  1.1430 +                // subParse() needs to determine whether a '-' after a number
  1.1431 +                // in the given text is a delimiter or is a minus sign for the
  1.1432 +                // preceding number. We give subParse() a clue based on the
  1.1433 +                // information in compiledPattern.
  1.1434 +                boolean useFollowingMinusSignAsDelimiter = false;
  1.1435 +
  1.1436 +                if (i < compiledPattern.length) {
  1.1437 +                    int nextTag = compiledPattern[i] >>> 8;
  1.1438 +                    if (!(nextTag == TAG_QUOTE_ASCII_CHAR ||
  1.1439 +                          nextTag == TAG_QUOTE_CHARS)) {
  1.1440 +                        obeyCount = true;
  1.1441 +                    }
  1.1442 +
  1.1443 +                    if (hasFollowingMinusSign &&
  1.1444 +                        (nextTag == TAG_QUOTE_ASCII_CHAR ||
  1.1445 +                         nextTag == TAG_QUOTE_CHARS)) {
  1.1446 +                        int c;
  1.1447 +                        if (nextTag == TAG_QUOTE_ASCII_CHAR) {
  1.1448 +                            c = compiledPattern[i] & 0xff;
  1.1449 +                        } else {
  1.1450 +                            c = compiledPattern[i+1];
  1.1451 +                        }
  1.1452 +
  1.1453 +                        if (c == minusSign) {
  1.1454 +                            useFollowingMinusSignAsDelimiter = true;
  1.1455 +                        }
  1.1456 +                    }
  1.1457 +                }
  1.1458 +                start = subParse(text, start, tag, count, obeyCount,
  1.1459 +                                 ambiguousYear, pos,
  1.1460 +                                 useFollowingMinusSignAsDelimiter, calb);
  1.1461 +                if (start < 0) {
  1.1462 +                    pos.index = oldStart;
  1.1463 +                    return null;
  1.1464 +                }
  1.1465 +            }
  1.1466 +        }
  1.1467 +
  1.1468 +        // At this point the fields of Calendar have been set.  Calendar
  1.1469 +        // will fill in default values for missing fields when the time
  1.1470 +        // is computed.
  1.1471 +
  1.1472 +        pos.index = start;
  1.1473 +
  1.1474 +        Date parsedDate;
  1.1475 +        try {
  1.1476 +            parsedDate = calb.establish(calendar).getTime();
  1.1477 +            // If the year value is ambiguous,
  1.1478 +            // then the two-digit year == the default start year
  1.1479 +            if (ambiguousYear[0]) {
  1.1480 +                if (parsedDate.before(defaultCenturyStart)) {
  1.1481 +                    parsedDate = calb.addYear(100).establish(calendar).getTime();
  1.1482 +                }
  1.1483 +            }
  1.1484 +        }
  1.1485 +        // An IllegalArgumentException will be thrown by Calendar.getTime()
  1.1486 +        // if any fields are out of range, e.g., MONTH == 17.
  1.1487 +        catch (IllegalArgumentException e) {
  1.1488 +            pos.errorIndex = start;
  1.1489 +            pos.index = oldStart;
  1.1490 +            return null;
  1.1491 +        }
  1.1492 +
  1.1493 +        return parsedDate;
  1.1494 +    }
  1.1495 +
  1.1496 +    /**
  1.1497 +     * Private code-size reduction function used by subParse.
  1.1498 +     * @param text the time text being parsed.
  1.1499 +     * @param start where to start parsing.
  1.1500 +     * @param field the date field being parsed.
  1.1501 +     * @param data the string array to parsed.
  1.1502 +     * @return the new start position if matching succeeded; a negative number
  1.1503 +     * indicating matching failure, otherwise.
  1.1504 +     */
  1.1505 +    private int matchString(String text, int start, int field, String[] data, CalendarBuilder calb)
  1.1506 +    {
  1.1507 +        int i = 0;
  1.1508 +        int count = data.length;
  1.1509 +
  1.1510 +        if (field == Calendar.DAY_OF_WEEK) i = 1;
  1.1511 +
  1.1512 +        // There may be multiple strings in the data[] array which begin with
  1.1513 +        // the same prefix (e.g., Cerven and Cervenec (June and July) in Czech).
  1.1514 +        // We keep track of the longest match, and return that.  Note that this
  1.1515 +        // unfortunately requires us to test all array elements.
  1.1516 +        int bestMatchLength = 0, bestMatch = -1;
  1.1517 +        for (; i<count; ++i)
  1.1518 +        {
  1.1519 +            int length = data[i].length();
  1.1520 +            // Always compare if we have no match yet; otherwise only compare
  1.1521 +            // against potentially better matches (longer strings).
  1.1522 +            if (length > bestMatchLength &&
  1.1523 +                text.regionMatches(true, start, data[i], 0, length))
  1.1524 +            {
  1.1525 +                bestMatch = i;
  1.1526 +                bestMatchLength = length;
  1.1527 +            }
  1.1528 +        }
  1.1529 +        if (bestMatch >= 0)
  1.1530 +        {
  1.1531 +            calb.set(field, bestMatch);
  1.1532 +            return start + bestMatchLength;
  1.1533 +        }
  1.1534 +        return -start;
  1.1535 +    }
  1.1536 +
  1.1537 +    /**
  1.1538 +     * Performs the same thing as matchString(String, int, int,
  1.1539 +     * String[]). This method takes a Map<String, Integer> instead of
  1.1540 +     * String[].
  1.1541 +     */
  1.1542 +    private int matchString(String text, int start, int field,
  1.1543 +                            Map<String,Integer> data, CalendarBuilder calb) {
  1.1544 +        if (data != null) {
  1.1545 +            String bestMatch = null;
  1.1546 +
  1.1547 +            for (String name : data.keySet()) {
  1.1548 +                int length = name.length();
  1.1549 +                if (bestMatch == null || length > bestMatch.length()) {
  1.1550 +                    if (text.regionMatches(true, start, name, 0, length)) {
  1.1551 +                        bestMatch = name;
  1.1552 +                    }
  1.1553 +                }
  1.1554 +            }
  1.1555 +
  1.1556 +            if (bestMatch != null) {
  1.1557 +                calb.set(field, data.get(bestMatch));
  1.1558 +                return start + bestMatch.length();
  1.1559 +            }
  1.1560 +        }
  1.1561 +        return -start;
  1.1562 +    }
  1.1563 +
  1.1564 +    private int matchZoneString(String text, int start, String[] zoneNames) {
  1.1565 +        for (int i = 1; i <= 4; ++i) {
  1.1566 +            // Checking long and short zones [1 & 2],
  1.1567 +            // and long and short daylight [3 & 4].
  1.1568 +            String zoneName = zoneNames[i];
  1.1569 +            if (text.regionMatches(true, start,
  1.1570 +                                   zoneName, 0, zoneName.length())) {
  1.1571 +                return i;
  1.1572 +            }
  1.1573 +        }
  1.1574 +        return -1;
  1.1575 +    }
  1.1576 +
  1.1577 +    private boolean matchDSTString(String text, int start, int zoneIndex, int standardIndex,
  1.1578 +                                   String[][] zoneStrings) {
  1.1579 +        int index = standardIndex + 2;
  1.1580 +        String zoneName  = zoneStrings[zoneIndex][index];
  1.1581 +        if (text.regionMatches(true, start,
  1.1582 +                               zoneName, 0, zoneName.length())) {
  1.1583 +            return true;
  1.1584 +        }
  1.1585 +        return false;
  1.1586 +    }
  1.1587 +
  1.1588 +    /**
  1.1589 +     * find time zone 'text' matched zoneStrings and set to internal
  1.1590 +     * calendar.
  1.1591 +     */
  1.1592 +    private int subParseZoneString(String text, int start, CalendarBuilder calb) {
  1.1593 +        boolean useSameName = false; // true if standard and daylight time use the same abbreviation.
  1.1594 +        TimeZone currentTimeZone = getTimeZone();
  1.1595 +
  1.1596 +        // At this point, check for named time zones by looking through
  1.1597 +        // the locale data from the TimeZoneNames strings.
  1.1598 +        // Want to be able to parse both short and long forms.
  1.1599 +        int zoneIndex = formatData.getZoneIndex(currentTimeZone.getID());
  1.1600 +        TimeZone tz = null;
  1.1601 +        String[][] zoneStrings = formatData.getZoneStringsWrapper();
  1.1602 +        String[] zoneNames = null;
  1.1603 +        int nameIndex = 0;
  1.1604 +        if (zoneIndex != -1) {
  1.1605 +            zoneNames = zoneStrings[zoneIndex];
  1.1606 +            if ((nameIndex = matchZoneString(text, start, zoneNames)) > 0) {
  1.1607 +                if (nameIndex <= 2) {
  1.1608 +                    // Check if the standard name (abbr) and the daylight name are the same.
  1.1609 +                    useSameName = zoneNames[nameIndex].equalsIgnoreCase(zoneNames[nameIndex + 2]);
  1.1610 +                }
  1.1611 +                tz = TimeZone.getTimeZone(zoneNames[0]);
  1.1612 +            }
  1.1613 +        }
  1.1614 +        if (tz == null) {
  1.1615 +            zoneIndex = formatData.getZoneIndex(TimeZone.getDefault().getID());
  1.1616 +            if (zoneIndex != -1) {
  1.1617 +                zoneNames = zoneStrings[zoneIndex];
  1.1618 +                if ((nameIndex = matchZoneString(text, start, zoneNames)) > 0) {
  1.1619 +                    if (nameIndex <= 2) {
  1.1620 +                        useSameName = zoneNames[nameIndex].equalsIgnoreCase(zoneNames[nameIndex + 2]);
  1.1621 +                    }
  1.1622 +                    tz = TimeZone.getTimeZone(zoneNames[0]);
  1.1623 +                }
  1.1624 +            }
  1.1625 +        }
  1.1626 +
  1.1627 +        if (tz == null) {
  1.1628 +            int len = zoneStrings.length;
  1.1629 +            for (int i = 0; i < len; i++) {
  1.1630 +                zoneNames = zoneStrings[i];
  1.1631 +                if ((nameIndex = matchZoneString(text, start, zoneNames)) > 0) {
  1.1632 +                    if (nameIndex <= 2) {
  1.1633 +                        useSameName = zoneNames[nameIndex].equalsIgnoreCase(zoneNames[nameIndex + 2]);
  1.1634 +                    }
  1.1635 +                    tz = TimeZone.getTimeZone(zoneNames[0]);
  1.1636 +                    break;
  1.1637 +                }
  1.1638 +            }
  1.1639 +        }
  1.1640 +        if (tz != null) { // Matched any ?
  1.1641 +            if (!tz.equals(currentTimeZone)) {
  1.1642 +                setTimeZone(tz);
  1.1643 +            }
  1.1644 +            // If the time zone matched uses the same name
  1.1645 +            // (abbreviation) for both standard and daylight time,
  1.1646 +            // let the time zone in the Calendar decide which one.
  1.1647 +            //
  1.1648 +            // Also if tz.getDSTSaving() returns 0 for DST, use tz to
  1.1649 +            // determine the local time. (6645292)
  1.1650 +            int dstAmount = (nameIndex >= 3) ? tz.getDSTSavings() : 0;
  1.1651 +            if (!(useSameName || (nameIndex >= 3 && dstAmount == 0))) {
  1.1652 +                calb.set(Calendar.ZONE_OFFSET, tz.getRawOffset())
  1.1653 +                    .set(Calendar.DST_OFFSET, dstAmount);
  1.1654 +            }
  1.1655 +            return (start + zoneNames[nameIndex].length());
  1.1656 +        }
  1.1657 +        return 0;
  1.1658 +    }
  1.1659 +
  1.1660 +    /**
  1.1661 +     * Parses numeric forms of time zone offset, such as "hh:mm", and
  1.1662 +     * sets calb to the parsed value.
  1.1663 +     *
  1.1664 +     * @param text  the text to be parsed
  1.1665 +     * @param start the character position to start parsing
  1.1666 +     * @param sign  1: positive; -1: negative
  1.1667 +     * @param count 0: 'Z' or "GMT+hh:mm" parsing; 1 - 3: the number of 'X's
  1.1668 +     * @param colon true - colon required between hh and mm; false - no colon required
  1.1669 +     * @param calb  a CalendarBuilder in which the parsed value is stored
  1.1670 +     * @return updated parsed position, or its negative value to indicate a parsing error
  1.1671 +     */
  1.1672 +    private int subParseNumericZone(String text, int start, int sign, int count,
  1.1673 +                                    boolean colon, CalendarBuilder calb) {
  1.1674 +        int index = start;
  1.1675 +
  1.1676 +      parse:
  1.1677 +        try {
  1.1678 +            char c = text.charAt(index++);
  1.1679 +            // Parse hh
  1.1680 +            int hours;
  1.1681 +            if (!isDigit(c)) {
  1.1682 +                break parse;
  1.1683 +            }
  1.1684 +            hours = c - '0';
  1.1685 +            c = text.charAt(index++);
  1.1686 +            if (isDigit(c)) {
  1.1687 +                hours = hours * 10 + (c - '0');
  1.1688 +            } else {
  1.1689 +                // If no colon in RFC 822 or 'X' (ISO), two digits are
  1.1690 +                // required.
  1.1691 +                if (count > 0 || !colon) {
  1.1692 +                    break parse;
  1.1693 +                }
  1.1694 +                --index;
  1.1695 +            }
  1.1696 +            if (hours > 23) {
  1.1697 +                break parse;
  1.1698 +            }
  1.1699 +            int minutes = 0;
  1.1700 +            if (count != 1) {
  1.1701 +                // Proceed with parsing mm
  1.1702 +                c = text.charAt(index++);
  1.1703 +                if (colon) {
  1.1704 +                    if (c != ':') {
  1.1705 +                        break parse;
  1.1706 +                    }
  1.1707 +                    c = text.charAt(index++);
  1.1708 +                }
  1.1709 +                if (!isDigit(c)) {
  1.1710 +                    break parse;
  1.1711 +                }
  1.1712 +                minutes = c - '0';
  1.1713 +                c = text.charAt(index++);
  1.1714 +                if (!isDigit(c)) {
  1.1715 +                    break parse;
  1.1716 +                }
  1.1717 +                minutes = minutes * 10 + (c - '0');
  1.1718 +                if (minutes > 59) {
  1.1719 +                    break parse;
  1.1720 +                }
  1.1721 +            }
  1.1722 +            minutes += hours * 60;
  1.1723 +            calb.set(Calendar.ZONE_OFFSET, minutes * MILLIS_PER_MINUTE * sign)
  1.1724 +                .set(Calendar.DST_OFFSET, 0);
  1.1725 +            return index;
  1.1726 +        } catch (IndexOutOfBoundsException e) {
  1.1727 +        }
  1.1728 +        return  1 - index; // -(index - 1)
  1.1729 +    }
  1.1730 +
  1.1731 +    private boolean isDigit(char c) {
  1.1732 +        return c >= '0' && c <= '9';
  1.1733 +    }
  1.1734 +
  1.1735 +    /**
  1.1736 +     * Private member function that converts the parsed date strings into
  1.1737 +     * timeFields. Returns -start (for ParsePosition) if failed.
  1.1738 +     * @param text the time text to be parsed.
  1.1739 +     * @param start where to start parsing.
  1.1740 +     * @param ch the pattern character for the date field text to be parsed.
  1.1741 +     * @param count the count of a pattern character.
  1.1742 +     * @param obeyCount if true, then the next field directly abuts this one,
  1.1743 +     * and we should use the count to know when to stop parsing.
  1.1744 +     * @param ambiguousYear return parameter; upon return, if ambiguousYear[0]
  1.1745 +     * is true, then a two-digit year was parsed and may need to be readjusted.
  1.1746 +     * @param origPos origPos.errorIndex is used to return an error index
  1.1747 +     * at which a parse error occurred, if matching failure occurs.
  1.1748 +     * @return the new start position if matching succeeded; -1 indicating
  1.1749 +     * matching failure, otherwise. In case matching failure occurred,
  1.1750 +     * an error index is set to origPos.errorIndex.
  1.1751 +     */
  1.1752 +    private int subParse(String text, int start, int patternCharIndex, int count,
  1.1753 +                         boolean obeyCount, boolean[] ambiguousYear,
  1.1754 +                         ParsePosition origPos,
  1.1755 +                         boolean useFollowingMinusSignAsDelimiter, CalendarBuilder calb) {
  1.1756 +        Number number = null;
  1.1757 +        int value = 0;
  1.1758 +        ParsePosition pos = new ParsePosition(0);
  1.1759 +        pos.index = start;
  1.1760 +        if (patternCharIndex == PATTERN_WEEK_YEAR && !calendar.isWeekDateSupported()) {
  1.1761 +            // use calendar year 'y' instead
  1.1762 +            patternCharIndex = PATTERN_YEAR;
  1.1763 +        }
  1.1764 +        int field = PATTERN_INDEX_TO_CALENDAR_FIELD[patternCharIndex];
  1.1765 +
  1.1766 +        // If there are any spaces here, skip over them.  If we hit the end
  1.1767 +        // of the string, then fail.
  1.1768 +        for (;;) {
  1.1769 +            if (pos.index >= text.length()) {
  1.1770 +                origPos.errorIndex = start;
  1.1771 +                return -1;
  1.1772 +            }
  1.1773 +            char c = text.charAt(pos.index);
  1.1774 +            if (c != ' ' && c != '\t') break;
  1.1775 +            ++pos.index;
  1.1776 +        }
  1.1777 +
  1.1778 +      parsing:
  1.1779 +        {
  1.1780 +            // We handle a few special cases here where we need to parse
  1.1781 +            // a number value.  We handle further, more generic cases below.  We need
  1.1782 +            // to handle some of them here because some fields require extra processing on
  1.1783 +            // the parsed value.
  1.1784 +            if (patternCharIndex == PATTERN_HOUR_OF_DAY1 ||
  1.1785 +                patternCharIndex == PATTERN_HOUR1 ||
  1.1786 +                (patternCharIndex == PATTERN_MONTH && count <= 2) ||
  1.1787 +                patternCharIndex == PATTERN_YEAR ||
  1.1788 +                patternCharIndex == PATTERN_WEEK_YEAR) {
  1.1789 +                // It would be good to unify this with the obeyCount logic below,
  1.1790 +                // but that's going to be difficult.
  1.1791 +                if (obeyCount) {
  1.1792 +                    if ((start+count) > text.length()) {
  1.1793 +                        break parsing;
  1.1794 +                    }
  1.1795 +                    number = numberFormat.parse(text.substring(0, start+count), pos);
  1.1796 +                } else {
  1.1797 +                    number = numberFormat.parse(text, pos);
  1.1798 +                }
  1.1799 +                if (number == null) {
  1.1800 +                    if (patternCharIndex != PATTERN_YEAR || calendar instanceof GregorianCalendar) {
  1.1801 +                        break parsing;
  1.1802 +                    }
  1.1803 +                } else {
  1.1804 +                    value = number.intValue();
  1.1805 +
  1.1806 +                    if (useFollowingMinusSignAsDelimiter && (value < 0) &&
  1.1807 +                        (((pos.index < text.length()) &&
  1.1808 +                         (text.charAt(pos.index) != minusSign)) ||
  1.1809 +                         ((pos.index == text.length()) &&
  1.1810 +                          (text.charAt(pos.index-1) == minusSign)))) {
  1.1811 +                        value = -value;
  1.1812 +                        pos.index--;
  1.1813 +                    }
  1.1814 +                }
  1.1815 +            }
  1.1816 +
  1.1817 +            boolean useDateFormatSymbols = useDateFormatSymbols();
  1.1818 +
  1.1819 +            int index;
  1.1820 +            switch (patternCharIndex) {
  1.1821 +            case PATTERN_ERA: // 'G'
  1.1822 +                if (useDateFormatSymbols) {
  1.1823 +                    if ((index = matchString(text, start, Calendar.ERA, formatData.getEras(), calb)) > 0) {
  1.1824 +                        return index;
  1.1825 +                    }
  1.1826 +                } else {
  1.1827 +                    Map<String, Integer> map = calendar.getDisplayNames(field,
  1.1828 +                                                                        Calendar.ALL_STYLES,
  1.1829 +                                                                        locale);
  1.1830 +                    if ((index = matchString(text, start, field, map, calb)) > 0) {
  1.1831 +                        return index;
  1.1832 +                    }
  1.1833 +                }
  1.1834 +                break parsing;
  1.1835 +
  1.1836 +            case PATTERN_WEEK_YEAR: // 'Y'
  1.1837 +            case PATTERN_YEAR:      // 'y'
  1.1838 +                if (!(calendar instanceof GregorianCalendar)) {
  1.1839 +                    // calendar might have text representations for year values,
  1.1840 +                    // such as "\u5143" in JapaneseImperialCalendar.
  1.1841 +                    int style = (count >= 4) ? Calendar.LONG : Calendar.SHORT;
  1.1842 +                    Map<String, Integer> map = calendar.getDisplayNames(field, style, locale);
  1.1843 +                    if (map != null) {
  1.1844 +                        if ((index = matchString(text, start, field, map, calb)) > 0) {
  1.1845 +                            return index;
  1.1846 +                        }
  1.1847 +                    }
  1.1848 +                    calb.set(field, value);
  1.1849 +                    return pos.index;
  1.1850 +                }
  1.1851 +
  1.1852 +                // If there are 3 or more YEAR pattern characters, this indicates
  1.1853 +                // that the year value is to be treated literally, without any
  1.1854 +                // two-digit year adjustments (e.g., from "01" to 2001).  Otherwise
  1.1855 +                // we made adjustments to place the 2-digit year in the proper
  1.1856 +                // century, for parsed strings from "00" to "99".  Any other string
  1.1857 +                // is treated literally:  "2250", "-1", "1", "002".
  1.1858 +                if (count <= 2 && (pos.index - start) == 2
  1.1859 +                    && Character.isDigit(text.charAt(start))
  1.1860 +                    && Character.isDigit(text.charAt(start+1))) {
  1.1861 +                    // Assume for example that the defaultCenturyStart is 6/18/1903.
  1.1862 +                    // This means that two-digit years will be forced into the range
  1.1863 +                    // 6/18/1903 to 6/17/2003.  As a result, years 00, 01, and 02
  1.1864 +                    // correspond to 2000, 2001, and 2002.  Years 04, 05, etc. correspond
  1.1865 +                    // to 1904, 1905, etc.  If the year is 03, then it is 2003 if the
  1.1866 +                    // other fields specify a date before 6/18, or 1903 if they specify a
  1.1867 +                    // date afterwards.  As a result, 03 is an ambiguous year.  All other
  1.1868 +                    // two-digit years are unambiguous.
  1.1869 +                    int ambiguousTwoDigitYear = defaultCenturyStartYear % 100;
  1.1870 +                    ambiguousYear[0] = value == ambiguousTwoDigitYear;
  1.1871 +                    value += (defaultCenturyStartYear/100)*100 +
  1.1872 +                        (value < ambiguousTwoDigitYear ? 100 : 0);
  1.1873 +                }
  1.1874 +                calb.set(field, value);
  1.1875 +                return pos.index;
  1.1876 +
  1.1877 +            case PATTERN_MONTH: // 'M'
  1.1878 +                if (count <= 2) // i.e., M or MM.
  1.1879 +                {
  1.1880 +                    // Don't want to parse the month if it is a string
  1.1881 +                    // while pattern uses numeric style: M or MM.
  1.1882 +                    // [We computed 'value' above.]
  1.1883 +                    calb.set(Calendar.MONTH, value - 1);
  1.1884 +                    return pos.index;
  1.1885 +                }
  1.1886 +
  1.1887 +                if (useDateFormatSymbols) {
  1.1888 +                    // count >= 3 // i.e., MMM or MMMM
  1.1889 +                    // Want to be able to parse both short and long forms.
  1.1890 +                    // Try count == 4 first:
  1.1891 +                    int newStart = 0;
  1.1892 +                    if ((newStart = matchString(text, start, Calendar.MONTH,
  1.1893 +                                                formatData.getMonths(), calb)) > 0) {
  1.1894 +                        return newStart;
  1.1895 +                    }
  1.1896 +                    // count == 4 failed, now try count == 3
  1.1897 +                    if ((index = matchString(text, start, Calendar.MONTH,
  1.1898 +                                             formatData.getShortMonths(), calb)) > 0) {
  1.1899 +                        return index;
  1.1900 +                    }
  1.1901 +                } else {
  1.1902 +                    Map<String, Integer> map = calendar.getDisplayNames(field,
  1.1903 +                                                                        Calendar.ALL_STYLES,
  1.1904 +                                                                        locale);
  1.1905 +                    if ((index = matchString(text, start, field, map, calb)) > 0) {
  1.1906 +                        return index;
  1.1907 +                    }
  1.1908 +                }
  1.1909 +                break parsing;
  1.1910 +
  1.1911 +            case PATTERN_HOUR_OF_DAY1: // 'k' 1-based.  eg, 23:59 + 1 hour =>> 24:59
  1.1912 +                if (!isLenient()) {
  1.1913 +                    // Validate the hour value in non-lenient
  1.1914 +                    if (value < 1 || value > 24) {
  1.1915 +                        break parsing;
  1.1916 +                    }
  1.1917 +                }
  1.1918 +                // [We computed 'value' above.]
  1.1919 +                if (value == calendar.getMaximum(Calendar.HOUR_OF_DAY)+1)
  1.1920 +                    value = 0;
  1.1921 +                calb.set(Calendar.HOUR_OF_DAY, value);
  1.1922 +                return pos.index;
  1.1923 +
  1.1924 +            case PATTERN_DAY_OF_WEEK:  // 'E'
  1.1925 +                {
  1.1926 +                    if (useDateFormatSymbols) {
  1.1927 +                        // Want to be able to parse both short and long forms.
  1.1928 +                        // Try count == 4 (DDDD) first:
  1.1929 +                        int newStart = 0;
  1.1930 +                        if ((newStart=matchString(text, start, Calendar.DAY_OF_WEEK,
  1.1931 +                                                  formatData.getWeekdays(), calb)) > 0) {
  1.1932 +                            return newStart;
  1.1933 +                        }
  1.1934 +                        // DDDD failed, now try DDD
  1.1935 +                        if ((index = matchString(text, start, Calendar.DAY_OF_WEEK,
  1.1936 +                                                 formatData.getShortWeekdays(), calb)) > 0) {
  1.1937 +                            return index;
  1.1938 +                        }
  1.1939 +                    } else {
  1.1940 +                        int[] styles = { Calendar.LONG, Calendar.SHORT };
  1.1941 +                        for (int style : styles) {
  1.1942 +                            Map<String,Integer> map = calendar.getDisplayNames(field, style, locale);
  1.1943 +                            if ((index = matchString(text, start, field, map, calb)) > 0) {
  1.1944 +                                return index;
  1.1945 +                            }
  1.1946 +                        }
  1.1947 +                    }
  1.1948 +                }
  1.1949 +                break parsing;
  1.1950 +
  1.1951 +            case PATTERN_AM_PM:    // 'a'
  1.1952 +                if (useDateFormatSymbols) {
  1.1953 +                    if ((index = matchString(text, start, Calendar.AM_PM,
  1.1954 +                                             formatData.getAmPmStrings(), calb)) > 0) {
  1.1955 +                        return index;
  1.1956 +                    }
  1.1957 +                } else {
  1.1958 +                    Map<String,Integer> map = calendar.getDisplayNames(field, Calendar.ALL_STYLES, locale);
  1.1959 +                    if ((index = matchString(text, start, field, map, calb)) > 0) {
  1.1960 +                        return index;
  1.1961 +                    }
  1.1962 +                }
  1.1963 +                break parsing;
  1.1964 +
  1.1965 +            case PATTERN_HOUR1: // 'h' 1-based.  eg, 11PM + 1 hour =>> 12 AM
  1.1966 +                if (!isLenient()) {
  1.1967 +                    // Validate the hour value in non-lenient
  1.1968 +                    if (value < 1 || value > 12) {
  1.1969 +                        break parsing;
  1.1970 +                    }
  1.1971 +                }
  1.1972 +                // [We computed 'value' above.]
  1.1973 +                if (value == calendar.getLeastMaximum(Calendar.HOUR)+1)
  1.1974 +                    value = 0;
  1.1975 +                calb.set(Calendar.HOUR, value);
  1.1976 +                return pos.index;
  1.1977 +
  1.1978 +            case PATTERN_ZONE_NAME:  // 'z'
  1.1979 +            case PATTERN_ZONE_VALUE: // 'Z'
  1.1980 +                {
  1.1981 +                    int sign = 0;
  1.1982 +                    try {
  1.1983 +                        char c = text.charAt(pos.index);
  1.1984 +                        if (c == '+') {
  1.1985 +                            sign = 1;
  1.1986 +                        } else if (c == '-') {
  1.1987 +                            sign = -1;
  1.1988 +                        }
  1.1989 +                        if (sign == 0) {
  1.1990 +                            // Try parsing a custom time zone "GMT+hh:mm" or "GMT".
  1.1991 +                            if ((c == 'G' || c == 'g')
  1.1992 +                                && (text.length() - start) >= GMT.length()
  1.1993 +                                && text.regionMatches(true, start, GMT, 0, GMT.length())) {
  1.1994 +                                pos.index = start + GMT.length();
  1.1995 +
  1.1996 +                                if ((text.length() - pos.index) > 0) {
  1.1997 +                                    c = text.charAt(pos.index);
  1.1998 +                                    if (c == '+') {
  1.1999 +                                        sign = 1;
  1.2000 +                                    } else if (c == '-') {
  1.2001 +                                        sign = -1;
  1.2002 +                                    }
  1.2003 +                                }
  1.2004 +
  1.2005 +                                if (sign == 0) {    /* "GMT" without offset */
  1.2006 +                                    calb.set(Calendar.ZONE_OFFSET, 0)
  1.2007 +                                        .set(Calendar.DST_OFFSET, 0);
  1.2008 +                                    return pos.index;
  1.2009 +                                }
  1.2010 +
  1.2011 +                                // Parse the rest as "hh:mm"
  1.2012 +                                int i = subParseNumericZone(text, ++pos.index,
  1.2013 +                                                            sign, 0, true, calb);
  1.2014 +                                if (i > 0) {
  1.2015 +                                    return i;
  1.2016 +                                }
  1.2017 +                                pos.index = -i;
  1.2018 +                            } else {
  1.2019 +                                // Try parsing the text as a time zone
  1.2020 +                                // name or abbreviation.
  1.2021 +                                int i = subParseZoneString(text, pos.index, calb);
  1.2022 +                                if (i > 0) {
  1.2023 +                                    return i;
  1.2024 +                                }
  1.2025 +                                pos.index = -i;
  1.2026 +                            }
  1.2027 +                        } else {
  1.2028 +                            // Parse the rest as "hhmm" (RFC 822)
  1.2029 +                            int i = subParseNumericZone(text, ++pos.index,
  1.2030 +                                                        sign, 0, false, calb);
  1.2031 +                            if (i > 0) {
  1.2032 +                                return i;
  1.2033 +                            }
  1.2034 +                            pos.index = -i;
  1.2035 +                        }
  1.2036 +                    } catch (IndexOutOfBoundsException e) {
  1.2037 +                    }
  1.2038 +                }
  1.2039 +                break parsing;
  1.2040 +
  1.2041 +            case PATTERN_ISO_ZONE:   // 'X'
  1.2042 +                {
  1.2043 +                    if ((text.length() - pos.index) <= 0) {
  1.2044 +                        break parsing;
  1.2045 +                    }
  1.2046 +
  1.2047 +                    int sign = 0;
  1.2048 +                    char c = text.charAt(pos.index);
  1.2049 +                    if (c == 'Z') {
  1.2050 +                        calb.set(Calendar.ZONE_OFFSET, 0).set(Calendar.DST_OFFSET, 0);
  1.2051 +                        return ++pos.index;
  1.2052 +                    }
  1.2053 +
  1.2054 +                    // parse text as "+/-hh[[:]mm]" based on count
  1.2055 +                    if (c == '+') {
  1.2056 +                        sign = 1;
  1.2057 +                    } else if (c == '-') {
  1.2058 +                        sign = -1;
  1.2059 +                    } else {
  1.2060 +                        ++pos.index;
  1.2061 +                        break parsing;
  1.2062 +                    }
  1.2063 +                    int i = subParseNumericZone(text, ++pos.index, sign, count,
  1.2064 +                                                count == 3, calb);
  1.2065 +                    if (i > 0) {
  1.2066 +                        return i;
  1.2067 +                    }
  1.2068 +                    pos.index = -i;
  1.2069 +                }
  1.2070 +                break parsing;
  1.2071 +
  1.2072 +            default:
  1.2073 +         // case PATTERN_DAY_OF_MONTH:         // 'd'
  1.2074 +         // case PATTERN_HOUR_OF_DAY0:         // 'H' 0-based.  eg, 23:59 + 1 hour =>> 00:59
  1.2075 +         // case PATTERN_MINUTE:               // 'm'
  1.2076 +         // case PATTERN_SECOND:               // 's'
  1.2077 +         // case PATTERN_MILLISECOND:          // 'S'
  1.2078 +         // case PATTERN_DAY_OF_YEAR:          // 'D'
  1.2079 +         // case PATTERN_DAY_OF_WEEK_IN_MONTH: // 'F'
  1.2080 +         // case PATTERN_WEEK_OF_YEAR:         // 'w'
  1.2081 +         // case PATTERN_WEEK_OF_MONTH:        // 'W'
  1.2082 +         // case PATTERN_HOUR0:                // 'K' 0-based.  eg, 11PM + 1 hour =>> 0 AM
  1.2083 +         // case PATTERN_ISO_DAY_OF_WEEK:      // 'u' (pseudo field);
  1.2084 +
  1.2085 +                // Handle "generic" fields
  1.2086 +                if (obeyCount) {
  1.2087 +                    if ((start+count) > text.length()) {
  1.2088 +                        break parsing;
  1.2089 +                    }
  1.2090 +                    number = numberFormat.parse(text.substring(0, start+count), pos);
  1.2091 +                } else {
  1.2092 +                    number = numberFormat.parse(text, pos);
  1.2093 +                }
  1.2094 +                if (number != null) {
  1.2095 +                    value = number.intValue();
  1.2096 +
  1.2097 +                    if (useFollowingMinusSignAsDelimiter && (value < 0) &&
  1.2098 +                        (((pos.index < text.length()) &&
  1.2099 +                         (text.charAt(pos.index) != minusSign)) ||
  1.2100 +                         ((pos.index == text.length()) &&
  1.2101 +                          (text.charAt(pos.index-1) == minusSign)))) {
  1.2102 +                        value = -value;
  1.2103 +                        pos.index--;
  1.2104 +                    }
  1.2105 +
  1.2106 +                    calb.set(field, value);
  1.2107 +                    return pos.index;
  1.2108 +                }
  1.2109 +                break parsing;
  1.2110 +            }
  1.2111 +        }
  1.2112 +
  1.2113 +        // Parsing failed.
  1.2114 +        origPos.errorIndex = pos.index;
  1.2115 +        return -1;
  1.2116 +    }
  1.2117 +
  1.2118 +    private final String getCalendarName() {
  1.2119 +        return calendar.getClass().getName();
  1.2120 +    }
  1.2121 +
  1.2122 +    private boolean useDateFormatSymbols() {
  1.2123 +        if (useDateFormatSymbols) {
  1.2124 +            return true;
  1.2125 +        }
  1.2126 +        return isGregorianCalendar() || locale == null;
  1.2127 +    }
  1.2128 +
  1.2129 +    private boolean isGregorianCalendar() {
  1.2130 +        return "java.util.GregorianCalendar".equals(getCalendarName());
  1.2131 +    }
  1.2132 +
  1.2133 +    /**
  1.2134 +     * Translates a pattern, mapping each character in the from string to the
  1.2135 +     * corresponding character in the to string.
  1.2136 +     *
  1.2137 +     * @exception IllegalArgumentException if the given pattern is invalid
  1.2138 +     */
  1.2139 +    private String translatePattern(String pattern, String from, String to) {
  1.2140 +        StringBuilder result = new StringBuilder();
  1.2141 +        boolean inQuote = false;
  1.2142 +        for (int i = 0; i < pattern.length(); ++i) {
  1.2143 +            char c = pattern.charAt(i);
  1.2144 +            if (inQuote) {
  1.2145 +                if (c == '\'')
  1.2146 +                    inQuote = false;
  1.2147 +            }
  1.2148 +            else {
  1.2149 +                if (c == '\'')
  1.2150 +                    inQuote = true;
  1.2151 +                else if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) {
  1.2152 +                    int ci = from.indexOf(c);
  1.2153 +                    if (ci >= 0) {
  1.2154 +                        // patternChars is longer than localPatternChars due
  1.2155 +                        // to serialization compatibility. The pattern letters
  1.2156 +                        // unsupported by localPatternChars pass through.
  1.2157 +                        if (ci < to.length()) {
  1.2158 +                            c = to.charAt(ci);
  1.2159 +                        }
  1.2160 +                    } else {
  1.2161 +                        throw new IllegalArgumentException("Illegal pattern " +
  1.2162 +                                                           " character '" +
  1.2163 +                                                           c + "'");
  1.2164 +                    }
  1.2165 +                }
  1.2166 +            }
  1.2167 +            result.append(c);
  1.2168 +        }
  1.2169 +        if (inQuote)
  1.2170 +            throw new IllegalArgumentException("Unfinished quote in pattern");
  1.2171 +        return result.toString();
  1.2172 +    }
  1.2173 +
  1.2174 +    /**
  1.2175 +     * Returns a pattern string describing this date format.
  1.2176 +     *
  1.2177 +     * @return a pattern string describing this date format.
  1.2178 +     */
  1.2179 +    public String toPattern() {
  1.2180 +        return pattern;
  1.2181 +    }
  1.2182 +
  1.2183 +    /**
  1.2184 +     * Returns a localized pattern string describing this date format.
  1.2185 +     *
  1.2186 +     * @return a localized pattern string describing this date format.
  1.2187 +     */
  1.2188 +    public String toLocalizedPattern() {
  1.2189 +        return translatePattern(pattern,
  1.2190 +                                DateFormatSymbols.patternChars,
  1.2191 +                                formatData.getLocalPatternChars());
  1.2192 +    }
  1.2193 +
  1.2194 +    /**
  1.2195 +     * Applies the given pattern string to this date format.
  1.2196 +     *
  1.2197 +     * @param pattern the new date and time pattern for this date format
  1.2198 +     * @exception NullPointerException if the given pattern is null
  1.2199 +     * @exception IllegalArgumentException if the given pattern is invalid
  1.2200 +     */
  1.2201 +    public void applyPattern(String pattern)
  1.2202 +    {
  1.2203 +        compiledPattern = compile(pattern);
  1.2204 +        this.pattern = pattern;
  1.2205 +    }
  1.2206 +
  1.2207 +    /**
  1.2208 +     * Applies the given localized pattern string to this date format.
  1.2209 +     *
  1.2210 +     * @param pattern a String to be mapped to the new date and time format
  1.2211 +     *        pattern for this format
  1.2212 +     * @exception NullPointerException if the given pattern is null
  1.2213 +     * @exception IllegalArgumentException if the given pattern is invalid
  1.2214 +     */
  1.2215 +    public void applyLocalizedPattern(String pattern) {
  1.2216 +         String p = translatePattern(pattern,
  1.2217 +                                     formatData.getLocalPatternChars(),
  1.2218 +                                     DateFormatSymbols.patternChars);
  1.2219 +         compiledPattern = compile(p);
  1.2220 +         this.pattern = p;
  1.2221 +    }
  1.2222 +
  1.2223 +    /**
  1.2224 +     * Gets a copy of the date and time format symbols of this date format.
  1.2225 +     *
  1.2226 +     * @return the date and time format symbols of this date format
  1.2227 +     * @see #setDateFormatSymbols
  1.2228 +     */
  1.2229 +    public DateFormatSymbols getDateFormatSymbols()
  1.2230 +    {
  1.2231 +        return (DateFormatSymbols)formatData.clone();
  1.2232 +    }
  1.2233 +
  1.2234 +    /**
  1.2235 +     * Sets the date and time format symbols of this date format.
  1.2236 +     *
  1.2237 +     * @param newFormatSymbols the new date and time format symbols
  1.2238 +     * @exception NullPointerException if the given newFormatSymbols is null
  1.2239 +     * @see #getDateFormatSymbols
  1.2240 +     */
  1.2241 +    public void setDateFormatSymbols(DateFormatSymbols newFormatSymbols)
  1.2242 +    {
  1.2243 +        this.formatData = (DateFormatSymbols)newFormatSymbols.clone();
  1.2244 +        useDateFormatSymbols = true;
  1.2245 +    }
  1.2246 +
  1.2247 +    /**
  1.2248 +     * Creates a copy of this <code>SimpleDateFormat</code>. This also
  1.2249 +     * clones the format's date format symbols.
  1.2250 +     *
  1.2251 +     * @return a clone of this <code>SimpleDateFormat</code>
  1.2252 +     */
  1.2253 +    public Object clone() {
  1.2254 +        SimpleDateFormat other = (SimpleDateFormat) super.clone();
  1.2255 +        other.formatData = (DateFormatSymbols) formatData.clone();
  1.2256 +        return other;
  1.2257 +    }
  1.2258 +
  1.2259 +    /**
  1.2260 +     * Returns the hash code value for this <code>SimpleDateFormat</code> object.
  1.2261 +     *
  1.2262 +     * @return the hash code value for this <code>SimpleDateFormat</code> object.
  1.2263 +     */
  1.2264 +    public int hashCode()
  1.2265 +    {
  1.2266 +        return pattern.hashCode();
  1.2267 +        // just enough fields for a reasonable distribution
  1.2268 +    }
  1.2269 +
  1.2270 +    /**
  1.2271 +     * Compares the given object with this <code>SimpleDateFormat</code> for
  1.2272 +     * equality.
  1.2273 +     *
  1.2274 +     * @return true if the given object is equal to this
  1.2275 +     * <code>SimpleDateFormat</code>
  1.2276 +     */
  1.2277 +    public boolean equals(Object obj)
  1.2278 +    {
  1.2279 +        if (!super.equals(obj)) return false; // super does class check
  1.2280 +        SimpleDateFormat that = (SimpleDateFormat) obj;
  1.2281 +        return (pattern.equals(that.pattern)
  1.2282 +                && formatData.equals(that.formatData));
  1.2283 +    }
  1.2284 +
  1.2285 +    /**
  1.2286 +     * After reading an object from the input stream, the format
  1.2287 +     * pattern in the object is verified.
  1.2288 +     * <p>
  1.2289 +     * @exception InvalidObjectException if the pattern is invalid
  1.2290 +     */
  1.2291 +    private void readObject(ObjectInputStream stream)
  1.2292 +                         throws IOException, ClassNotFoundException {
  1.2293 +        stream.defaultReadObject();
  1.2294 +
  1.2295 +        try {
  1.2296 +            compiledPattern = compile(pattern);
  1.2297 +        } catch (Exception e) {
  1.2298 +            throw new InvalidObjectException("invalid pattern");
  1.2299 +        }
  1.2300 +
  1.2301 +        if (serialVersionOnStream < 1) {
  1.2302 +            // didn't have defaultCenturyStart field
  1.2303 +            initializeDefaultCentury();
  1.2304 +        }
  1.2305 +        else {
  1.2306 +            // fill in dependent transient field
  1.2307 +            parseAmbiguousDatesAsAfter(defaultCenturyStart);
  1.2308 +        }
  1.2309 +        serialVersionOnStream = currentSerialVersion;
  1.2310 +
  1.2311 +        // If the deserialized object has a SimpleTimeZone, try
  1.2312 +        // to replace it with a ZoneInfo equivalent in order to
  1.2313 +        // be compatible with the SimpleTimeZone-based
  1.2314 +        // implementation as much as possible.
  1.2315 +        TimeZone tz = getTimeZone();
  1.2316 +        if (tz instanceof SimpleTimeZone) {
  1.2317 +            String id = tz.getID();
  1.2318 +            TimeZone zi = TimeZone.getTimeZone(id);
  1.2319 +            if (zi != null && zi.hasSameRules(tz) && zi.getID().equals(id)) {
  1.2320 +                setTimeZone(zi);
  1.2321 +            }
  1.2322 +        }
  1.2323 +    }
  1.2324 +
  1.2325 +    /**
  1.2326 +     * Analyze the negative subpattern of DecimalFormat and set/update values
  1.2327 +     * as necessary.
  1.2328 +     */
  1.2329 +    private void checkNegativeNumberExpression() {
  1.2330 +        if ((numberFormat instanceof DecimalFormat) &&
  1.2331 +            !numberFormat.equals(originalNumberFormat)) {
  1.2332 +            String numberPattern = ((DecimalFormat)numberFormat).toPattern();
  1.2333 +            if (!numberPattern.equals(originalNumberPattern)) {
  1.2334 +                hasFollowingMinusSign = false;
  1.2335 +
  1.2336 +                int separatorIndex = numberPattern.indexOf(';');
  1.2337 +                // If the negative subpattern is not absent, we have to analayze
  1.2338 +                // it in order to check if it has a following minus sign.
  1.2339 +                if (separatorIndex > -1) {
  1.2340 +                    int minusIndex = numberPattern.indexOf('-', separatorIndex);
  1.2341 +                    if ((minusIndex > numberPattern.lastIndexOf('0')) &&
  1.2342 +                        (minusIndex > numberPattern.lastIndexOf('#'))) {
  1.2343 +                        hasFollowingMinusSign = true;
  1.2344 +                        minusSign = ((DecimalFormat)numberFormat).getDecimalFormatSymbols().getMinusSign();
  1.2345 +                    }
  1.2346 +                }
  1.2347 +                originalNumberPattern = numberPattern;
  1.2348 +            }
  1.2349 +            originalNumberFormat = numberFormat;
  1.2350 +        }
  1.2351 +    }
  1.2352 +
  1.2353 +}