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