1 | /* SimpleDateFormat.java -- A class for parsing/formating simple
|
---|
2 | date constructs
|
---|
3 | Copyright (C) 1998, 1999, 2000, 2001 Free Software Foundation, Inc.
|
---|
4 |
|
---|
5 | This file is part of GNU Classpath.
|
---|
6 |
|
---|
7 | GNU Classpath is free software; you can redistribute it and/or modify
|
---|
8 | it under the terms of the GNU General Public License as published by
|
---|
9 | the Free Software Foundation; either version 2, or (at your option)
|
---|
10 | any later version.
|
---|
11 |
|
---|
12 | GNU Classpath is distributed in the hope that it will be useful, but
|
---|
13 | WITHOUT ANY WARRANTY; without even the implied warranty of
|
---|
14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
---|
15 | General Public License for more details.
|
---|
16 |
|
---|
17 | You should have received a copy of the GNU General Public License
|
---|
18 | along with GNU Classpath; see the file COPYING. If not, write to the
|
---|
19 | Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
---|
20 | 02111-1307 USA.
|
---|
21 |
|
---|
22 | Linking this library statically or dynamically with other modules is
|
---|
23 | making a combined work based on this library. Thus, the terms and
|
---|
24 | conditions of the GNU General Public License cover the whole
|
---|
25 | combination.
|
---|
26 |
|
---|
27 | As a special exception, the copyright holders of this library give you
|
---|
28 | permission to link this library with independent modules to produce an
|
---|
29 | executable, regardless of the license terms of these independent
|
---|
30 | modules, and to copy and distribute the resulting executable under
|
---|
31 | terms of your choice, provided that you also meet, for each linked
|
---|
32 | independent module, the terms and conditions of the license of that
|
---|
33 | module. An independent module is a module which is not derived from
|
---|
34 | or based on this library. If you modify this library, you may extend
|
---|
35 | this exception to your version of the library, but you are not
|
---|
36 | obligated to do so. If you do not wish to do so, delete this
|
---|
37 | exception statement from your version. */
|
---|
38 |
|
---|
39 |
|
---|
40 | package java.text;
|
---|
41 |
|
---|
42 | import java.util.Calendar;
|
---|
43 | import java.util.Date;
|
---|
44 | import java.util.Enumeration;
|
---|
45 | import java.util.GregorianCalendar;
|
---|
46 | import java.util.Locale;
|
---|
47 | import java.util.TimeZone;
|
---|
48 | import java.util.SimpleTimeZone;
|
---|
49 | import java.util.Vector;
|
---|
50 | import java.io.ObjectInputStream;
|
---|
51 | import java.io.IOException;
|
---|
52 |
|
---|
53 | /**
|
---|
54 | * SimpleDateFormat provides convenient methods for parsing and formatting
|
---|
55 | * dates using Gregorian calendars (see java.util.GregorianCalendar).
|
---|
56 | */
|
---|
57 | public class SimpleDateFormat extends DateFormat
|
---|
58 | {
|
---|
59 | /** A pair class used by SimpleDateFormat as a compiled representation
|
---|
60 | * of a format string.
|
---|
61 | */
|
---|
62 | private class FieldSizePair
|
---|
63 | {
|
---|
64 | public int field;
|
---|
65 | public int size;
|
---|
66 |
|
---|
67 | /** Constructs a pair with the given field and size values */
|
---|
68 | public FieldSizePair(int f, int s) {
|
---|
69 | field = f;
|
---|
70 | size = s;
|
---|
71 | }
|
---|
72 | }
|
---|
73 |
|
---|
74 | private transient Vector tokens;
|
---|
75 | private DateFormatSymbols formatData; // formatData
|
---|
76 | private Date defaultCenturyStart;
|
---|
77 | private transient int defaultCentury;
|
---|
78 | private String pattern;
|
---|
79 | private int serialVersionOnStream = 1; // 0 indicates JDK1.1.3 or earlier
|
---|
80 | private static final long serialVersionUID = 4774881970558875024L;
|
---|
81 |
|
---|
82 | // This string is specified in the JCL. We set it here rather than
|
---|
83 | // do a DateFormatSymbols(Locale.US).getLocalPatternChars() since
|
---|
84 | // someone could theoretically change those values (though unlikely).
|
---|
85 | private static final String standardChars = "GyMdkHmsSEDFwWahKz";
|
---|
86 |
|
---|
87 | private void readObject(ObjectInputStream stream)
|
---|
88 | throws IOException, ClassNotFoundException
|
---|
89 | {
|
---|
90 | stream.defaultReadObject();
|
---|
91 | if (serialVersionOnStream < 1)
|
---|
92 | {
|
---|
93 | computeCenturyStart ();
|
---|
94 | serialVersionOnStream = 1;
|
---|
95 | }
|
---|
96 | else
|
---|
97 | // Ensure that defaultCentury gets set.
|
---|
98 | set2DigitYearStart(defaultCenturyStart);
|
---|
99 |
|
---|
100 | // Set up items normally taken care of by the constructor.
|
---|
101 | tokens = new Vector();
|
---|
102 | compileFormat(pattern);
|
---|
103 | }
|
---|
104 |
|
---|
105 | private void compileFormat(String pattern)
|
---|
106 | {
|
---|
107 | // Any alphabetical characters are treated as pattern characters
|
---|
108 | // unless enclosed in single quotes.
|
---|
109 |
|
---|
110 | char thisChar;
|
---|
111 | int pos;
|
---|
112 | int field;
|
---|
113 | FieldSizePair current = null;
|
---|
114 |
|
---|
115 | for (int i=0; i<pattern.length(); i++) {
|
---|
116 | thisChar = pattern.charAt(i);
|
---|
117 | field = formatData.getLocalPatternChars().indexOf(thisChar);
|
---|
118 | if (field == -1) {
|
---|
119 | current = null;
|
---|
120 | if (Character.isLetter(thisChar)) {
|
---|
121 | // Not a valid letter
|
---|
122 | tokens.addElement(new FieldSizePair(-1,0));
|
---|
123 | } else if (thisChar == '\'') {
|
---|
124 | // Quoted text section; skip to next single quote
|
---|
125 | pos = pattern.indexOf('\'',i+1);
|
---|
126 | if (pos == -1) {
|
---|
127 | // This ought to be an exception, but spec does not
|
---|
128 | // let us throw one.
|
---|
129 | tokens.addElement(new FieldSizePair(-1,0));
|
---|
130 | }
|
---|
131 | if ((pos+1 < pattern.length()) && (pattern.charAt(pos+1) == '\'')) {
|
---|
132 | tokens.addElement(pattern.substring(i+1,pos+1));
|
---|
133 | } else {
|
---|
134 | tokens.addElement(pattern.substring(i+1,pos));
|
---|
135 | }
|
---|
136 | i = pos;
|
---|
137 | } else {
|
---|
138 | // A special character
|
---|
139 | tokens.addElement(new Character(thisChar));
|
---|
140 | }
|
---|
141 | } else {
|
---|
142 | // A valid field
|
---|
143 | if ((current != null) && (field == current.field)) {
|
---|
144 | current.size++;
|
---|
145 | } else {
|
---|
146 | current = new FieldSizePair(field,1);
|
---|
147 | tokens.addElement(current);
|
---|
148 | }
|
---|
149 | }
|
---|
150 | }
|
---|
151 | }
|
---|
152 |
|
---|
153 | public String toString()
|
---|
154 | {
|
---|
155 | StringBuffer output = new StringBuffer();
|
---|
156 | Enumeration e = tokens.elements();
|
---|
157 | while (e.hasMoreElements()) {
|
---|
158 | output.append(e.nextElement().toString());
|
---|
159 | }
|
---|
160 | return output.toString();
|
---|
161 | }
|
---|
162 |
|
---|
163 | /**
|
---|
164 | * Constructs a SimpleDateFormat using the default pattern for
|
---|
165 | * the default locale.
|
---|
166 | */
|
---|
167 | public SimpleDateFormat()
|
---|
168 | {
|
---|
169 | /*
|
---|
170 | * There does not appear to be a standard API for determining
|
---|
171 | * what the default pattern for a locale is, so use package-scope
|
---|
172 | * variables in DateFormatSymbols to encapsulate this.
|
---|
173 | */
|
---|
174 | super();
|
---|
175 | Locale locale = Locale.getDefault();
|
---|
176 | calendar = new GregorianCalendar(locale);
|
---|
177 | computeCenturyStart();
|
---|
178 | tokens = new Vector();
|
---|
179 | formatData = new DateFormatSymbols(locale);
|
---|
180 | pattern = (formatData.dateFormats[DEFAULT] + ' '
|
---|
181 | + formatData.timeFormats[DEFAULT]);
|
---|
182 | compileFormat(pattern);
|
---|
183 | numberFormat = NumberFormat.getInstance(locale);
|
---|
184 | numberFormat.setGroupingUsed (false);
|
---|
185 | }
|
---|
186 |
|
---|
187 | /**
|
---|
188 | * Creates a date formatter using the specified pattern, with the default
|
---|
189 | * DateFormatSymbols for the default locale.
|
---|
190 | */
|
---|
191 | public SimpleDateFormat(String pattern)
|
---|
192 | {
|
---|
193 | this(pattern, Locale.getDefault());
|
---|
194 | }
|
---|
195 |
|
---|
196 | /**
|
---|
197 | * Creates a date formatter using the specified pattern, with the default
|
---|
198 | * DateFormatSymbols for the given locale.
|
---|
199 | */
|
---|
200 | public SimpleDateFormat(String pattern, Locale locale)
|
---|
201 | {
|
---|
202 | super();
|
---|
203 | calendar = new GregorianCalendar(locale);
|
---|
204 | computeCenturyStart();
|
---|
205 | tokens = new Vector();
|
---|
206 | formatData = new DateFormatSymbols(locale);
|
---|
207 | compileFormat(pattern);
|
---|
208 | this.pattern = pattern;
|
---|
209 | numberFormat = NumberFormat.getInstance(locale);
|
---|
210 | numberFormat.setGroupingUsed (false);
|
---|
211 | }
|
---|
212 |
|
---|
213 | /**
|
---|
214 | * Creates a date formatter using the specified pattern. The
|
---|
215 | * specified DateFormatSymbols will be used when formatting.
|
---|
216 | */
|
---|
217 | public SimpleDateFormat(String pattern, DateFormatSymbols formatData)
|
---|
218 | {
|
---|
219 | super();
|
---|
220 | calendar = new GregorianCalendar();
|
---|
221 | computeCenturyStart ();
|
---|
222 | tokens = new Vector();
|
---|
223 | this.formatData = formatData;
|
---|
224 | compileFormat(pattern);
|
---|
225 | this.pattern = pattern;
|
---|
226 | numberFormat = NumberFormat.getInstance();
|
---|
227 | numberFormat.setGroupingUsed (false);
|
---|
228 | }
|
---|
229 |
|
---|
230 | // What is the difference between localized and unlocalized? The
|
---|
231 | // docs don't say.
|
---|
232 |
|
---|
233 | /**
|
---|
234 | * This method returns a string with the formatting pattern being used
|
---|
235 | * by this object. This string is unlocalized.
|
---|
236 | *
|
---|
237 | * @return The format string.
|
---|
238 | */
|
---|
239 | public String toPattern()
|
---|
240 | {
|
---|
241 | return pattern;
|
---|
242 | }
|
---|
243 |
|
---|
244 | /**
|
---|
245 | * This method returns a string with the formatting pattern being used
|
---|
246 | * by this object. This string is localized.
|
---|
247 | *
|
---|
248 | * @return The format string.
|
---|
249 | */
|
---|
250 | public String toLocalizedPattern()
|
---|
251 | {
|
---|
252 | String localChars = formatData.getLocalPatternChars();
|
---|
253 | return applyLocalizedPattern (pattern, standardChars, localChars);
|
---|
254 | }
|
---|
255 |
|
---|
256 | /**
|
---|
257 | * This method sets the formatting pattern that should be used by this
|
---|
258 | * object. This string is not localized.
|
---|
259 | *
|
---|
260 | * @param pattern The new format pattern.
|
---|
261 | */
|
---|
262 | public void applyPattern(String pattern)
|
---|
263 | {
|
---|
264 | tokens = new Vector();
|
---|
265 | compileFormat(pattern);
|
---|
266 | this.pattern = pattern;
|
---|
267 | }
|
---|
268 |
|
---|
269 | /**
|
---|
270 | * This method sets the formatting pattern that should be used by this
|
---|
271 | * object. This string is localized.
|
---|
272 | *
|
---|
273 | * @param pattern The new format pattern.
|
---|
274 | */
|
---|
275 | public void applyLocalizedPattern(String pattern)
|
---|
276 | {
|
---|
277 | String localChars = formatData.getLocalPatternChars();
|
---|
278 | pattern = applyLocalizedPattern (pattern, localChars, standardChars);
|
---|
279 | applyPattern(pattern);
|
---|
280 | }
|
---|
281 |
|
---|
282 | private String applyLocalizedPattern(String pattern,
|
---|
283 | String oldChars, String newChars)
|
---|
284 | {
|
---|
285 | int len = pattern.length();
|
---|
286 | StringBuffer buf = new StringBuffer(len);
|
---|
287 | boolean quoted = false;
|
---|
288 | for (int i = 0; i < len; i++)
|
---|
289 | {
|
---|
290 | char ch = pattern.charAt(i);
|
---|
291 | if (ch == '\'')
|
---|
292 | quoted = ! quoted;
|
---|
293 | if (! quoted)
|
---|
294 | {
|
---|
295 | int j = oldChars.indexOf(ch);
|
---|
296 | if (j >= 0)
|
---|
297 | ch = newChars.charAt(j);
|
---|
298 | }
|
---|
299 | buf.append(ch);
|
---|
300 | }
|
---|
301 | return buf.toString();
|
---|
302 | }
|
---|
303 |
|
---|
304 | /**
|
---|
305 | * Returns the start of the century used for two digit years.
|
---|
306 | *
|
---|
307 | * @return A <code>Date</code> representing the start of the century
|
---|
308 | * for two digit years.
|
---|
309 | */
|
---|
310 | public Date get2DigitYearStart()
|
---|
311 | {
|
---|
312 | return defaultCenturyStart;
|
---|
313 | }
|
---|
314 |
|
---|
315 | /**
|
---|
316 | * Sets the start of the century used for two digit years.
|
---|
317 | *
|
---|
318 | * @param date A <code>Date</code> representing the start of the century for
|
---|
319 | * two digit years.
|
---|
320 | */
|
---|
321 | public void set2DigitYearStart(Date date)
|
---|
322 | {
|
---|
323 | defaultCenturyStart = date;
|
---|
324 | calendar.clear();
|
---|
325 | calendar.setTime(date);
|
---|
326 | int year = calendar.get(Calendar.YEAR);
|
---|
327 | defaultCentury = year - (year % 100);
|
---|
328 | }
|
---|
329 |
|
---|
330 | /**
|
---|
331 | * This method returns the format symbol information used for parsing
|
---|
332 | * and formatting dates.
|
---|
333 | *
|
---|
334 | * @return The date format symbols.
|
---|
335 | */
|
---|
336 | public DateFormatSymbols getDateFormatSymbols()
|
---|
337 | {
|
---|
338 | return formatData;
|
---|
339 | }
|
---|
340 |
|
---|
341 | /**
|
---|
342 | * This method sets the format symbols information used for parsing
|
---|
343 | * and formatting dates.
|
---|
344 | *
|
---|
345 | * @param formatData The date format symbols.
|
---|
346 | */
|
---|
347 | public void setDateFormatSymbols(DateFormatSymbols formatData)
|
---|
348 | {
|
---|
349 | this.formatData = formatData;
|
---|
350 | }
|
---|
351 |
|
---|
352 | /**
|
---|
353 | * This methods tests whether the specified object is equal to this
|
---|
354 | * object. This will be true if and only if the specified object:
|
---|
355 | * <p>
|
---|
356 | * <ul>
|
---|
357 | * <li>Is not <code>null</code>.
|
---|
358 | * <li>Is an instance of <code>SimpleDateFormat</code>.
|
---|
359 | * <li>Is equal to this object at the superclass (i.e., <code>DateFormat</code>)
|
---|
360 | * level.
|
---|
361 | * <li>Has the same formatting pattern.
|
---|
362 | * <li>Is using the same formatting symbols.
|
---|
363 | * <li>Is using the same century for two digit years.
|
---|
364 | * </ul>
|
---|
365 | *
|
---|
366 | * @param obj The object to compare for equality against.
|
---|
367 | *
|
---|
368 | * @return <code>true</code> if the specified object is equal to this object,
|
---|
369 | * <code>false</code> otherwise.
|
---|
370 | */
|
---|
371 | public boolean equals(Object o)
|
---|
372 | {
|
---|
373 | if (o == null)
|
---|
374 | return false;
|
---|
375 |
|
---|
376 | if (!super.equals(o))
|
---|
377 | return false;
|
---|
378 |
|
---|
379 | if (!(o instanceof SimpleDateFormat))
|
---|
380 | return false;
|
---|
381 |
|
---|
382 | SimpleDateFormat sdf = (SimpleDateFormat)o;
|
---|
383 |
|
---|
384 | if (!toPattern().equals(sdf.toPattern()))
|
---|
385 | return false;
|
---|
386 |
|
---|
387 | if (!get2DigitYearStart().equals(sdf.get2DigitYearStart()))
|
---|
388 | return false;
|
---|
389 |
|
---|
390 | if (!getDateFormatSymbols().equals(sdf.getDateFormatSymbols()))
|
---|
391 | return false;
|
---|
392 |
|
---|
393 | return true;
|
---|
394 | }
|
---|
395 |
|
---|
396 |
|
---|
397 | /**
|
---|
398 | * Formats the date input according to the format string in use,
|
---|
399 | * appending to the specified StringBuffer. The input StringBuffer
|
---|
400 | * is returned as output for convenience.
|
---|
401 | */
|
---|
402 | public StringBuffer format(Date date, StringBuffer buffer, FieldPosition pos)
|
---|
403 | {
|
---|
404 | String temp;
|
---|
405 | calendar.setTime(date);
|
---|
406 |
|
---|
407 | // go through vector, filling in fields where applicable, else toString
|
---|
408 | Enumeration e = tokens.elements();
|
---|
409 | while (e.hasMoreElements()) {
|
---|
410 | Object o = e.nextElement();
|
---|
411 | if (o instanceof FieldSizePair) {
|
---|
412 | FieldSizePair p = (FieldSizePair) o;
|
---|
413 | int beginIndex = buffer.length();
|
---|
414 | switch (p.field) {
|
---|
415 | case ERA_FIELD:
|
---|
416 | buffer.append(formatData.eras[calendar.get(Calendar.ERA)]);
|
---|
417 | break;
|
---|
418 | case YEAR_FIELD:
|
---|
419 | temp = String.valueOf(calendar.get(Calendar.YEAR));
|
---|
420 | if (p.size < 4)
|
---|
421 | buffer.append(temp.substring(temp.length()-2));
|
---|
422 | else
|
---|
423 | buffer.append(temp);
|
---|
424 | break;
|
---|
425 | case MONTH_FIELD:
|
---|
426 | if (p.size < 3)
|
---|
427 | withLeadingZeros(calendar.get(Calendar.MONTH)+1,p.size,buffer);
|
---|
428 | else if (p.size < 4)
|
---|
429 | buffer.append(formatData.shortMonths[calendar.get(Calendar.MONTH)]);
|
---|
430 | else
|
---|
431 | buffer.append(formatData.months[calendar.get(Calendar.MONTH)]);
|
---|
432 | break;
|
---|
433 | case DATE_FIELD:
|
---|
434 | withLeadingZeros(calendar.get(Calendar.DATE),p.size,buffer);
|
---|
435 | break;
|
---|
436 | case HOUR_OF_DAY1_FIELD: // 1-24
|
---|
437 | withLeadingZeros(((calendar.get(Calendar.HOUR_OF_DAY)+23)%24)+1,p.size,buffer);
|
---|
438 | break;
|
---|
439 | case HOUR_OF_DAY0_FIELD: // 0-23
|
---|
440 | withLeadingZeros(calendar.get(Calendar.HOUR_OF_DAY),p.size,buffer);
|
---|
441 | break;
|
---|
442 | case MINUTE_FIELD:
|
---|
443 | withLeadingZeros(calendar.get(Calendar.MINUTE),p.size,buffer);
|
---|
444 | break;
|
---|
445 | case SECOND_FIELD:
|
---|
446 | withLeadingZeros(calendar.get(Calendar.SECOND),p.size,buffer);
|
---|
447 | break;
|
---|
448 | case MILLISECOND_FIELD:
|
---|
449 | withLeadingZeros(calendar.get(Calendar.MILLISECOND),p.size,buffer);
|
---|
450 | break;
|
---|
451 | case DAY_OF_WEEK_FIELD:
|
---|
452 | if (p.size < 4)
|
---|
453 | buffer.append(formatData.shortWeekdays[calendar.get(Calendar.DAY_OF_WEEK)]);
|
---|
454 | else
|
---|
455 | buffer.append(formatData.weekdays[calendar.get(Calendar.DAY_OF_WEEK)]);
|
---|
456 | break;
|
---|
457 | case DAY_OF_YEAR_FIELD:
|
---|
458 | withLeadingZeros(calendar.get(Calendar.DAY_OF_YEAR),p.size,buffer);
|
---|
459 | break;
|
---|
460 | case DAY_OF_WEEK_IN_MONTH_FIELD:
|
---|
461 | withLeadingZeros(calendar.get(Calendar.DAY_OF_WEEK_IN_MONTH),p.size,buffer);
|
---|
462 | break;
|
---|
463 | case WEEK_OF_YEAR_FIELD:
|
---|
464 | withLeadingZeros(calendar.get(Calendar.WEEK_OF_YEAR),p.size,buffer);
|
---|
465 | break;
|
---|
466 | case WEEK_OF_MONTH_FIELD:
|
---|
467 | withLeadingZeros(calendar.get(Calendar.WEEK_OF_MONTH),p.size,buffer);
|
---|
468 | break;
|
---|
469 | case AM_PM_FIELD:
|
---|
470 | buffer.append(formatData.ampms[calendar.get(Calendar.AM_PM)]);
|
---|
471 | break;
|
---|
472 | case HOUR1_FIELD: // 1-12
|
---|
473 | withLeadingZeros(((calendar.get(Calendar.HOUR)+11)%12)+1,p.size,buffer);
|
---|
474 | break;
|
---|
475 | case HOUR0_FIELD: // 0-11
|
---|
476 | withLeadingZeros(calendar.get(Calendar.HOUR),p.size,buffer);
|
---|
477 | break;
|
---|
478 | case TIMEZONE_FIELD:
|
---|
479 | TimeZone zone = calendar.getTimeZone();
|
---|
480 | boolean isDST = calendar.get(Calendar.DST_OFFSET) != 0;
|
---|
481 | // FIXME: XXX: This should be a localized time zone.
|
---|
482 | String zoneID = zone.getDisplayName(isDST, p.size > 3 ? TimeZone.LONG : TimeZone.SHORT);
|
---|
483 | buffer.append(zoneID);
|
---|
484 | break;
|
---|
485 | default:
|
---|
486 | throw new IllegalArgumentException("Illegal pattern character");
|
---|
487 | }
|
---|
488 | if (pos != null && p.field == pos.getField())
|
---|
489 | {
|
---|
490 | pos.setBeginIndex(beginIndex);
|
---|
491 | pos.setEndIndex(buffer.length());
|
---|
492 | }
|
---|
493 | } else {
|
---|
494 | buffer.append(o.toString());
|
---|
495 | }
|
---|
496 | }
|
---|
497 | return buffer;
|
---|
498 | }
|
---|
499 |
|
---|
500 | private void withLeadingZeros(int value, int length, StringBuffer buffer)
|
---|
501 | {
|
---|
502 | String valStr = String.valueOf(value);
|
---|
503 | for (length -= valStr.length(); length > 0; length--)
|
---|
504 | buffer.append('0');
|
---|
505 | buffer.append(valStr);
|
---|
506 | }
|
---|
507 |
|
---|
508 | private final boolean expect (String source, ParsePosition pos, char ch)
|
---|
509 | {
|
---|
510 | int x = pos.getIndex();
|
---|
511 | boolean r = x < source.length() && source.charAt(x) == ch;
|
---|
512 | if (r)
|
---|
513 | pos.setIndex(x + 1);
|
---|
514 | else
|
---|
515 | pos.setErrorIndex(x);
|
---|
516 | return r;
|
---|
517 | }
|
---|
518 |
|
---|
519 | /**
|
---|
520 | * This method parses the specified string into a date.
|
---|
521 | *
|
---|
522 | * @param dateStr The date string to parse.
|
---|
523 | * @param pos The input and output parse position
|
---|
524 | *
|
---|
525 | * @return The parsed date, or <code>null</code> if the string cannot be
|
---|
526 | * parsed.
|
---|
527 | */
|
---|
528 | public Date parse (String dateStr, ParsePosition pos)
|
---|
529 | {
|
---|
530 | int fmt_index = 0;
|
---|
531 | int fmt_max = pattern.length();
|
---|
532 |
|
---|
533 | calendar.clear();
|
---|
534 | boolean saw_timezone = false;
|
---|
535 | int quote_start = -1;
|
---|
536 | boolean is2DigitYear = false;
|
---|
537 | for (; fmt_index < fmt_max; ++fmt_index)
|
---|
538 | {
|
---|
539 | char ch = pattern.charAt(fmt_index);
|
---|
540 | if (ch == '\'')
|
---|
541 | {
|
---|
542 | int index = pos.getIndex();
|
---|
543 | if (fmt_index < fmt_max - 1
|
---|
544 | && pattern.charAt(fmt_index + 1) == '\'')
|
---|
545 | {
|
---|
546 | if (! expect (dateStr, pos, ch))
|
---|
547 | return null;
|
---|
548 | ++fmt_index;
|
---|
549 | }
|
---|
550 | else
|
---|
551 | quote_start = quote_start < 0 ? fmt_index : -1;
|
---|
552 | continue;
|
---|
553 | }
|
---|
554 |
|
---|
555 | if (quote_start != -1
|
---|
556 | || ((ch < 'a' || ch > 'z')
|
---|
557 | && (ch < 'A' || ch > 'Z')))
|
---|
558 | {
|
---|
559 | if (! expect (dateStr, pos, ch))
|
---|
560 | return null;
|
---|
561 | continue;
|
---|
562 | }
|
---|
563 |
|
---|
564 | // We've arrived at a potential pattern character in the
|
---|
565 | // pattern.
|
---|
566 | int first = fmt_index;
|
---|
567 | while (++fmt_index < fmt_max && pattern.charAt(fmt_index) == ch)
|
---|
568 | ;
|
---|
569 | int fmt_count = fmt_index - first;
|
---|
570 | --fmt_index;
|
---|
571 |
|
---|
572 | // We can handle most fields automatically: most either are
|
---|
573 | // numeric or are looked up in a string vector. In some cases
|
---|
574 | // we need an offset. When numeric, `offset' is added to the
|
---|
575 | // resulting value. When doing a string lookup, offset is the
|
---|
576 | // initial index into the string array.
|
---|
577 | int calendar_field;
|
---|
578 | boolean is_numeric = true;
|
---|
579 | String[] match = null;
|
---|
580 | int offset = 0;
|
---|
581 | boolean maybe2DigitYear = false;
|
---|
582 | switch (ch)
|
---|
583 | {
|
---|
584 | case 'd':
|
---|
585 | calendar_field = Calendar.DATE;
|
---|
586 | break;
|
---|
587 | case 'D':
|
---|
588 | calendar_field = Calendar.DAY_OF_YEAR;
|
---|
589 | break;
|
---|
590 | case 'F':
|
---|
591 | calendar_field = Calendar.DAY_OF_WEEK_IN_MONTH;
|
---|
592 | break;
|
---|
593 | case 'E':
|
---|
594 | is_numeric = false;
|
---|
595 | offset = 1;
|
---|
596 | calendar_field = Calendar.DAY_OF_WEEK;
|
---|
597 | match = (fmt_count <= 3
|
---|
598 | ? formatData.getShortWeekdays()
|
---|
599 | : formatData.getWeekdays());
|
---|
600 | break;
|
---|
601 | case 'w':
|
---|
602 | calendar_field = Calendar.WEEK_OF_YEAR;
|
---|
603 | break;
|
---|
604 | case 'W':
|
---|
605 | calendar_field = Calendar.WEEK_OF_MONTH;
|
---|
606 | break;
|
---|
607 | case 'M':
|
---|
608 | calendar_field = Calendar.MONTH;
|
---|
609 | if (fmt_count <= 2)
|
---|
610 | offset = -1;
|
---|
611 | else
|
---|
612 | {
|
---|
613 | is_numeric = false;
|
---|
614 | match = (fmt_count <= 3
|
---|
615 | ? formatData.getShortMonths()
|
---|
616 | : formatData.getMonths());
|
---|
617 | }
|
---|
618 | break;
|
---|
619 | case 'y':
|
---|
620 | calendar_field = Calendar.YEAR;
|
---|
621 | if (fmt_count <= 2)
|
---|
622 | maybe2DigitYear = true;
|
---|
623 | break;
|
---|
624 | case 'K':
|
---|
625 | calendar_field = Calendar.HOUR;
|
---|
626 | break;
|
---|
627 | case 'h':
|
---|
628 | calendar_field = Calendar.HOUR;
|
---|
629 | break;
|
---|
630 | case 'H':
|
---|
631 | calendar_field = Calendar.HOUR_OF_DAY;
|
---|
632 | break;
|
---|
633 | case 'k':
|
---|
634 | calendar_field = Calendar.HOUR_OF_DAY;
|
---|
635 | break;
|
---|
636 | case 'm':
|
---|
637 | calendar_field = Calendar.MINUTE;
|
---|
638 | break;
|
---|
639 | case 's':
|
---|
640 | calendar_field = Calendar.SECOND;
|
---|
641 | break;
|
---|
642 | case 'S':
|
---|
643 | calendar_field = Calendar.MILLISECOND;
|
---|
644 | break;
|
---|
645 | case 'a':
|
---|
646 | is_numeric = false;
|
---|
647 | calendar_field = Calendar.AM_PM;
|
---|
648 | match = formatData.getAmPmStrings();
|
---|
649 | break;
|
---|
650 | case 'z':
|
---|
651 | // We need a special case for the timezone, because it
|
---|
652 | // uses a different data structure than the other cases.
|
---|
653 | is_numeric = false;
|
---|
654 | calendar_field = Calendar.DST_OFFSET;
|
---|
655 | String[][] zoneStrings = formatData.getZoneStrings();
|
---|
656 | int zoneCount = zoneStrings.length;
|
---|
657 | int index = pos.getIndex();
|
---|
658 | boolean found_zone = false;
|
---|
659 | for (int j = 0; j < zoneCount; j++)
|
---|
660 | {
|
---|
661 | String[] strings = zoneStrings[j];
|
---|
662 | int k;
|
---|
663 | for (k = 1; k < strings.length; ++k)
|
---|
664 | {
|
---|
665 | if (dateStr.startsWith(strings[k], index))
|
---|
666 | break;
|
---|
667 | }
|
---|
668 | if (k != strings.length)
|
---|
669 | {
|
---|
670 | found_zone = true;
|
---|
671 | saw_timezone = true;
|
---|
672 | TimeZone tz = TimeZone.getTimeZone (strings[0]);
|
---|
673 | calendar.setTimeZone (tz);
|
---|
674 | calendar.set (Calendar.ZONE_OFFSET, tz.getRawOffset ());
|
---|
675 | offset = 0;
|
---|
676 | if (k > 2 && tz instanceof SimpleTimeZone)
|
---|
677 | {
|
---|
678 | SimpleTimeZone stz = (SimpleTimeZone) tz;
|
---|
679 | offset = stz.getDSTSavings ();
|
---|
680 | }
|
---|
681 | pos.setIndex(index + strings[k].length());
|
---|
682 | break;
|
---|
683 | }
|
---|
684 | }
|
---|
685 | if (! found_zone)
|
---|
686 | {
|
---|
687 | pos.setErrorIndex(pos.getIndex());
|
---|
688 | return null;
|
---|
689 | }
|
---|
690 | break;
|
---|
691 | default:
|
---|
692 | pos.setErrorIndex(pos.getIndex());
|
---|
693 | return null;
|
---|
694 | }
|
---|
695 |
|
---|
696 | // Compute the value we should assign to the field.
|
---|
697 | int value;
|
---|
698 | int index = -1;
|
---|
699 | if (is_numeric)
|
---|
700 | {
|
---|
701 | numberFormat.setMinimumIntegerDigits(fmt_count);
|
---|
702 | if (maybe2DigitYear)
|
---|
703 | index = pos.getIndex();
|
---|
704 | Number n = numberFormat.parse(dateStr, pos);
|
---|
705 | if (pos == null || ! (n instanceof Long))
|
---|
706 | return null;
|
---|
707 | value = n.intValue() + offset;
|
---|
708 | }
|
---|
709 | else if (match != null)
|
---|
710 | {
|
---|
711 | index = pos.getIndex();
|
---|
712 | int i;
|
---|
713 | for (i = offset; i < match.length; ++i)
|
---|
714 | {
|
---|
715 | if (dateStr.startsWith(match[i], index))
|
---|
716 | break;
|
---|
717 | }
|
---|
718 | if (i == match.length)
|
---|
719 | {
|
---|
720 | pos.setErrorIndex(index);
|
---|
721 | return null;
|
---|
722 | }
|
---|
723 | pos.setIndex(index + match[i].length());
|
---|
724 | value = i;
|
---|
725 | }
|
---|
726 | else
|
---|
727 | value = offset;
|
---|
728 |
|
---|
729 | if (maybe2DigitYear)
|
---|
730 | {
|
---|
731 | // Parse into default century if the numeric year string has
|
---|
732 | // exactly 2 digits.
|
---|
733 | int digit_count = pos.getIndex() - index;
|
---|
734 | if (digit_count == 2)
|
---|
735 | is2DigitYear = true;
|
---|
736 | }
|
---|
737 |
|
---|
738 | // Assign the value and move on.
|
---|
739 | calendar.set(calendar_field, value);
|
---|
740 | }
|
---|
741 |
|
---|
742 | if (is2DigitYear)
|
---|
743 | {
|
---|
744 | // Apply the 80-20 heuristic to dermine the full year based on
|
---|
745 | // defaultCenturyStart.
|
---|
746 | int year = defaultCentury + calendar.get(Calendar.YEAR);
|
---|
747 | calendar.set(Calendar.YEAR, year);
|
---|
748 | if (calendar.getTime().compareTo(defaultCenturyStart) < 0)
|
---|
749 | calendar.set(Calendar.YEAR, year + 100);
|
---|
750 | }
|
---|
751 |
|
---|
752 | try
|
---|
753 | {
|
---|
754 | if (! saw_timezone)
|
---|
755 | {
|
---|
756 | // Use the real rules to determine whether or not this
|
---|
757 | // particular time is in daylight savings.
|
---|
758 | calendar.clear (Calendar.DST_OFFSET);
|
---|
759 | calendar.clear (Calendar.ZONE_OFFSET);
|
---|
760 | }
|
---|
761 | return calendar.getTime();
|
---|
762 | }
|
---|
763 | catch (IllegalArgumentException x)
|
---|
764 | {
|
---|
765 | pos.setErrorIndex(pos.getIndex());
|
---|
766 | return null;
|
---|
767 | }
|
---|
768 | }
|
---|
769 |
|
---|
770 | // Compute the start of the current century as defined by
|
---|
771 | // get2DigitYearStart.
|
---|
772 | private void computeCenturyStart()
|
---|
773 | {
|
---|
774 | int year = calendar.get(Calendar.YEAR);
|
---|
775 | calendar.set(Calendar.YEAR, year - 80);
|
---|
776 | set2DigitYearStart(calendar.getTime());
|
---|
777 | }
|
---|
778 | }
|
---|