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