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