1 /*
2 * Copyright (c) 1996, 2018, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation. Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26 /*
27 * (C) Copyright Taligent, Inc. 1996 - All Rights Reserved
28 * (C) Copyright IBM Corp. 1996-1998 - All Rights Reserved
29 *
30 * The original version of this source code and documentation is copyrighted
31 * and owned by Taligent, Inc., a wholly-owned subsidiary of IBM. These
32 * materials are provided under terms of a License Agreement between Taligent
33 * and Sun. This technology is protected by multiple US and International
34 * patents. This notice and attribution to Taligent may not be removed.
35 * Taligent is a registered trademark of Taligent, Inc.
36 *
37 */
38
39 package java.text;
40
41 import java.io.IOException;
42 import java.io.InvalidObjectException;
43 import java.io.ObjectInputStream;
44 import static java.text.DateFormatSymbols.*;
45 import java.util.Calendar;
46 import java.util.Date;
47 import java.util.GregorianCalendar;
48 import java.util.Locale;
49 import java.util.Map;
50 import java.util.SimpleTimeZone;
51 import java.util.SortedMap;
52 import java.util.TimeZone;
53 import java.util.concurrent.ConcurrentHashMap;
54 import java.util.concurrent.ConcurrentMap;
55 import sun.util.calendar.CalendarUtils;
56 import sun.util.calendar.ZoneInfoFile;
57 import sun.util.locale.provider.LocaleProviderAdapter;
58 import sun.util.locale.provider.TimeZoneNameUtility;
59
60 /**
61 * <code>SimpleDateFormat</code> is a concrete class for formatting and
62 * parsing dates in a locale-sensitive manner. It allows for formatting
63 * (date → text), parsing (text → date), and normalization.
64 *
65 * <p>
66 * <code>SimpleDateFormat</code> allows you to start by choosing
67 * any user-defined patterns for date-time formatting. However, you
68 * are encouraged to create a date-time formatter with either
69 * <code>getTimeInstance</code>, <code>getDateInstance</code>, or
70 * <code>getDateTimeInstance</code> in <code>DateFormat</code>. Each
71 * of these class methods can return a date/time formatter initialized
72 * with a default format pattern. You may modify the format pattern
73 * using the <code>applyPattern</code> methods as desired.
74 * For more information on using these methods, see
75 * {@link DateFormat}.
76 *
77 * <h3>Date and Time Patterns</h3>
78 * <p>
79 * Date and time formats are specified by <em>date and time pattern</em>
80 * strings.
81 * Within date and time pattern strings, unquoted letters from
82 * <code>'A'</code> to <code>'Z'</code> and from <code>'a'</code> to
83 * <code>'z'</code> are interpreted as pattern letters representing the
84 * components of a date or time string.
85 * Text can be quoted using single quotes (<code>'</code>) to avoid
86 * interpretation.
87 * <code>"''"</code> represents a single quote.
88 * All other characters are not interpreted; they're simply copied into the
89 * output string during formatting or matched against the input string
90 * during parsing.
91 * <p>
92 * The following pattern letters are defined (all other characters from
93 * <code>'A'</code> to <code>'Z'</code> and from <code>'a'</code> to
94 * <code>'z'</code> are reserved):
95 * <blockquote>
96 * <table class="striped">
97 * <caption style="display:none">Chart shows pattern letters, date/time component, presentation, and examples.</caption>
98 * <thead>
99 * <tr>
100 * <th scope="col" style="text-align:left">Letter
101 * <th scope="col" style="text-align:left">Date or Time Component
102 * <th scope="col" style="text-align:left">Presentation
103 * <th scope="col" style="text-align:left">Examples
104 * </thead>
105 * <tbody>
106 * <tr>
107 * <th scope="row"><code>G</code>
108 * <td>Era designator
109 * <td><a href="#text">Text</a>
110 * <td><code>AD</code>
111 * <tr>
112 * <th scope="row"><code>y</code>
113 * <td>Year
114 * <td><a href="#year">Year</a>
115 * <td><code>1996</code>; <code>96</code>
116 * <tr>
117 * <th scope="row"><code>Y</code>
118 * <td>Week year
119 * <td><a href="#year">Year</a>
120 * <td><code>2009</code>; <code>09</code>
121 * <tr>
122 * <th scope="row"><code>M</code>
123 * <td>Month in year (context sensitive)
124 * <td><a href="#month">Month</a>
125 * <td><code>July</code>; <code>Jul</code>; <code>07</code>
126 * <tr>
127 * <th scope="row"><code>L</code>
128 * <td>Month in year (standalone form)
129 * <td><a href="#month">Month</a>
130 * <td><code>July</code>; <code>Jul</code>; <code>07</code>
131 * <tr>
132 * <th scope="row"><code>w</code>
133 * <td>Week in year
134 * <td><a href="#number">Number</a>
135 * <td><code>27</code>
136 * <tr>
137 * <th scope="row"><code>W</code>
138 * <td>Week in month
139 * <td><a href="#number">Number</a>
140 * <td><code>2</code>
141 * <tr>
142 * <th scope="row"><code>D</code>
143 * <td>Day in year
144 * <td><a href="#number">Number</a>
145 * <td><code>189</code>
146 * <tr>
147 * <th scope="row"><code>d</code>
148 * <td>Day in month
149 * <td><a href="#number">Number</a>
150 * <td><code>10</code>
151 * <tr>
152 * <th scope="row"><code>F</code>
153 * <td>Day of week in month
154 * <td><a href="#number">Number</a>
155 * <td><code>2</code>
156 * <tr>
157 * <th scope="row"><code>E</code>
158 * <td>Day name in week
159 * <td><a href="#text">Text</a>
160 * <td><code>Tuesday</code>; <code>Tue</code>
161 * <tr>
162 * <th scope="row"><code>u</code>
163 * <td>Day number of week (1 = Monday, ..., 7 = Sunday)
164 * <td><a href="#number">Number</a>
165 * <td><code>1</code>
166 * <tr>
167 * <th scope="row"><code>a</code>
168 * <td>Am/pm marker
169 * <td><a href="#text">Text</a>
170 * <td><code>PM</code>
171 * <tr>
172 * <th scope="row"><code>H</code>
173 * <td>Hour in day (0-23)
174 * <td><a href="#number">Number</a>
175 * <td><code>0</code>
176 * <tr>
177 * <th scope="row"><code>k</code>
178 * <td>Hour in day (1-24)
179 * <td><a href="#number">Number</a>
180 * <td><code>24</code>
181 * <tr>
182 * <th scope="row"><code>K</code>
183 * <td>Hour in am/pm (0-11)
184 * <td><a href="#number">Number</a>
185 * <td><code>0</code>
186 * <tr>
187 * <th scope="row"><code>h</code>
188 * <td>Hour in am/pm (1-12)
189 * <td><a href="#number">Number</a>
190 * <td><code>12</code>
191 * <tr>
192 * <th scope="row"><code>m</code>
193 * <td>Minute in hour
194 * <td><a href="#number">Number</a>
195 * <td><code>30</code>
196 * <tr>
197 * <th scope="row"><code>s</code>
198 * <td>Second in minute
199 * <td><a href="#number">Number</a>
200 * <td><code>55</code>
201 * <tr>
202 * <th scope="row"><code>S</code>
203 * <td>Millisecond
204 * <td><a href="#number">Number</a>
205 * <td><code>978</code>
206 * <tr>
207 * <th scope="row"><code>z</code>
208 * <td>Time zone
209 * <td><a href="#timezone">General time zone</a>
210 * <td><code>Pacific Standard Time</code>; <code>PST</code>; <code>GMT-08:00</code>
211 * <tr>
212 * <th scope="row"><code>Z</code>
213 * <td>Time zone
214 * <td><a href="#rfc822timezone">RFC 822 time zone</a>
215 * <td><code>-0800</code>
216 * <tr>
217 * <th scope="row"><code>X</code>
218 * <td>Time zone
219 * <td><a href="#iso8601timezone">ISO 8601 time zone</a>
220 * <td><code>-08</code>; <code>-0800</code>; <code>-08:00</code>
221 * </tbody>
222 * </table>
223 * </blockquote>
224 * Pattern letters are usually repeated, as their number determines the
225 * exact presentation:
226 * <ul>
227 * <li><strong><a id="text">Text:</a></strong>
228 * For formatting, if the number of pattern letters is 4 or more,
229 * the full form is used; otherwise a short or abbreviated form
230 * is used if available.
231 * For parsing, both forms are accepted, independent of the number
232 * of pattern letters.<br><br></li>
233 * <li><strong><a id="number">Number:</a></strong>
234 * For formatting, the number of pattern letters is the minimum
235 * number of digits, and shorter numbers are zero-padded to this amount.
236 * For parsing, the number of pattern letters is ignored unless
237 * it's needed to separate two adjacent fields.<br><br></li>
238 * <li><strong><a id="year">Year:</a></strong>
239 * If the formatter's {@link #getCalendar() Calendar} is the Gregorian
240 * calendar, the following rules are applied.<br>
241 * <ul>
242 * <li>For formatting, if the number of pattern letters is 2, the year
243 * is truncated to 2 digits; otherwise it is interpreted as a
244 * <a href="#number">number</a>.
245 * <li>For parsing, if the number of pattern letters is more than 2,
246 * the year is interpreted literally, regardless of the number of
247 * digits. So using the pattern "MM/dd/yyyy", "01/11/12" parses to
248 * Jan 11, 12 A.D.
249 * <li>For parsing with the abbreviated year pattern ("y" or "yy"),
250 * <code>SimpleDateFormat</code> must interpret the abbreviated year
251 * relative to some century. It does this by adjusting dates to be
252 * within 80 years before and 20 years after the time the <code>SimpleDateFormat</code>
253 * instance is created. For example, using a pattern of "MM/dd/yy" and a
254 * <code>SimpleDateFormat</code> instance created on Jan 1, 1997, the string
255 * "01/11/12" would be interpreted as Jan 11, 2012 while the string "05/04/64"
256 * would be interpreted as May 4, 1964.
257 * During parsing, only strings consisting of exactly two digits, as defined by
258 * {@link Character#isDigit(char)}, will be parsed into the default century.
259 * Any other numeric string, such as a one digit string, a three or more digit
260 * string, or a two digit string that isn't all digits (for example, "-1"), is
261 * interpreted literally. So "01/02/3" or "01/02/003" are parsed, using the
262 * same pattern, as Jan 2, 3 AD. Likewise, "01/02/-3" is parsed as Jan 2, 4 BC.
263 * </ul>
264 * Otherwise, calendar system specific forms are applied.
265 * For both formatting and parsing, if the number of pattern
266 * letters is 4 or more, a calendar specific {@linkplain
267 * Calendar#LONG long form} is used. Otherwise, a calendar
268 * specific {@linkplain Calendar#SHORT short or abbreviated form}
269 * is used.<br>
270 * <br>
271 * If week year {@code 'Y'} is specified and the {@linkplain
272 * #getCalendar() calendar} doesn't support any <a
273 * href="../util/GregorianCalendar.html#week_year"> week
274 * years</a>, the calendar year ({@code 'y'}) is used instead. The
275 * support of week years can be tested with a call to {@link
276 * DateFormat#getCalendar() getCalendar()}.{@link
277 * java.util.Calendar#isWeekDateSupported()
278 * isWeekDateSupported()}.<br><br></li>
279 * <li><strong><a id="month">Month:</a></strong>
280 * If the number of pattern letters is 3 or more, the month is
281 * interpreted as <a href="#text">text</a>; otherwise,
282 * it is interpreted as a <a href="#number">number</a>.<br>
283 * <ul>
284 * <li>Letter <em>M</em> produces context-sensitive month names, such as the
285 * embedded form of names. Letter <em>M</em> is context-sensitive in the
286 * sense that when it is used in the standalone pattern, for example,
287 * "MMMM", it gives the standalone form of a month name and when it is
288 * used in the pattern containing other field(s), for example, "d MMMM",
289 * it gives the format form of a month name. For example, January in the
290 * Catalan language is "de gener" in the format form while it is "gener"
291 * in the standalone form. In this case, "MMMM" will produce "gener" and
292 * the month part of the "d MMMM" will produce "de gener". If a
293 * {@code DateFormatSymbols} has been set explicitly with constructor
294 * {@link #SimpleDateFormat(String,DateFormatSymbols)} or method {@link
295 * #setDateFormatSymbols(DateFormatSymbols)}, the month names given by
296 * the {@code DateFormatSymbols} are used.</li>
297 * <li>Letter <em>L</em> produces the standalone form of month names.</li>
298 * </ul>
299 * <br></li>
300 * <li><strong><a id="timezone">General time zone:</a></strong>
301 * Time zones are interpreted as <a href="#text">text</a> if they have
302 * names. For time zones representing a GMT offset value, the
303 * following syntax is used:
304 * <pre>
305 * <a id="GMTOffsetTimeZone"><i>GMTOffsetTimeZone:</i></a>
306 * <code>GMT</code> <i>Sign</i> <i>Hours</i> <code>:</code> <i>Minutes</i>
307 * <i>Sign:</i> one of
308 * <code>+ -</code>
309 * <i>Hours:</i>
310 * <i>Digit</i>
311 * <i>Digit</i> <i>Digit</i>
312 * <i>Minutes:</i>
313 * <i>Digit</i> <i>Digit</i>
314 * <i>Digit:</i> one of
315 * <code>0 1 2 3 4 5 6 7 8 9</code></pre>
316 * <i>Hours</i> must be between 0 and 23, and <i>Minutes</i> must be between
317 * 00 and 59. The format is locale independent and digits must be taken
318 * from the Basic Latin block of the Unicode standard.
319 * <p>For parsing, <a href="#rfc822timezone">RFC 822 time zones</a> are also
320 * accepted.<br><br></li>
321 * <li><strong><a id="rfc822timezone">RFC 822 time zone:</a></strong>
322 * For formatting, the RFC 822 4-digit time zone format is used:
323 *
324 * <pre>
325 * <i>RFC822TimeZone:</i>
326 * <i>Sign</i> <i>TwoDigitHours</i> <i>Minutes</i>
327 * <i>TwoDigitHours:</i>
328 * <i>Digit Digit</i></pre>
329 * <i>TwoDigitHours</i> must be between 00 and 23. Other definitions
330 * are as for <a href="#timezone">general time zones</a>.
331 *
332 * <p>For parsing, <a href="#timezone">general time zones</a> are also
333 * accepted.
334 * <li><strong><a id="iso8601timezone">ISO 8601 Time zone:</a></strong>
335 * The number of pattern letters designates the format for both formatting
336 * and parsing as follows:
337 * <pre>
338 * <i>ISO8601TimeZone:</i>
339 * <i>OneLetterISO8601TimeZone</i>
340 * <i>TwoLetterISO8601TimeZone</i>
341 * <i>ThreeLetterISO8601TimeZone</i>
342 * <i>OneLetterISO8601TimeZone:</i>
343 * <i>Sign</i> <i>TwoDigitHours</i>
344 * {@code Z}
345 * <i>TwoLetterISO8601TimeZone:</i>
346 * <i>Sign</i> <i>TwoDigitHours</i> <i>Minutes</i>
347 * {@code Z}
348 * <i>ThreeLetterISO8601TimeZone:</i>
349 * <i>Sign</i> <i>TwoDigitHours</i> {@code :} <i>Minutes</i>
350 * {@code Z}</pre>
351 * Other definitions are as for <a href="#timezone">general time zones</a> or
352 * <a href="#rfc822timezone">RFC 822 time zones</a>.
353 *
354 * <p>For formatting, if the offset value from GMT is 0, {@code "Z"} is
355 * produced. If the number of pattern letters is 1, any fraction of an hour
356 * is ignored. For example, if the pattern is {@code "X"} and the time zone is
357 * {@code "GMT+05:30"}, {@code "+05"} is produced.
358 *
359 * <p>For parsing, {@code "Z"} is parsed as the UTC time zone designator.
360 * <a href="#timezone">General time zones</a> are <em>not</em> accepted.
361 *
362 * <p>If the number of pattern letters is 4 or more, {@link
363 * IllegalArgumentException} is thrown when constructing a {@code
364 * SimpleDateFormat} or {@linkplain #applyPattern(String) applying a
365 * pattern}.
366 * </ul>
367 * <code>SimpleDateFormat</code> also supports <em>localized date and time
368 * pattern</em> strings. In these strings, the pattern letters described above
369 * may be replaced with other, locale dependent, pattern letters.
370 * <code>SimpleDateFormat</code> does not deal with the localization of text
371 * other than the pattern letters; that's up to the client of the class.
372 *
373 * <h4>Examples</h4>
374 *
375 * The following examples show how date and time patterns are interpreted in
376 * the U.S. locale. The given date and time are 2001-07-04 12:08:56 local time
377 * in the U.S. Pacific Time time zone.
378 * <blockquote>
379 * <table class="striped">
380 * <caption style="display:none">Examples of date and time patterns interpreted in the U.S. locale</caption>
381 * <thead>
382 * <tr>
383 * <th scope="col" style="text-align:left">Date and Time Pattern
384 * <th scope="col" style="text-align:left">Result
385 * </thead>
386 * <tbody>
387 * <tr>
388 * <th scope="row"><code>"yyyy.MM.dd G 'at' HH:mm:ss z"</code>
389 * <td><code>2001.07.04 AD at 12:08:56 PDT</code>
390 * <tr>
391 * <th scope="row"><code>"EEE, MMM d, ''yy"</code>
392 * <td><code>Wed, Jul 4, '01</code>
393 * <tr>
394 * <th scope="row"><code>"h:mm a"</code>
395 * <td><code>12:08 PM</code>
396 * <tr>
397 * <th scope="row"><code>"hh 'o''clock' a, zzzz"</code>
398 * <td><code>12 o'clock PM, Pacific Daylight Time</code>
399 * <tr>
400 * <th scope="row"><code>"K:mm a, z"</code>
401 * <td><code>0:08 PM, PDT</code>
402 * <tr>
403 * <th scope="row"><code>"yyyyy.MMMMM.dd GGG hh:mm aaa"</code>
404 * <td><code>02001.July.04 AD 12:08 PM</code>
405 * <tr>
406 * <th scope="row"><code>"EEE, d MMM yyyy HH:mm:ss Z"</code>
407 * <td><code>Wed, 4 Jul 2001 12:08:56 -0700</code>
408 * <tr>
409 * <th scope="row"><code>"yyMMddHHmmssZ"</code>
410 * <td><code>010704120856-0700</code>
411 * <tr>
412 * <th scope="row"><code>"yyyy-MM-dd'T'HH:mm:ss.SSSZ"</code>
413 * <td><code>2001-07-04T12:08:56.235-0700</code>
414 * <tr>
415 * <th scope="row"><code>"yyyy-MM-dd'T'HH:mm:ss.SSSXXX"</code>
416 * <td><code>2001-07-04T12:08:56.235-07:00</code>
417 * <tr>
418 * <th scope="row"><code>"YYYY-'W'ww-u"</code>
419 * <td><code>2001-W27-3</code>
420 * </tbody>
421 * </table>
422 * </blockquote>
423 *
424 * <h4><a id="synchronization">Synchronization</a></h4>
425 *
426 * <p>
427 * Date formats are not synchronized.
428 * It is recommended to create separate format instances for each thread.
429 * If multiple threads access a format concurrently, it must be synchronized
430 * externally.
431 *
432 * @see <a href="http://docs.oracle.com/javase/tutorial/i18n/format/simpleDateFormat.html">Java Tutorial</a>
433 * @see java.util.Calendar
434 * @see java.util.TimeZone
435 * @see DateFormat
436 * @see DateFormatSymbols
437 * @author Mark Davis, Chen-Lieh Huang, Alan Liu
438 * @since 1.1
439 */
440 public class SimpleDateFormat extends DateFormat {
441
442 // the official serial version ID which says cryptically
443 // which version we're compatible with
444 static final long serialVersionUID = 4774881970558875024L;
445
446 // the internal serial version which says which version was written
447 // - 0 (default) for version up to JDK 1.1.3
448 // - 1 for version from JDK 1.1.4, which includes a new field
449 static final int currentSerialVersion = 1;
450
451 /**
452 * The version of the serialized data on the stream. Possible values:
453 * <ul>
454 * <li><b>0</b> or not present on stream: JDK 1.1.3. This version
455 * has no <code>defaultCenturyStart</code> on stream.
456 * <li><b>1</b> JDK 1.1.4 or later. This version adds
457 * <code>defaultCenturyStart</code>.
458 * </ul>
459 * When streaming out this class, the most recent format
460 * and the highest allowable <code>serialVersionOnStream</code>
461 * is written.
462 * @serial
463 * @since 1.1.4
464 */
465 private int serialVersionOnStream = currentSerialVersion;
466
467 /**
468 * The pattern string of this formatter. This is always a non-localized
469 * pattern. May not be null. See class documentation for details.
470 * @serial
471 */
472 private String pattern;
473
474 /**
475 * Saved numberFormat and pattern.
476 * @see SimpleDateFormat#checkNegativeNumberExpression
477 */
478 private transient NumberFormat originalNumberFormat;
479 private transient String originalNumberPattern;
480
481 /**
482 * The minus sign to be used with format and parse.
483 */
484 private transient char minusSign = '-';
485
486 /**
487 * True when a negative sign follows a number.
488 * (True as default in Arabic.)
489 */
490 private transient boolean hasFollowingMinusSign = false;
491
492 /**
493 * True if standalone form needs to be used.
494 */
495 private transient boolean forceStandaloneForm = false;
496
497 /**
498 * The compiled pattern.
499 */
500 private transient char[] compiledPattern;
501
502 /**
503 * Tags for the compiled pattern.
504 */
505 private static final int TAG_QUOTE_ASCII_CHAR = 100;
506 private static final int TAG_QUOTE_CHARS = 101;
507
508 /**
509 * Locale dependent digit zero.
510 * @see #zeroPaddingNumber
511 * @see java.text.DecimalFormatSymbols#getZeroDigit
512 */
513 private transient char zeroDigit;
514
515 /**
516 * The symbols used by this formatter for week names, month names,
517 * etc. May not be null.
518 * @serial
519 * @see java.text.DateFormatSymbols
520 */
521 private DateFormatSymbols formatData;
522
523 /**
524 * We map dates with two-digit years into the century starting at
525 * <code>defaultCenturyStart</code>, which may be any date. May
526 * not be null.
527 * @serial
528 * @since 1.1.4
529 */
530 private Date defaultCenturyStart;
531
532 private transient int defaultCenturyStartYear;
533
534 private static final int MILLIS_PER_MINUTE = 60 * 1000;
535
536 // For time zones that have no names, use strings GMT+minutes and
537 // GMT-minutes. For instance, in France the time zone is GMT+60.
538 private static final String GMT = "GMT";
539
540 /**
541 * Cache NumberFormat instances with Locale key.
542 */
543 private static final ConcurrentMap<Locale, NumberFormat> cachedNumberFormatData
544 = new ConcurrentHashMap<>(3);
545
546 /**
547 * The Locale used to instantiate this
548 * <code>SimpleDateFormat</code>. The value may be null if this object
549 * has been created by an older <code>SimpleDateFormat</code> and
550 * deserialized.
551 *
552 * @serial
553 * @since 1.6
554 */
555 private Locale locale;
556
557 /**
558 * Indicates whether this <code>SimpleDateFormat</code> should use
559 * the DateFormatSymbols. If true, the format and parse methods
560 * use the DateFormatSymbols values. If false, the format and
561 * parse methods call Calendar.getDisplayName or
562 * Calendar.getDisplayNames.
563 */
564 transient boolean useDateFormatSymbols;
565
566 /**
567 * Constructs a <code>SimpleDateFormat</code> using the default pattern and
568 * date format symbols for the default
569 * {@link java.util.Locale.Category#FORMAT FORMAT} locale.
570 * <b>Note:</b> This constructor may not support all locales.
571 * For full coverage, use the factory methods in the {@link DateFormat}
572 * class.
573 */
574 public SimpleDateFormat() {
575 this("", Locale.getDefault(Locale.Category.FORMAT));
576 applyPatternImpl(LocaleProviderAdapter.getResourceBundleBased().getLocaleResources(locale)
577 .getDateTimePattern(SHORT, SHORT, calendar));
578 }
579
580 /**
581 * Constructs a <code>SimpleDateFormat</code> using the given pattern and
582 * the default date format symbols for the default
583 * {@link java.util.Locale.Category#FORMAT FORMAT} locale.
584 * <b>Note:</b> This constructor may not support all locales.
585 * For full coverage, use the factory methods in the {@link DateFormat}
586 * class.
587 * <p>This is equivalent to calling
588 * {@link #SimpleDateFormat(String, Locale)
589 * SimpleDateFormat(pattern, Locale.getDefault(Locale.Category.FORMAT))}.
590 *
591 * @see java.util.Locale#getDefault(java.util.Locale.Category)
592 * @see java.util.Locale.Category#FORMAT
593 * @param pattern the pattern describing the date and time format
594 * @exception NullPointerException if the given pattern is null
595 * @exception IllegalArgumentException if the given pattern is invalid
596 */
597 public SimpleDateFormat(String pattern)
598 {
599 this(pattern, Locale.getDefault(Locale.Category.FORMAT));
600 }
601
602 /**
603 * Constructs a <code>SimpleDateFormat</code> using the given pattern and
604 * the default date format symbols for the given locale.
605 * <b>Note:</b> This constructor may not support all locales.
606 * For full coverage, use the factory methods in the {@link DateFormat}
607 * class.
608 *
609 * @param pattern the pattern describing the date and time format
610 * @param locale the locale whose date format symbols should be used
611 * @exception NullPointerException if the given pattern or locale is null
612 * @exception IllegalArgumentException if the given pattern is invalid
613 */
614 public SimpleDateFormat(String pattern, Locale locale)
615 {
616 if (pattern == null || locale == null) {
617 throw new NullPointerException();
618 }
619
620 initializeCalendar(locale);
621 this.pattern = pattern;
622 this.formatData = DateFormatSymbols.getInstanceRef(locale);
623 this.locale = locale;
624 initialize(locale);
625 }
626
627 /**
628 * Constructs a <code>SimpleDateFormat</code> using the given pattern and
629 * date format symbols.
630 *
631 * @param pattern the pattern describing the date and time format
632 * @param formatSymbols the date format symbols to be used for formatting
633 * @exception NullPointerException if the given pattern or formatSymbols is null
634 * @exception IllegalArgumentException if the given pattern is invalid
635 */
636 public SimpleDateFormat(String pattern, DateFormatSymbols formatSymbols)
637 {
638 if (pattern == null || formatSymbols == null) {
639 throw new NullPointerException();
640 }
641
642 this.pattern = pattern;
643 this.formatData = (DateFormatSymbols) formatSymbols.clone();
644 this.locale = Locale.getDefault(Locale.Category.FORMAT);
645 initializeCalendar(this.locale);
646 initialize(this.locale);
647 useDateFormatSymbols = true;
648 }
649
650 /* Initialize compiledPattern and numberFormat fields */
651 private void initialize(Locale loc) {
652 // Verify and compile the given pattern.
653 compiledPattern = compile(pattern);
654
655 /* try the cache first */
656 numberFormat = cachedNumberFormatData.get(loc);
657 if (numberFormat == null) { /* cache miss */
658 numberFormat = NumberFormat.getIntegerInstance(loc);
659 numberFormat.setGroupingUsed(false);
660
661 /* update cache */
662 cachedNumberFormatData.putIfAbsent(loc, numberFormat);
663 }
664 numberFormat = (NumberFormat) numberFormat.clone();
665
666 initializeDefaultCentury();
667 }
668
669 private void initializeCalendar(Locale loc) {
670 if (calendar == null) {
671 assert loc != null;
672 // The format object must be constructed using the symbols for this zone.
673 // However, the calendar should use the current default TimeZone.
674 // If this is not contained in the locale zone strings, then the zone
675 // will be formatted using generic GMT+/-H:MM nomenclature.
676 calendar = Calendar.getInstance(loc);
677 }
678 }
679
680 /**
681 * Returns the compiled form of the given pattern. The syntax of
682 * the compiled pattern is:
683 * <blockquote>
684 * CompiledPattern:
685 * EntryList
686 * EntryList:
687 * Entry
688 * EntryList Entry
689 * Entry:
690 * TagField
691 * TagField data
692 * TagField:
693 * Tag Length
694 * TaggedData
695 * Tag:
696 * pattern_char_index
697 * TAG_QUOTE_CHARS
698 * Length:
699 * short_length
700 * long_length
701 * TaggedData:
702 * TAG_QUOTE_ASCII_CHAR ascii_char
703 *
704 * </blockquote>
705 *
706 * where `short_length' is an 8-bit unsigned integer between 0 and
707 * 254. `long_length' is a sequence of an 8-bit integer 255 and a
708 * 32-bit signed integer value which is split into upper and lower
709 * 16-bit fields in two char's. `pattern_char_index' is an 8-bit
710 * integer between 0 and 18. `ascii_char' is an 7-bit ASCII
711 * character value. `data' depends on its Tag value.
712 * <p>
713 * If Length is short_length, Tag and short_length are packed in a
714 * single char, as illustrated below.
715 * <blockquote>
716 * char[0] = (Tag << 8) | short_length;
717 * </blockquote>
718 *
719 * If Length is long_length, Tag and 255 are packed in the first
720 * char and a 32-bit integer, as illustrated below.
721 * <blockquote>
722 * char[0] = (Tag << 8) | 255;
723 * char[1] = (char) (long_length >>> 16);
724 * char[2] = (char) (long_length & 0xffff);
725 * </blockquote>
726 * <p>
727 * If Tag is a pattern_char_index, its Length is the number of
728 * pattern characters. For example, if the given pattern is
729 * "yyyy", Tag is 1 and Length is 4, followed by no data.
730 * <p>
731 * If Tag is TAG_QUOTE_CHARS, its Length is the number of char's
732 * following the TagField. For example, if the given pattern is
733 * "'o''clock'", Length is 7 followed by a char sequence of
734 * <code>o&nbs;'&nbs;c&nbs;l&nbs;o&nbs;c&nbs;k</code>.
735 * <p>
736 * TAG_QUOTE_ASCII_CHAR is a special tag and has an ASCII
737 * character in place of Length. For example, if the given pattern
738 * is "'o'", the TaggedData entry is
739 * <code>((TAG_QUOTE_ASCII_CHAR&nbs;<<&nbs;8)&nbs;|&nbs;'o')</code>.
740 *
741 * @exception NullPointerException if the given pattern is null
742 * @exception IllegalArgumentException if the given pattern is invalid
743 */
744 private char[] compile(String pattern) {
745 int length = pattern.length();
746 boolean inQuote = false;
747 StringBuilder compiledCode = new StringBuilder(length * 2);
748 StringBuilder tmpBuffer = null;
749 int count = 0, tagcount = 0;
750 int lastTag = -1, prevTag = -1;
751
752 for (int i = 0; i < length; i++) {
753 char c = pattern.charAt(i);
754
755 if (c == '\'') {
756 // '' is treated as a single quote regardless of being
757 // in a quoted section.
758 if ((i + 1) < length) {
759 c = pattern.charAt(i + 1);
760 if (c == '\'') {
761 i++;
762 if (count != 0) {
763 encode(lastTag, count, compiledCode);
764 tagcount++;
765 prevTag = lastTag;
766 lastTag = -1;
767 count = 0;
768 }
769 if (inQuote) {
770 tmpBuffer.append(c);
771 } else {
772 compiledCode.append((char)(TAG_QUOTE_ASCII_CHAR << 8 | c));
773 }
774 continue;
775 }
776 }
777 if (!inQuote) {
778 if (count != 0) {
779 encode(lastTag, count, compiledCode);
780 tagcount++;
781 prevTag = lastTag;
782 lastTag = -1;
783 count = 0;
784 }
785 if (tmpBuffer == null) {
786 tmpBuffer = new StringBuilder(length);
787 } else {
788 tmpBuffer.setLength(0);
789 }
790 inQuote = true;
791 } else {
792 int len = tmpBuffer.length();
793 if (len == 1) {
794 char ch = tmpBuffer.charAt(0);
795 if (ch < 128) {
796 compiledCode.append((char)(TAG_QUOTE_ASCII_CHAR << 8 | ch));
797 } else {
798 compiledCode.append((char)(TAG_QUOTE_CHARS << 8 | 1));
799 compiledCode.append(ch);
800 }
801 } else {
802 encode(TAG_QUOTE_CHARS, len, compiledCode);
803 compiledCode.append(tmpBuffer);
804 }
805 inQuote = false;
806 }
807 continue;
808 }
809 if (inQuote) {
810 tmpBuffer.append(c);
811 continue;
812 }
813 if (!(c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z')) {
814 if (count != 0) {
815 encode(lastTag, count, compiledCode);
816 tagcount++;
817 prevTag = lastTag;
818 lastTag = -1;
819 count = 0;
820 }
821 if (c < 128) {
822 // In most cases, c would be a delimiter, such as ':'.
823 compiledCode.append((char)(TAG_QUOTE_ASCII_CHAR << 8 | c));
824 } else {
825 // Take any contiguous non-ASCII alphabet characters and
826 // put them in a single TAG_QUOTE_CHARS.
827 int j;
828 for (j = i + 1; j < length; j++) {
829 char d = pattern.charAt(j);
830 if (d == '\'' || (d >= 'a' && d <= 'z' || d >= 'A' && d <= 'Z')) {
831 break;
832 }
833 }
834 compiledCode.append((char)(TAG_QUOTE_CHARS << 8 | (j - i)));
835 for (; i < j; i++) {
836 compiledCode.append(pattern.charAt(i));
837 }
838 i--;
839 }
840 continue;
841 }
842
843 int tag;
844 if ((tag = DateFormatSymbols.patternChars.indexOf(c)) == -1) {
845 throw new IllegalArgumentException("Illegal pattern character " +
846 "'" + c + "'");
847 }
848 if (lastTag == -1 || lastTag == tag) {
849 lastTag = tag;
850 count++;
851 continue;
852 }
853 encode(lastTag, count, compiledCode);
854 tagcount++;
855 prevTag = lastTag;
856 lastTag = tag;
857 count = 1;
858 }
859
860 if (inQuote) {
861 throw new IllegalArgumentException("Unterminated quote");
862 }
863
864 if (count != 0) {
865 encode(lastTag, count, compiledCode);
866 tagcount++;
867 prevTag = lastTag;
868 }
869
870 forceStandaloneForm = (tagcount == 1 && prevTag == PATTERN_MONTH);
871
872 // Copy the compiled pattern to a char array
873 int len = compiledCode.length();
874 char[] r = new char[len];
875 compiledCode.getChars(0, len, r, 0);
876 return r;
877 }
878
879 /**
880 * Encodes the given tag and length and puts encoded char(s) into buffer.
881 */
882 private static void encode(int tag, int length, StringBuilder buffer) {
883 if (tag == PATTERN_ISO_ZONE && length >= 4) {
884 throw new IllegalArgumentException("invalid ISO 8601 format: length=" + length);
885 }
886 if (length < 255) {
887 buffer.append((char)(tag << 8 | length));
888 } else {
889 buffer.append((char)((tag << 8) | 0xff));
890 buffer.append((char)(length >>> 16));
891 buffer.append((char)(length & 0xffff));
892 }
893 }
894
895 /* Initialize the fields we use to disambiguate ambiguous years. Separate
896 * so we can call it from readObject().
897 */
898 private void initializeDefaultCentury() {
899 calendar.setTimeInMillis(System.currentTimeMillis());
900 calendar.add( Calendar.YEAR, -80 );
901 parseAmbiguousDatesAsAfter(calendar.getTime());
902 }
903
904 /* Define one-century window into which to disambiguate dates using
905 * two-digit years.
906 */
907 private void parseAmbiguousDatesAsAfter(Date startDate) {
908 defaultCenturyStart = startDate;
909 calendar.setTime(startDate);
910 defaultCenturyStartYear = calendar.get(Calendar.YEAR);
911 }
912
913 /**
914 * Sets the 100-year period 2-digit years will be interpreted as being in
915 * to begin on the date the user specifies.
916 *
917 * @param startDate During parsing, two digit years will be placed in the range
918 * <code>startDate</code> to <code>startDate + 100 years</code>.
919 * @see #get2DigitYearStart
920 * @throws NullPointerException if {@code startDate} is {@code null}.
921 * @since 1.2
922 */
923 public void set2DigitYearStart(Date startDate) {
924 parseAmbiguousDatesAsAfter(new Date(startDate.getTime()));
925 }
926
927 /**
928 * Returns the beginning date of the 100-year period 2-digit years are interpreted
929 * as being within.
930 *
931 * @return the start of the 100-year period into which two digit years are
932 * parsed
933 * @see #set2DigitYearStart
934 * @since 1.2
935 */
936 public Date get2DigitYearStart() {
937 return (Date) defaultCenturyStart.clone();
938 }
939
940 /**
941 * Formats the given <code>Date</code> into a date/time string and appends
942 * the result to the given <code>StringBuffer</code>.
943 *
944 * @param date the date-time value to be formatted into a date-time string.
945 * @param toAppendTo where the new date-time text is to be appended.
946 * @param pos keeps track on the position of the field within
947 * the returned string. For example, given a date-time text
948 * {@code "1996.07.10 AD at 15:08:56 PDT"}, if the given {@code fieldPosition}
949 * is {@link DateFormat#YEAR_FIELD}, the begin index and end index of
950 * {@code fieldPosition} will be set to 0 and 4, respectively.
951 * Notice that if the same date-time field appears more than once in a
952 * pattern, the {@code fieldPosition} will be set for the first occurrence
953 * of that date-time field. For instance, formatting a {@code Date} to the
954 * date-time string {@code "1 PM PDT (Pacific Daylight Time)"} using the
955 * pattern {@code "h a z (zzzz)"} and the alignment field
956 * {@link DateFormat#TIMEZONE_FIELD}, the begin index and end index of
957 * {@code fieldPosition} will be set to 5 and 8, respectively, for the
958 * first occurrence of the timezone pattern character {@code 'z'}.
959 * @return the formatted date-time string.
960 * @exception NullPointerException if any of the parameters is {@code null}.
961 */
962 @Override
963 public StringBuffer format(Date date, StringBuffer toAppendTo,
964 FieldPosition pos)
965 {
966 pos.beginIndex = pos.endIndex = 0;
967 return format(date, toAppendTo, pos.getFieldDelegate());
968 }
969
970 // Called from Format after creating a FieldDelegate
971 private StringBuffer format(Date date, StringBuffer toAppendTo,
972 FieldDelegate delegate) {
973 // Convert input date to time field list
974 calendar.setTime(date);
975
976 boolean useDateFormatSymbols = useDateFormatSymbols();
977
978 for (int i = 0; i < compiledPattern.length; ) {
979 int tag = compiledPattern[i] >>> 8;
980 int count = compiledPattern[i++] & 0xff;
981 if (count == 255) {
982 count = compiledPattern[i++] << 16;
983 count |= compiledPattern[i++];
984 }
985
986 switch (tag) {
987 case TAG_QUOTE_ASCII_CHAR:
988 toAppendTo.append((char)count);
989 break;
990
991 case TAG_QUOTE_CHARS:
992 toAppendTo.append(compiledPattern, i, count);
993 i += count;
994 break;
995
996 default:
997 subFormat(tag, count, delegate, toAppendTo, useDateFormatSymbols);
998 break;
999 }
1000 }
1001 return toAppendTo;
1002 }
1003
1004 /**
1005 * Formats an Object producing an <code>AttributedCharacterIterator</code>.
1006 * You can use the returned <code>AttributedCharacterIterator</code>
1007 * to build the resulting String, as well as to determine information
1008 * about the resulting String.
1009 * <p>
1010 * Each attribute key of the AttributedCharacterIterator will be of type
1011 * <code>DateFormat.Field</code>, with the corresponding attribute value
1012 * being the same as the attribute key.
1013 *
1014 * @exception NullPointerException if obj is null.
1015 * @exception IllegalArgumentException if the Format cannot format the
1016 * given object, or if the Format's pattern string is invalid.
1017 * @param obj The object to format
1018 * @return AttributedCharacterIterator describing the formatted value.
1019 * @since 1.4
1020 */
1021 @Override
1022 public AttributedCharacterIterator formatToCharacterIterator(Object obj) {
1023 StringBuffer sb = new StringBuffer();
1024 CharacterIteratorFieldDelegate delegate = new
1025 CharacterIteratorFieldDelegate();
1026
1027 if (obj instanceof Date) {
1028 format((Date)obj, sb, delegate);
1029 }
1030 else if (obj instanceof Number) {
1031 format(new Date(((Number)obj).longValue()), sb, delegate);
1032 }
1033 else if (obj == null) {
1034 throw new NullPointerException(
1035 "formatToCharacterIterator must be passed non-null object");
1036 }
1037 else {
1038 throw new IllegalArgumentException(
1039 "Cannot format given Object as a Date");
1040 }
1041 return delegate.getIterator(sb.toString());
1042 }
1043
1044 // Map index into pattern character string to Calendar field number
1045 private static final int[] PATTERN_INDEX_TO_CALENDAR_FIELD = {
1046 Calendar.ERA,
1047 Calendar.YEAR,
1048 Calendar.MONTH,
1049 Calendar.DATE,
1050 Calendar.HOUR_OF_DAY,
1051 Calendar.HOUR_OF_DAY,
1052 Calendar.MINUTE,
1053 Calendar.SECOND,
1054 Calendar.MILLISECOND,
1055 Calendar.DAY_OF_WEEK,
1056 Calendar.DAY_OF_YEAR,
1057 Calendar.DAY_OF_WEEK_IN_MONTH,
1058 Calendar.WEEK_OF_YEAR,
1059 Calendar.WEEK_OF_MONTH,
1060 Calendar.AM_PM,
1061 Calendar.HOUR,
1062 Calendar.HOUR,
1063 Calendar.ZONE_OFFSET,
1064 Calendar.ZONE_OFFSET,
1065 CalendarBuilder.WEEK_YEAR, // Pseudo Calendar field
1066 CalendarBuilder.ISO_DAY_OF_WEEK, // Pseudo Calendar field
1067 Calendar.ZONE_OFFSET,
1068 Calendar.MONTH
1069 };
1070
1071 // Map index into pattern character string to DateFormat field number
1072 private static final int[] PATTERN_INDEX_TO_DATE_FORMAT_FIELD = {
1073 DateFormat.ERA_FIELD,
1074 DateFormat.YEAR_FIELD,
1075 DateFormat.MONTH_FIELD,
1076 DateFormat.DATE_FIELD,
1077 DateFormat.HOUR_OF_DAY1_FIELD,
1078 DateFormat.HOUR_OF_DAY0_FIELD,
1079 DateFormat.MINUTE_FIELD,
1080 DateFormat.SECOND_FIELD,
1081 DateFormat.MILLISECOND_FIELD,
1082 DateFormat.DAY_OF_WEEK_FIELD,
1083 DateFormat.DAY_OF_YEAR_FIELD,
1084 DateFormat.DAY_OF_WEEK_IN_MONTH_FIELD,
1085 DateFormat.WEEK_OF_YEAR_FIELD,
1086 DateFormat.WEEK_OF_MONTH_FIELD,
1087 DateFormat.AM_PM_FIELD,
1088 DateFormat.HOUR1_FIELD,
1089 DateFormat.HOUR0_FIELD,
1090 DateFormat.TIMEZONE_FIELD,
1091 DateFormat.TIMEZONE_FIELD,
1092 DateFormat.YEAR_FIELD,
1093 DateFormat.DAY_OF_WEEK_FIELD,
1094 DateFormat.TIMEZONE_FIELD,
1095 DateFormat.MONTH_FIELD
1096 };
1097
1098 // Maps from DecimalFormatSymbols index to Field constant
1099 private static final Field[] PATTERN_INDEX_TO_DATE_FORMAT_FIELD_ID = {
1100 Field.ERA,
1101 Field.YEAR,
1102 Field.MONTH,
1103 Field.DAY_OF_MONTH,
1104 Field.HOUR_OF_DAY1,
1105 Field.HOUR_OF_DAY0,
1106 Field.MINUTE,
1107 Field.SECOND,
1108 Field.MILLISECOND,
1109 Field.DAY_OF_WEEK,
1110 Field.DAY_OF_YEAR,
1111 Field.DAY_OF_WEEK_IN_MONTH,
1112 Field.WEEK_OF_YEAR,
1113 Field.WEEK_OF_MONTH,
1114 Field.AM_PM,
1115 Field.HOUR1,
1116 Field.HOUR0,
1117 Field.TIME_ZONE,
1118 Field.TIME_ZONE,
1119 Field.YEAR,
1120 Field.DAY_OF_WEEK,
1121 Field.TIME_ZONE,
1122 Field.MONTH
1123 };
1124
1125 /**
1126 * Private member function that does the real date/time formatting.
1127 */
1128 private void subFormat(int patternCharIndex, int count,
1129 FieldDelegate delegate, StringBuffer buffer,
1130 boolean useDateFormatSymbols)
1131 {
1132 int maxIntCount = Integer.MAX_VALUE;
1133 String current = null;
1134 int beginOffset = buffer.length();
1135
1136 int field = PATTERN_INDEX_TO_CALENDAR_FIELD[patternCharIndex];
1137 int value;
1138 if (field == CalendarBuilder.WEEK_YEAR) {
1139 if (calendar.isWeekDateSupported()) {
1140 value = calendar.getWeekYear();
1141 } else {
1142 // use calendar year 'y' instead
1143 patternCharIndex = PATTERN_YEAR;
1144 field = PATTERN_INDEX_TO_CALENDAR_FIELD[patternCharIndex];
1145 value = calendar.get(field);
1146 }
1147 } else if (field == CalendarBuilder.ISO_DAY_OF_WEEK) {
1148 value = CalendarBuilder.toISODayOfWeek(calendar.get(Calendar.DAY_OF_WEEK));
1149 } else {
1150 value = calendar.get(field);
1151 }
1152
1153 int style = (count >= 4) ? Calendar.LONG : Calendar.SHORT;
1154 if (!useDateFormatSymbols && field < Calendar.ZONE_OFFSET
1155 && patternCharIndex != PATTERN_MONTH_STANDALONE) {
1156 current = calendar.getDisplayName(field, style, locale);
1157 }
1158
1159 // Note: zeroPaddingNumber() assumes that maxDigits is either
1160 // 2 or maxIntCount. If we make any changes to this,
1161 // zeroPaddingNumber() must be fixed.
1162
1163 switch (patternCharIndex) {
1164 case PATTERN_ERA: // 'G'
1165 if (useDateFormatSymbols) {
1166 String[] eras = formatData.getEras();
1167 if (value < eras.length) {
1168 current = eras[value];
1169 }
1170 }
1171 if (current == null) {
1172 current = "";
1173 }
1174 break;
1175
1176 case PATTERN_WEEK_YEAR: // 'Y'
1177 case PATTERN_YEAR: // 'y'
1178 if (calendar instanceof GregorianCalendar) {
1179 if (count != 2) {
1180 zeroPaddingNumber(value, count, maxIntCount, buffer);
1181 } else {
1182 zeroPaddingNumber(value, 2, 2, buffer);
1183 } // clip 1996 to 96
1184 } else {
1185 if (current == null) {
1186 zeroPaddingNumber(value, style == Calendar.LONG ? 1 : count,
1187 maxIntCount, buffer);
1188 }
1189 }
1190 break;
1191
1192 case PATTERN_MONTH: // 'M' (context seinsive)
1193 if (useDateFormatSymbols) {
1194 String[] months;
1195 if (count >= 4) {
1196 months = formatData.getMonths();
1197 current = months[value];
1198 } else if (count == 3) {
1199 months = formatData.getShortMonths();
1200 current = months[value];
1201 }
1202 } else {
1203 if (count < 3) {
1204 current = null;
1205 } else if (forceStandaloneForm) {
1206 current = calendar.getDisplayName(field, style | 0x8000, locale);
1207 if (current == null) {
1208 current = calendar.getDisplayName(field, style, locale);
1209 }
1210 }
1211 }
1212 if (current == null) {
1213 zeroPaddingNumber(value+1, count, maxIntCount, buffer);
1214 }
1215 break;
1216
1217 case PATTERN_MONTH_STANDALONE: // 'L'
1218 assert current == null;
1219 if (locale == null) {
1220 String[] months;
1221 if (count >= 4) {
1222 months = formatData.getMonths();
1223 current = months[value];
1224 } else if (count == 3) {
1225 months = formatData.getShortMonths();
1226 current = months[value];
1227 }
1228 } else {
1229 if (count >= 3) {
1230 current = calendar.getDisplayName(field, style | 0x8000, locale);
1231 }
1232 }
1233 if (current == null) {
1234 zeroPaddingNumber(value+1, count, maxIntCount, buffer);
1235 }
1236 break;
1237
1238 case PATTERN_HOUR_OF_DAY1: // 'k' 1-based. eg, 23:59 + 1 hour =>> 24:59
1239 if (current == null) {
1240 if (value == 0) {
1241 zeroPaddingNumber(calendar.getMaximum(Calendar.HOUR_OF_DAY) + 1,
1242 count, maxIntCount, buffer);
1243 } else {
1244 zeroPaddingNumber(value, count, maxIntCount, buffer);
1245 }
1246 }
1247 break;
1248
1249 case PATTERN_DAY_OF_WEEK: // 'E'
1250 if (useDateFormatSymbols) {
1251 String[] weekdays;
1252 if (count >= 4) {
1253 weekdays = formatData.getWeekdays();
1254 current = weekdays[value];
1255 } else { // count < 4, use abbreviated form if exists
1256 weekdays = formatData.getShortWeekdays();
1257 current = weekdays[value];
1258 }
1259 }
1260 break;
1261
1262 case PATTERN_AM_PM: // 'a'
1263 if (useDateFormatSymbols) {
1264 String[] ampm = formatData.getAmPmStrings();
1265 current = ampm[value];
1266 }
1267 break;
1268
1269 case PATTERN_HOUR1: // 'h' 1-based. eg, 11PM + 1 hour =>> 12 AM
1270 if (current == null) {
1271 if (value == 0) {
1272 zeroPaddingNumber(calendar.getLeastMaximum(Calendar.HOUR) + 1,
1273 count, maxIntCount, buffer);
1274 } else {
1275 zeroPaddingNumber(value, count, maxIntCount, buffer);
1276 }
1277 }
1278 break;
1279
1280 case PATTERN_ZONE_NAME: // 'z'
1281 if (current == null) {
1282 if (formatData.locale == null || formatData.isZoneStringsSet) {
1283 int zoneIndex =
1284 formatData.getZoneIndex(calendar.getTimeZone().getID());
1285 if (zoneIndex == -1) {
1286 value = calendar.get(Calendar.ZONE_OFFSET) +
1287 calendar.get(Calendar.DST_OFFSET);
1288 buffer.append(ZoneInfoFile.toCustomID(value));
1289 } else {
1290 int index = (calendar.get(Calendar.DST_OFFSET) == 0) ? 1: 3;
1291 if (count < 4) {
1292 // Use the short name
1293 index++;
1294 }
1295 String[][] zoneStrings = formatData.getZoneStringsWrapper();
1296 buffer.append(zoneStrings[zoneIndex][index]);
1297 }
1298 } else {
1299 TimeZone tz = calendar.getTimeZone();
1300 boolean daylight = (calendar.get(Calendar.DST_OFFSET) != 0);
1301 int tzstyle = (count < 4 ? TimeZone.SHORT : TimeZone.LONG);
1302 buffer.append(tz.getDisplayName(daylight, tzstyle, formatData.locale));
1303 }
1304 }
1305 break;
1306
1307 case PATTERN_ZONE_VALUE: // 'Z' ("-/+hhmm" form)
1308 value = (calendar.get(Calendar.ZONE_OFFSET) +
1309 calendar.get(Calendar.DST_OFFSET)) / 60000;
1310
1311 int width = 4;
1312 if (value >= 0) {
1313 buffer.append('+');
1314 } else {
1315 width++;
1316 }
1317
1318 int num = (value / 60) * 100 + (value % 60);
1319 CalendarUtils.sprintf0d(buffer, num, width);
1320 break;
1321
1322 case PATTERN_ISO_ZONE: // 'X'
1323 value = calendar.get(Calendar.ZONE_OFFSET)
1324 + calendar.get(Calendar.DST_OFFSET);
1325
1326 if (value == 0) {
1327 buffer.append('Z');
1328 break;
1329 }
1330
1331 value /= 60000;
1332 if (value >= 0) {
1333 buffer.append('+');
1334 } else {
1335 buffer.append('-');
1336 value = -value;
1337 }
1338
1339 CalendarUtils.sprintf0d(buffer, value / 60, 2);
1340 if (count == 1) {
1341 break;
1342 }
1343
1344 if (count == 3) {
1345 buffer.append(':');
1346 }
1347 CalendarUtils.sprintf0d(buffer, value % 60, 2);
1348 break;
1349
1350 default:
1351 // case PATTERN_DAY_OF_MONTH: // 'd'
1352 // case PATTERN_HOUR_OF_DAY0: // 'H' 0-based. eg, 23:59 + 1 hour =>> 00:59
1353 // case PATTERN_MINUTE: // 'm'
1354 // case PATTERN_SECOND: // 's'
1355 // case PATTERN_MILLISECOND: // 'S'
1356 // case PATTERN_DAY_OF_YEAR: // 'D'
1357 // case PATTERN_DAY_OF_WEEK_IN_MONTH: // 'F'
1358 // case PATTERN_WEEK_OF_YEAR: // 'w'
1359 // case PATTERN_WEEK_OF_MONTH: // 'W'
1360 // case PATTERN_HOUR0: // 'K' eg, 11PM + 1 hour =>> 0 AM
1361 // case PATTERN_ISO_DAY_OF_WEEK: // 'u' pseudo field, Monday = 1, ..., Sunday = 7
1362 if (current == null) {
1363 zeroPaddingNumber(value, count, maxIntCount, buffer);
1364 }
1365 break;
1366 } // switch (patternCharIndex)
1367
1368 if (current != null) {
1369 buffer.append(current);
1370 }
1371
1372 int fieldID = PATTERN_INDEX_TO_DATE_FORMAT_FIELD[patternCharIndex];
1373 Field f = PATTERN_INDEX_TO_DATE_FORMAT_FIELD_ID[patternCharIndex];
1374
1375 delegate.formatted(fieldID, f, f, beginOffset, buffer.length(), buffer);
1376 }
1377
1378 /**
1379 * Formats a number with the specified minimum and maximum number of digits.
1380 */
1381 private void zeroPaddingNumber(int value, int minDigits, int maxDigits, StringBuffer buffer)
1382 {
1383 // Optimization for 1, 2 and 4 digit numbers. This should
1384 // cover most cases of formatting date/time related items.
1385 // Note: This optimization code assumes that maxDigits is
1386 // either 2 or Integer.MAX_VALUE (maxIntCount in format()).
1387 try {
1388 if (zeroDigit == 0) {
1389 zeroDigit = ((DecimalFormat)numberFormat).getDecimalFormatSymbols().getZeroDigit();
1390 }
1391 if (value >= 0) {
1392 if (value < 100 && minDigits >= 1 && minDigits <= 2) {
1393 if (value < 10) {
1394 if (minDigits == 2) {
1395 buffer.append(zeroDigit);
1396 }
1397 buffer.append((char)(zeroDigit + value));
1398 } else {
1399 buffer.append((char)(zeroDigit + value / 10));
1400 buffer.append((char)(zeroDigit + value % 10));
1401 }
1402 return;
1403 } else if (value >= 1000 && value < 10000) {
1404 if (minDigits == 4) {
1405 buffer.append((char)(zeroDigit + value / 1000));
1406 value %= 1000;
1407 buffer.append((char)(zeroDigit + value / 100));
1408 value %= 100;
1409 buffer.append((char)(zeroDigit + value / 10));
1410 buffer.append((char)(zeroDigit + value % 10));
1411 return;
1412 }
1413 if (minDigits == 2 && maxDigits == 2) {
1414 zeroPaddingNumber(value % 100, 2, 2, buffer);
1415 return;
1416 }
1417 }
1418 }
1419 } catch (Exception e) {
1420 }
1421
1422 numberFormat.setMinimumIntegerDigits(minDigits);
1423 numberFormat.setMaximumIntegerDigits(maxDigits);
1424 numberFormat.format((long)value, buffer, DontCareFieldPosition.INSTANCE);
1425 }
1426
1427
1428 /**
1429 * Parses text from a string to produce a <code>Date</code>.
1430 * <p>
1431 * The method attempts to parse text starting at the index given by
1432 * <code>pos</code>.
1433 * If parsing succeeds, then the index of <code>pos</code> is updated
1434 * to the index after the last character used (parsing does not necessarily
1435 * use all characters up to the end of the string), and the parsed
1436 * date is returned. The updated <code>pos</code> can be used to
1437 * indicate the starting point for the next call to this method.
1438 * If an error occurs, then the index of <code>pos</code> is not
1439 * changed, the error index of <code>pos</code> is set to the index of
1440 * the character where the error occurred, and null is returned.
1441 *
1442 * <p>This parsing operation uses the {@link DateFormat#calendar
1443 * calendar} to produce a {@code Date}. All of the {@code
1444 * calendar}'s date-time fields are {@linkplain Calendar#clear()
1445 * cleared} before parsing, and the {@code calendar}'s default
1446 * values of the date-time fields are used for any missing
1447 * date-time information. For example, the year value of the
1448 * parsed {@code Date} is 1970 with {@link GregorianCalendar} if
1449 * no year value is given from the parsing operation. The {@code
1450 * TimeZone} value may be overwritten, depending on the given
1451 * pattern and the time zone value in {@code text}. Any {@code
1452 * TimeZone} value that has previously been set by a call to
1453 * {@link #setTimeZone(java.util.TimeZone) setTimeZone} may need
1454 * to be restored for further operations.
1455 *
1456 * @param text A <code>String</code>, part of which should be parsed.
1457 * @param pos A <code>ParsePosition</code> object with index and error
1458 * index information as described above.
1459 * @return A <code>Date</code> parsed from the string. In case of
1460 * error, returns null.
1461 * @exception NullPointerException if <code>text</code> or <code>pos</code> is null.
1462 */
1463 @Override
1464 public Date parse(String text, ParsePosition pos)
1465 {
1466 checkNegativeNumberExpression();
1467
1468 int start = pos.index;
1469 int oldStart = start;
1470 int textLength = text.length();
1471
1472 boolean[] ambiguousYear = {false};
1473
1474 CalendarBuilder calb = new CalendarBuilder();
1475
1476 for (int i = 0; i < compiledPattern.length; ) {
1477 int tag = compiledPattern[i] >>> 8;
1478 int count = compiledPattern[i++] & 0xff;
1479 if (count == 255) {
1480 count = compiledPattern[i++] << 16;
1481 count |= compiledPattern[i++];
1482 }
1483
1484 switch (tag) {
1485 case TAG_QUOTE_ASCII_CHAR:
1486 if (start >= textLength || text.charAt(start) != (char)count) {
1487 pos.index = oldStart;
1488 pos.errorIndex = start;
1489 return null;
1490 }
1491 start++;
1492 break;
1493
1494 case TAG_QUOTE_CHARS:
1495 while (count-- > 0) {
1496 if (start >= textLength || text.charAt(start) != compiledPattern[i++]) {
1497 pos.index = oldStart;
1498 pos.errorIndex = start;
1499 return null;
1500 }
1501 start++;
1502 }
1503 break;
1504
1505 default:
1506 // Peek the next pattern to determine if we need to
1507 // obey the number of pattern letters for
1508 // parsing. It's required when parsing contiguous
1509 // digit text (e.g., "20010704") with a pattern which
1510 // has no delimiters between fields, like "yyyyMMdd".
1511 boolean obeyCount = false;
1512
1513 // In Arabic, a minus sign for a negative number is put after
1514 // the number. Even in another locale, a minus sign can be
1515 // put after a number using DateFormat.setNumberFormat().
1516 // If both the minus sign and the field-delimiter are '-',
1517 // subParse() needs to determine whether a '-' after a number
1518 // in the given text is a delimiter or is a minus sign for the
1519 // preceding number. We give subParse() a clue based on the
1520 // information in compiledPattern.
1521 boolean useFollowingMinusSignAsDelimiter = false;
1522
1523 if (i < compiledPattern.length) {
1524 int nextTag = compiledPattern[i] >>> 8;
1525 int nextCount = compiledPattern[i] & 0xff;
1526 obeyCount = shouldObeyCount(nextTag, nextCount);
1527
1528 if (hasFollowingMinusSign &&
1529 (nextTag == TAG_QUOTE_ASCII_CHAR ||
1530 nextTag == TAG_QUOTE_CHARS)) {
1531
1532 if (nextTag != TAG_QUOTE_ASCII_CHAR) {
1533 nextCount = compiledPattern[i+1];
1534 }
1535
1536 if (nextCount == minusSign) {
1537 useFollowingMinusSignAsDelimiter = true;
1538 }
1539 }
1540 }
1541 start = subParse(text, start, tag, count, obeyCount,
1542 ambiguousYear, pos,
1543 useFollowingMinusSignAsDelimiter, calb);
1544 if (start < 0) {
1545 pos.index = oldStart;
1546 return null;
1547 }
1548 }
1549 }
1550
1551 // At this point the fields of Calendar have been set. Calendar
1552 // will fill in default values for missing fields when the time
1553 // is computed.
1554
1555 pos.index = start;
1556
1557 Date parsedDate;
1558 try {
1559 parsedDate = calb.establish(calendar).getTime();
1560 // If the year value is ambiguous,
1561 // then the two-digit year == the default start year
1562 if (ambiguousYear[0]) {
1563 if (parsedDate.before(defaultCenturyStart)) {
1564 parsedDate = calb.addYear(100).establish(calendar).getTime();
1565 }
1566 }
1567 }
1568 // An IllegalArgumentException will be thrown by Calendar.getTime()
1569 // if any fields are out of range, e.g., MONTH == 17.
1570 catch (IllegalArgumentException e) {
1571 pos.errorIndex = start;
1572 pos.index = oldStart;
1573 return null;
1574 }
1575
1576 return parsedDate;
1577 }
1578
1579 /* If the next tag/pattern is a <Numeric_Field> then the parser
1580 * should consider the count of digits while parsing the contigous digits
1581 * for the current tag/pattern
1582 */
1583 private boolean shouldObeyCount(int tag, int count) {
1584 switch (tag) {
1585 case PATTERN_MONTH:
1586 case PATTERN_MONTH_STANDALONE:
1587 return count <= 2;
1588 case PATTERN_YEAR:
1589 case PATTERN_DAY_OF_MONTH:
1590 case PATTERN_HOUR_OF_DAY1:
1591 case PATTERN_HOUR_OF_DAY0:
1592 case PATTERN_MINUTE:
1593 case PATTERN_SECOND:
1594 case PATTERN_MILLISECOND:
1595 case PATTERN_DAY_OF_YEAR:
1596 case PATTERN_DAY_OF_WEEK_IN_MONTH:
1597 case PATTERN_WEEK_OF_YEAR:
1598 case PATTERN_WEEK_OF_MONTH:
1599 case PATTERN_HOUR1:
1600 case PATTERN_HOUR0:
1601 case PATTERN_WEEK_YEAR:
1602 case PATTERN_ISO_DAY_OF_WEEK:
1603 return true;
1604 default:
1605 return false;
1606 }
1607 }
1608
1609 /**
1610 * Private code-size reduction function used by subParse.
1611 * @param text the time text being parsed.
1612 * @param start where to start parsing.
1613 * @param field the date field being parsed.
1614 * @param data the string array to parsed.
1615 * @return the new start position if matching succeeded; a negative number
1616 * indicating matching failure, otherwise.
1617 */
1618 private int matchString(String text, int start, int field, String[] data, CalendarBuilder calb)
1619 {
1620 int i = 0;
1621 int count = data.length;
1622
1623 if (field == Calendar.DAY_OF_WEEK) {
1624 i = 1;
1625 }
1626
1627 // There may be multiple strings in the data[] array which begin with
1628 // the same prefix (e.g., Cerven and Cervenec (June and July) in Czech).
1629 // We keep track of the longest match, and return that. Note that this
1630 // unfortunately requires us to test all array elements.
1631 int bestMatchLength = 0, bestMatch = -1;
1632 for (; i<count; ++i)
1633 {
1634 int length = data[i].length();
1635 // Always compare if we have no match yet; otherwise only compare
1636 // against potentially better matches (longer strings).
1637 if (length > bestMatchLength &&
1638 text.regionMatches(true, start, data[i], 0, length))
1639 {
1640 bestMatch = i;
1641 bestMatchLength = length;
1642 }
1643 }
1644 if (bestMatch >= 0)
1645 {
1646 calb.set(field, bestMatch);
1647 return start + bestMatchLength;
1648 }
1649 return -start;
1650 }
1651
1652 /**
1653 * Performs the same thing as matchString(String, int, int,
1654 * String[]). This method takes a Map<String, Integer> instead of
1655 * String[].
1656 */
1657 private int matchString(String text, int start, int field,
1658 Map<String,Integer> data, CalendarBuilder calb) {
1659 if (data != null) {
1660 // TODO: make this default when it's in the spec.
1661 if (data instanceof SortedMap) {
1662 for (String name : data.keySet()) {
1663 if (text.regionMatches(true, start, name, 0, name.length())) {
1664 calb.set(field, data.get(name));
1665 return start + name.length();
1666 }
1667 }
1668 return -start;
1669 }
1670
1671 String bestMatch = null;
1672
1673 for (String name : data.keySet()) {
1674 int length = name.length();
1675 if (bestMatch == null || length > bestMatch.length()) {
1676 if (text.regionMatches(true, start, name, 0, length)) {
1677 bestMatch = name;
1678 }
1679 }
1680 }
1681
1682 if (bestMatch != null) {
1683 calb.set(field, data.get(bestMatch));
1684 return start + bestMatch.length();
1685 }
1686 }
1687 return -start;
1688 }
1689
1690 private int matchZoneString(String text, int start, String[] zoneNames) {
1691 for (int i = 1; i <= 4; ++i) {
1692 // Checking long and short zones [1 & 2],
1693 // and long and short daylight [3 & 4].
1694 String zoneName = zoneNames[i];
1695 if (zoneName.isEmpty()) {
1696 // fill in by retrieving single name
1697 zoneName = TimeZoneNameUtility.retrieveDisplayName(
1698 zoneNames[0], i >= 3, i % 2, locale);
1699 zoneNames[i] = zoneName;
1700 }
1701 if (text.regionMatches(true, start,
1702 zoneName, 0, zoneName.length())) {
1703 return i;
1704 }
1705 }
1706 return -1;
1707 }
1708
1709 private boolean matchDSTString(String text, int start, int zoneIndex, int standardIndex,
1710 String[][] zoneStrings) {
1711 int index = standardIndex + 2;
1712 String zoneName = zoneStrings[zoneIndex][index];
1713 if (text.regionMatches(true, start,
1714 zoneName, 0, zoneName.length())) {
1715 return true;
1716 }
1717 return false;
1718 }
1719
1720 /**
1721 * find time zone 'text' matched zoneStrings and set to internal
1722 * calendar.
1723 */
1724 private int subParseZoneString(String text, int start, CalendarBuilder calb) {
1725 boolean useSameName = false; // true if standard and daylight time use the same abbreviation.
1726 TimeZone currentTimeZone = getTimeZone();
1727
1728 // At this point, check for named time zones by looking through
1729 // the locale data from the TimeZoneNames strings.
1730 // Want to be able to parse both short and long forms.
1731 int zoneIndex = formatData.getZoneIndex(currentTimeZone.getID());
1732 TimeZone tz = null;
1733 String[][] zoneStrings = formatData.getZoneStringsWrapper();
1734 String[] zoneNames = null;
1735 int nameIndex = 0;
1736 if (zoneIndex != -1) {
1737 zoneNames = zoneStrings[zoneIndex];
1738 if ((nameIndex = matchZoneString(text, start, zoneNames)) > 0) {
1739 if (nameIndex <= 2) {
1740 // Check if the standard name (abbr) and the daylight name are the same.
1741 useSameName = zoneNames[nameIndex].equalsIgnoreCase(zoneNames[nameIndex + 2]);
1742 }
1743 tz = TimeZone.getTimeZone(zoneNames[0]);
1744 }
1745 }
1746 if (tz == null) {
1747 zoneIndex = formatData.getZoneIndex(TimeZone.getDefault().getID());
1748 if (zoneIndex != -1) {
1749 zoneNames = zoneStrings[zoneIndex];
1750 if ((nameIndex = matchZoneString(text, start, zoneNames)) > 0) {
1751 if (nameIndex <= 2) {
1752 useSameName = zoneNames[nameIndex].equalsIgnoreCase(zoneNames[nameIndex + 2]);
1753 }
1754 tz = TimeZone.getTimeZone(zoneNames[0]);
1755 }
1756 }
1757 }
1758
1759 if (tz == null) {
1760 int len = zoneStrings.length;
1761 for (int i = 0; i < len; i++) {
1762 zoneNames = zoneStrings[i];
1763 if ((nameIndex = matchZoneString(text, start, zoneNames)) > 0) {
1764 if (nameIndex <= 2) {
1765 useSameName = zoneNames[nameIndex].equalsIgnoreCase(zoneNames[nameIndex + 2]);
1766 }
1767 tz = TimeZone.getTimeZone(zoneNames[0]);
1768 break;
1769 }
1770 }
1771 }
1772 if (tz != null) { // Matched any ?
1773 if (!tz.equals(currentTimeZone)) {
1774 setTimeZone(tz);
1775 }
1776 // If the time zone matched uses the same name
1777 // (abbreviation) for both standard and daylight time,
1778 // let the time zone in the Calendar decide which one.
1779 //
1780 // Also if tz.getDSTSaving() returns 0 for DST, use tz to
1781 // determine the local time. (6645292)
1782 int dstAmount = (nameIndex >= 3) ? tz.getDSTSavings() : 0;
1783 if (!(useSameName || (nameIndex >= 3 && dstAmount == 0))) {
1784 calb.clear(Calendar.ZONE_OFFSET).set(Calendar.DST_OFFSET, dstAmount);
1785 }
1786 return (start + zoneNames[nameIndex].length());
1787 }
1788 return -start;
1789 }
1790
1791 /**
1792 * Parses numeric forms of time zone offset, such as "hh:mm", and
1793 * sets calb to the parsed value.
1794 *
1795 * @param text the text to be parsed
1796 * @param start the character position to start parsing
1797 * @param sign 1: positive; -1: negative
1798 * @param count 0: 'Z' or "GMT+hh:mm" parsing; 1 - 3: the number of 'X's
1799 * @param colon true - colon required between hh and mm; false - no colon required
1800 * @param calb a CalendarBuilder in which the parsed value is stored
1801 * @return updated parsed position, or its negative value to indicate a parsing error
1802 */
1803 private int subParseNumericZone(String text, int start, int sign, int count,
1804 boolean colon, CalendarBuilder calb) {
1805 int index = start;
1806
1807 parse:
1808 try {
1809 char c = text.charAt(index++);
1810 // Parse hh
1811 int hours;
1812 if (!isDigit(c)) {
1813 break parse;
1814 }
1815 hours = c - '0';
1816 c = text.charAt(index++);
1817 if (isDigit(c)) {
1818 hours = hours * 10 + (c - '0');
1819 } else {
1820 // If no colon in RFC 822 or 'X' (ISO), two digits are
1821 // required.
1822 if (count > 0 || !colon) {
1823 break parse;
1824 }
1825 --index;
1826 }
1827 if (hours > 23) {
1828 break parse;
1829 }
1830 int minutes = 0;
1831 if (count != 1) {
1832 // Proceed with parsing mm
1833 c = text.charAt(index++);
1834 if (colon) {
1835 if (c != ':') {
1836 break parse;
1837 }
1838 c = text.charAt(index++);
1839 }
1840 if (!isDigit(c)) {
1841 break parse;
1842 }
1843 minutes = c - '0';
1844 c = text.charAt(index++);
1845 if (!isDigit(c)) {
1846 break parse;
1847 }
1848 minutes = minutes * 10 + (c - '0');
1849 if (minutes > 59) {
1850 break parse;
1851 }
1852 }
1853 minutes += hours * 60;
1854 calb.set(Calendar.ZONE_OFFSET, minutes * MILLIS_PER_MINUTE * sign)
1855 .set(Calendar.DST_OFFSET, 0);
1856 return index;
1857 } catch (IndexOutOfBoundsException e) {
1858 }
1859 return 1 - index; // -(index - 1)
1860 }
1861
1862 private boolean isDigit(char c) {
1863 return c >= '0' && c <= '9';
1864 }
1865
1866 /**
1867 * Private member function that converts the parsed date strings into
1868 * timeFields. Returns -start (for ParsePosition) if failed.
1869 * @param text the time text to be parsed.
1870 * @param start where to start parsing.
1871 * @param patternCharIndex the index of the pattern character.
1872 * @param count the count of a pattern character.
1873 * @param obeyCount if true, then the next field directly abuts this one,
1874 * and we should use the count to know when to stop parsing.
1875 * @param ambiguousYear return parameter; upon return, if ambiguousYear[0]
1876 * is true, then a two-digit year was parsed and may need to be readjusted.
1877 * @param origPos origPos.errorIndex is used to return an error index
1878 * at which a parse error occurred, if matching failure occurs.
1879 * @return the new start position if matching succeeded; -1 indicating
1880 * matching failure, otherwise. In case matching failure occurred,
1881 * an error index is set to origPos.errorIndex.
1882 */
1883 private int subParse(String text, int start, int patternCharIndex, int count,
1884 boolean obeyCount, boolean[] ambiguousYear,
1885 ParsePosition origPos,
1886 boolean useFollowingMinusSignAsDelimiter, CalendarBuilder calb) {
1887 Number number;
1888 int value = 0;
1889 ParsePosition pos = new ParsePosition(0);
1890 pos.index = start;
1891 if (patternCharIndex == PATTERN_WEEK_YEAR && !calendar.isWeekDateSupported()) {
1892 // use calendar year 'y' instead
1893 patternCharIndex = PATTERN_YEAR;
1894 }
1895 int field = PATTERN_INDEX_TO_CALENDAR_FIELD[patternCharIndex];
1896
1897 // If there are any spaces here, skip over them. If we hit the end
1898 // of the string, then fail.
1899 for (;;) {
1900 if (pos.index >= text.length()) {
1901 origPos.errorIndex = start;
1902 return -1;
1903 }
1904 char c = text.charAt(pos.index);
1905 if (c != ' ' && c != '\t') {
1906 break;
1907 }
1908 ++pos.index;
1909 }
1910 // Remember the actual start index
1911 int actualStart = pos.index;
1912
1913 parsing:
1914 {
1915 // We handle a few special cases here where we need to parse
1916 // a number value. We handle further, more generic cases below. We need
1917 // to handle some of them here because some fields require extra processing on
1918 // the parsed value.
1919 if (patternCharIndex == PATTERN_HOUR_OF_DAY1 ||
1920 patternCharIndex == PATTERN_HOUR1 ||
1921 (patternCharIndex == PATTERN_MONTH && count <= 2) ||
1922 (patternCharIndex == PATTERN_MONTH_STANDALONE && count <= 2) ||
1923 patternCharIndex == PATTERN_YEAR ||
1924 patternCharIndex == PATTERN_WEEK_YEAR) {
1925 // It would be good to unify this with the obeyCount logic below,
1926 // but that's going to be difficult.
1927 if (obeyCount) {
1928 if ((start+count) > text.length()) {
1929 break parsing;
1930 }
1931 number = numberFormat.parse(text.substring(0, start+count), pos);
1932 } else {
1933 number = numberFormat.parse(text, pos);
1934 }
1935 if (number == null) {
1936 if (patternCharIndex != PATTERN_YEAR || calendar instanceof GregorianCalendar) {
1937 break parsing;
1938 }
1939 } else {
1940 value = number.intValue();
1941
1942 if (useFollowingMinusSignAsDelimiter && (value < 0) &&
1943 (((pos.index < text.length()) &&
1944 (text.charAt(pos.index) != minusSign)) ||
1945 ((pos.index == text.length()) &&
1946 (text.charAt(pos.index-1) == minusSign)))) {
1947 value = -value;
1948 pos.index--;
1949 }
1950 }
1951 }
1952
1953 boolean useDateFormatSymbols = useDateFormatSymbols();
1954
1955 int index;
1956 switch (patternCharIndex) {
1957 case PATTERN_ERA: // 'G'
1958 if (useDateFormatSymbols) {
1959 if ((index = matchString(text, start, Calendar.ERA, formatData.getEras(), calb)) > 0) {
1960 return index;
1961 }
1962 } else {
1963 Map<String, Integer> map = getDisplayNamesMap(field, locale);
1964 if ((index = matchString(text, start, field, map, calb)) > 0) {
1965 return index;
1966 }
1967 }
1968 break parsing;
1969
1970 case PATTERN_WEEK_YEAR: // 'Y'
1971 case PATTERN_YEAR: // 'y'
1972 if (!(calendar instanceof GregorianCalendar)) {
1973 // calendar might have text representations for year values,
1974 // such as "\u5143" in JapaneseImperialCalendar.
1975 int style = (count >= 4) ? Calendar.LONG : Calendar.SHORT;
1976 Map<String, Integer> map = calendar.getDisplayNames(field, style, locale);
1977 if (map != null) {
1978 if ((index = matchString(text, start, field, map, calb)) > 0) {
1979 return index;
1980 }
1981 }
1982 calb.set(field, value);
1983 return pos.index;
1984 }
1985
1986 // If there are 3 or more YEAR pattern characters, this indicates
1987 // that the year value is to be treated literally, without any
1988 // two-digit year adjustments (e.g., from "01" to 2001). Otherwise
1989 // we made adjustments to place the 2-digit year in the proper
1990 // century, for parsed strings from "00" to "99". Any other string
1991 // is treated literally: "2250", "-1", "1", "002".
1992 if (count <= 2 && (pos.index - actualStart) == 2
1993 && Character.isDigit(text.charAt(actualStart))
1994 && Character.isDigit(text.charAt(actualStart + 1))) {
1995 // Assume for example that the defaultCenturyStart is 6/18/1903.
1996 // This means that two-digit years will be forced into the range
1997 // 6/18/1903 to 6/17/2003. As a result, years 00, 01, and 02
1998 // correspond to 2000, 2001, and 2002. Years 04, 05, etc. correspond
1999 // to 1904, 1905, etc. If the year is 03, then it is 2003 if the
2000 // other fields specify a date before 6/18, or 1903 if they specify a
2001 // date afterwards. As a result, 03 is an ambiguous year. All other
2002 // two-digit years are unambiguous.
2003 int ambiguousTwoDigitYear = defaultCenturyStartYear % 100;
2004 ambiguousYear[0] = value == ambiguousTwoDigitYear;
2005 value += (defaultCenturyStartYear/100)*100 +
2006 (value < ambiguousTwoDigitYear ? 100 : 0);
2007 }
2008 calb.set(field, value);
2009 return pos.index;
2010
2011 case PATTERN_MONTH: // 'M'
2012 if (count <= 2) // i.e., M or MM.
2013 {
2014 // Don't want to parse the month if it is a string
2015 // while pattern uses numeric style: M or MM.
2016 // [We computed 'value' above.]
2017 calb.set(Calendar.MONTH, value - 1);
2018 return pos.index;
2019 }
2020
2021 if (useDateFormatSymbols) {
2022 // count >= 3 // i.e., MMM or MMMM
2023 // Want to be able to parse both short and long forms.
2024 // Try count == 4 first:
2025 int newStart;
2026 if ((newStart = matchString(text, start, Calendar.MONTH,
2027 formatData.getMonths(), calb)) > 0) {
2028 return newStart;
2029 }
2030 // count == 4 failed, now try count == 3
2031 if ((index = matchString(text, start, Calendar.MONTH,
2032 formatData.getShortMonths(), calb)) > 0) {
2033 return index;
2034 }
2035 } else {
2036 Map<String, Integer> map = getDisplayNamesMap(field, locale);
2037 if ((index = matchString(text, start, field, map, calb)) > 0) {
2038 return index;
2039 }
2040 }
2041 break parsing;
2042
2043 case PATTERN_MONTH_STANDALONE: // 'L'
2044 if (count <= 2) {
2045 // Don't want to parse the month if it is a string
2046 // while pattern uses numeric style: L or LL
2047 //[we computed 'value' above.]
2048 calb.set(Calendar.MONTH, value - 1);
2049 return pos.index;
2050 }
2051 Map<String, Integer> maps = getDisplayNamesMap(field, locale);
2052 if ((index = matchString(text, start, field, maps, calb)) > 0) {
2053 return index;
2054 }
2055 break parsing;
2056
2057 case PATTERN_HOUR_OF_DAY1: // 'k' 1-based. eg, 23:59 + 1 hour =>> 24:59
2058 if (!isLenient()) {
2059 // Validate the hour value in non-lenient
2060 if (value < 1 || value > 24) {
2061 break parsing;
2062 }
2063 }
2064 // [We computed 'value' above.]
2065 if (value == calendar.getMaximum(Calendar.HOUR_OF_DAY) + 1) {
2066 value = 0;
2067 }
2068 calb.set(Calendar.HOUR_OF_DAY, value);
2069 return pos.index;
2070
2071 case PATTERN_DAY_OF_WEEK: // 'E'
2072 {
2073 if (useDateFormatSymbols) {
2074 // Want to be able to parse both short and long forms.
2075 // Try count == 4 (DDDD) first:
2076 int newStart;
2077 if ((newStart=matchString(text, start, Calendar.DAY_OF_WEEK,
2078 formatData.getWeekdays(), calb)) > 0) {
2079 return newStart;
2080 }
2081 // DDDD failed, now try DDD
2082 if ((index = matchString(text, start, Calendar.DAY_OF_WEEK,
2083 formatData.getShortWeekdays(), calb)) > 0) {
2084 return index;
2085 }
2086 } else {
2087 int[] styles = { Calendar.LONG, Calendar.SHORT };
2088 for (int style : styles) {
2089 Map<String,Integer> map = calendar.getDisplayNames(field, style, locale);
2090 if ((index = matchString(text, start, field, map, calb)) > 0) {
2091 return index;
2092 }
2093 }
2094 }
2095 }
2096 break parsing;
2097
2098 case PATTERN_AM_PM: // 'a'
2099 if (useDateFormatSymbols) {
2100 if ((index = matchString(text, start, Calendar.AM_PM,
2101 formatData.getAmPmStrings(), calb)) > 0) {
2102 return index;
2103 }
2104 } else {
2105 Map<String,Integer> map = getDisplayNamesMap(field, locale);
2106 if ((index = matchString(text, start, field, map, calb)) > 0) {
2107 return index;
2108 }
2109 }
2110 break parsing;
2111
2112 case PATTERN_HOUR1: // 'h' 1-based. eg, 11PM + 1 hour =>> 12 AM
2113 if (!isLenient()) {
2114 // Validate the hour value in non-lenient
2115 if (value < 1 || value > 12) {
2116 break parsing;
2117 }
2118 }
2119 // [We computed 'value' above.]
2120 if (value == calendar.getLeastMaximum(Calendar.HOUR) + 1) {
2121 value = 0;
2122 }
2123 calb.set(Calendar.HOUR, value);
2124 return pos.index;
2125
2126 case PATTERN_ZONE_NAME: // 'z'
2127 case PATTERN_ZONE_VALUE: // 'Z'
2128 {
2129 int sign = 0;
2130 try {
2131 char c = text.charAt(pos.index);
2132 if (c == '+') {
2133 sign = 1;
2134 } else if (c == '-') {
2135 sign = -1;
2136 }
2137 if (sign == 0) {
2138 // Try parsing a custom time zone "GMT+hh:mm" or "GMT".
2139 if ((c == 'G' || c == 'g')
2140 && (text.length() - start) >= GMT.length()
2141 && text.regionMatches(true, start, GMT, 0, GMT.length())) {
2142 pos.index = start + GMT.length();
2143
2144 if ((text.length() - pos.index) > 0) {
2145 c = text.charAt(pos.index);
2146 if (c == '+') {
2147 sign = 1;
2148 } else if (c == '-') {
2149 sign = -1;
2150 }
2151 }
2152
2153 if (sign == 0) { /* "GMT" without offset */
2154 calb.set(Calendar.ZONE_OFFSET, 0)
2155 .set(Calendar.DST_OFFSET, 0);
2156 return pos.index;
2157 }
2158
2159 // Parse the rest as "hh:mm"
2160 int i = subParseNumericZone(text, ++pos.index,
2161 sign, 0, true, calb);
2162 if (i > 0) {
2163 return i;
2164 }
2165 pos.index = -i;
2166 } else {
2167 // Try parsing the text as a time zone
2168 // name or abbreviation.
2169 int i = subParseZoneString(text, pos.index, calb);
2170 if (i > 0) {
2171 return i;
2172 }
2173 pos.index = -i;
2174 }
2175 } else {
2176 // Parse the rest as "hhmm" (RFC 822)
2177 int i = subParseNumericZone(text, ++pos.index,
2178 sign, 0, false, calb);
2179 if (i > 0) {
2180 return i;
2181 }
2182 pos.index = -i;
2183 }
2184 } catch (IndexOutOfBoundsException e) {
2185 }
2186 }
2187 break parsing;
2188
2189 case PATTERN_ISO_ZONE: // 'X'
2190 {
2191 if ((text.length() - pos.index) <= 0) {
2192 break parsing;
2193 }
2194
2195 int sign;
2196 char c = text.charAt(pos.index);
2197 if (c == 'Z') {
2198 calb.set(Calendar.ZONE_OFFSET, 0).set(Calendar.DST_OFFSET, 0);
2199 return ++pos.index;
2200 }
2201
2202 // parse text as "+/-hh[[:]mm]" based on count
2203 if (c == '+') {
2204 sign = 1;
2205 } else if (c == '-') {
2206 sign = -1;
2207 } else {
2208 ++pos.index;
2209 break parsing;
2210 }
2211 int i = subParseNumericZone(text, ++pos.index, sign, count,
2212 count == 3, calb);
2213 if (i > 0) {
2214 return i;
2215 }
2216 pos.index = -i;
2217 }
2218 break parsing;
2219
2220 default:
2221 // case PATTERN_DAY_OF_MONTH: // 'd'
2222 // case PATTERN_HOUR_OF_DAY0: // 'H' 0-based. eg, 23:59 + 1 hour =>> 00:59
2223 // case PATTERN_MINUTE: // 'm'
2224 // case PATTERN_SECOND: // 's'
2225 // case PATTERN_MILLISECOND: // 'S'
2226 // case PATTERN_DAY_OF_YEAR: // 'D'
2227 // case PATTERN_DAY_OF_WEEK_IN_MONTH: // 'F'
2228 // case PATTERN_WEEK_OF_YEAR: // 'w'
2229 // case PATTERN_WEEK_OF_MONTH: // 'W'
2230 // case PATTERN_HOUR0: // 'K' 0-based. eg, 11PM + 1 hour =>> 0 AM
2231 // case PATTERN_ISO_DAY_OF_WEEK: // 'u' (pseudo field);
2232
2233 // Handle "generic" fields
2234 if (obeyCount) {
2235 if ((start+count) > text.length()) {
2236 break parsing;
2237 }
2238 number = numberFormat.parse(text.substring(0, start+count), pos);
2239 } else {
2240 number = numberFormat.parse(text, pos);
2241 }
2242 if (number != null) {
2243 value = number.intValue();
2244
2245 if (useFollowingMinusSignAsDelimiter && (value < 0) &&
2246 (((pos.index < text.length()) &&
2247 (text.charAt(pos.index) != minusSign)) ||
2248 ((pos.index == text.length()) &&
2249 (text.charAt(pos.index-1) == minusSign)))) {
2250 value = -value;
2251 pos.index--;
2252 }
2253
2254 calb.set(field, value);
2255 return pos.index;
2256 }
2257 break parsing;
2258 }
2259 }
2260
2261 // Parsing failed.
2262 origPos.errorIndex = pos.index;
2263 return -1;
2264 }
2265
2266 /**
2267 * Returns true if the DateFormatSymbols has been set explicitly or locale
2268 * is null.
2269 */
2270 private boolean useDateFormatSymbols() {
2271 return useDateFormatSymbols || locale == null;
2272 }
2273
2274 /**
2275 * Translates a pattern, mapping each character in the from string to the
2276 * corresponding character in the to string.
2277 *
2278 * @exception IllegalArgumentException if the given pattern is invalid
2279 */
2280 private String translatePattern(String pattern, String from, String to) {
2281 StringBuilder result = new StringBuilder();
2282 boolean inQuote = false;
2283 for (int i = 0; i < pattern.length(); ++i) {
2284 char c = pattern.charAt(i);
2285 if (inQuote) {
2286 if (c == '\'') {
2287 inQuote = false;
2288 }
2289 }
2290 else {
2291 if (c == '\'') {
2292 inQuote = true;
2293 } else if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) {
2294 int ci = from.indexOf(c);
2295 if (ci >= 0) {
2296 // patternChars is longer than localPatternChars due
2297 // to serialization compatibility. The pattern letters
2298 // unsupported by localPatternChars pass through.
2299 if (ci < to.length()) {
2300 c = to.charAt(ci);
2301 }
2302 } else {
2303 throw new IllegalArgumentException("Illegal pattern " +
2304 " character '" +
2305 c + "'");
2306 }
2307 }
2308 }
2309 result.append(c);
2310 }
2311 if (inQuote) {
2312 throw new IllegalArgumentException("Unfinished quote in pattern");
2313 }
2314 return result.toString();
2315 }
2316
2317 /**
2318 * Returns a pattern string describing this date format.
2319 *
2320 * @return a pattern string describing this date format.
2321 */
2322 public String toPattern() {
2323 return pattern;
2324 }
2325
2326 /**
2327 * Returns a localized pattern string describing this date format.
2328 *
2329 * @return a localized pattern string describing this date format.
2330 */
2331 public String toLocalizedPattern() {
2332 return translatePattern(pattern,
2333 DateFormatSymbols.patternChars,
2334 formatData.getLocalPatternChars());
2335 }
2336
2337 /**
2338 * Applies the given pattern string to this date format.
2339 *
2340 * @param pattern the new date and time pattern for this date format
2341 * @exception NullPointerException if the given pattern is null
2342 * @exception IllegalArgumentException if the given pattern is invalid
2343 */
2344 public void applyPattern(String pattern)
2345 {
2346 applyPatternImpl(pattern);
2347 }
2348
2349 private void applyPatternImpl(String pattern) {
2350 compiledPattern = compile(pattern);
2351 this.pattern = pattern;
2352 }
2353
2354 /**
2355 * Applies the given localized pattern string to this date format.
2356 *
2357 * @param pattern a String to be mapped to the new date and time format
2358 * pattern for this format
2359 * @exception NullPointerException if the given pattern is null
2360 * @exception IllegalArgumentException if the given pattern is invalid
2361 */
2362 public void applyLocalizedPattern(String pattern) {
2363 String p = translatePattern(pattern,
2364 formatData.getLocalPatternChars(),
2365 DateFormatSymbols.patternChars);
2366 compiledPattern = compile(p);
2367 this.pattern = p;
2368 }
2369
2370 /**
2371 * Gets a copy of the date and time format symbols of this date format.
2372 *
2373 * @return the date and time format symbols of this date format
2374 * @see #setDateFormatSymbols
2375 */
2376 public DateFormatSymbols getDateFormatSymbols()
2377 {
2378 return (DateFormatSymbols)formatData.clone();
2379 }
2380
2381 /**
2382 * Sets the date and time format symbols of this date format.
2383 *
2384 * @param newFormatSymbols the new date and time format symbols
2385 * @exception NullPointerException if the given newFormatSymbols is null
2386 * @see #getDateFormatSymbols
2387 */
2388 public void setDateFormatSymbols(DateFormatSymbols newFormatSymbols)
2389 {
2390 this.formatData = (DateFormatSymbols)newFormatSymbols.clone();
2391 useDateFormatSymbols = true;
2392 }
2393
2394 /**
2395 * Creates a copy of this <code>SimpleDateFormat</code>. This also
2396 * clones the format's date format symbols.
2397 *
2398 * @return a clone of this <code>SimpleDateFormat</code>
2399 */
2400 @Override
2401 public Object clone() {
2402 SimpleDateFormat other = (SimpleDateFormat) super.clone();
2403 other.formatData = (DateFormatSymbols) formatData.clone();
2404 return other;
2405 }
2406
2407 /**
2408 * Returns the hash code value for this <code>SimpleDateFormat</code> object.
2409 *
2410 * @return the hash code value for this <code>SimpleDateFormat</code> object.
2411 */
2412 @Override
2413 public int hashCode()
2414 {
2415 return pattern.hashCode();
2416 // just enough fields for a reasonable distribution
2417 }
2418
2419 /**
2420 * Compares the given object with this <code>SimpleDateFormat</code> for
2421 * equality.
2422 *
2423 * @return true if the given object is equal to this
2424 * <code>SimpleDateFormat</code>
2425 */
2426 @Override
2427 public boolean equals(Object obj)
2428 {
2429 if (!super.equals(obj)) {
2430 return false; // super does class check
2431 }
2432 SimpleDateFormat that = (SimpleDateFormat) obj;
2433 return (pattern.equals(that.pattern)
2434 && formatData.equals(that.formatData));
2435 }
2436
2437 private static final int[] REST_OF_STYLES = {
2438 Calendar.SHORT_STANDALONE, Calendar.LONG_FORMAT, Calendar.LONG_STANDALONE,
2439 };
2440 private Map<String, Integer> getDisplayNamesMap(int field, Locale locale) {
2441 Map<String, Integer> map = calendar.getDisplayNames(field, Calendar.SHORT_FORMAT, locale);
2442 // Get all SHORT and LONG styles (avoid NARROW styles).
2443 for (int style : REST_OF_STYLES) {
2444 Map<String, Integer> m = calendar.getDisplayNames(field, style, locale);
2445 if (m != null) {
2446 map.putAll(m);
2447 }
2448 }
2449 return map;
2450 }
2451
2452 /**
2453 * After reading an object from the input stream, the format
2454 * pattern in the object is verified.
2455 *
2456 * @exception InvalidObjectException if the pattern is invalid
2457 */
2458 private void readObject(ObjectInputStream stream)
2459 throws IOException, ClassNotFoundException {
2460 stream.defaultReadObject();
2461
2462 try {
2463 compiledPattern = compile(pattern);
2464 } catch (Exception e) {
2465 throw new InvalidObjectException("invalid pattern");
2466 }
2467
2468 if (serialVersionOnStream < 1) {
2469 // didn't have defaultCenturyStart field
2470 initializeDefaultCentury();
2471 }
2472 else {
2473 // fill in dependent transient field
2474 parseAmbiguousDatesAsAfter(defaultCenturyStart);
2475 }
2476 serialVersionOnStream = currentSerialVersion;
2477
2478 // If the deserialized object has a SimpleTimeZone, try
2479 // to replace it with a ZoneInfo equivalent in order to
2480 // be compatible with the SimpleTimeZone-based
2481 // implementation as much as possible.
2482 TimeZone tz = getTimeZone();
2483 if (tz instanceof SimpleTimeZone) {
2484 String id = tz.getID();
2485 TimeZone zi = TimeZone.getTimeZone(id);
2486 if (zi != null && zi.hasSameRules(tz) && zi.getID().equals(id)) {
2487 setTimeZone(zi);
2488 }
2489 }
2490 }
2491
2492 /**
2493 * Analyze the negative subpattern of DecimalFormat and set/update values
2494 * as necessary.
2495 */
2496 private void checkNegativeNumberExpression() {
2497 if ((numberFormat instanceof DecimalFormat) &&
2498 !numberFormat.equals(originalNumberFormat)) {
2499 String numberPattern = ((DecimalFormat)numberFormat).toPattern();
2500 if (!numberPattern.equals(originalNumberPattern)) {
2501 hasFollowingMinusSign = false;
2502
2503 int separatorIndex = numberPattern.indexOf(';');
2504 // If the negative subpattern is not absent, we have to analayze
2505 // it in order to check if it has a following minus sign.
2506 if (separatorIndex > -1) {
2507 int minusIndex = numberPattern.indexOf('-', separatorIndex);
2508 if ((minusIndex > numberPattern.lastIndexOf('0')) &&
2509 (minusIndex > numberPattern.lastIndexOf('#'))) {
2510 hasFollowingMinusSign = true;
2511 minusSign = ((DecimalFormat)numberFormat).getDecimalFormatSymbols().getMinusSign();
2512 }
2513 }
2514 originalNumberPattern = numberPattern;
2515 }
2516 originalNumberFormat = numberFormat;
2517 }
2518 }
2519
2520 }
2521