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 +}