Overview Package Class Use Source Tree Index Deprecated About
GNU Classpath (0.95)
Frames | No Frames

Source for java.text.SimpleDateFormat

 1:  /* SimpleDateFormat.java -- A class for parsing/formating simple 
 2:  date constructs
 3:  Copyright (C) 1998, 1999, 2000, 2001, 2003, 2004, 2005
 4:  Free Software Foundation, Inc.
 5: 
 6: This file is part of GNU Classpath.
 7: 
 8: GNU Classpath is free software; you can redistribute it and/or modify
 9: it under the terms of the GNU General Public License as published by
 10: the Free Software Foundation; either version 2, or (at your option)
 11: any later version.
 12:  
 13: GNU Classpath is distributed in the hope that it will be useful, but
 14: WITHOUT ANY WARRANTY; without even the implied warranty of
 15: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 16: General Public License for more details.
 17: 
 18: You should have received a copy of the GNU General Public License
 19: along with GNU Classpath; see the file COPYING. If not, write to the
 20: Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 21: 02110-1301 USA.
 22: 
 23: Linking this library statically or dynamically with other modules is
 24: making a combined work based on this library. Thus, the terms and
 25: conditions of the GNU General Public License cover the whole
 26: combination.
 27: 
 28: As a special exception, the copyright holders of this library give you
 29: permission to link this library with independent modules to produce an
 30: executable, regardless of the license terms of these independent
 31: modules, and to copy and distribute the resulting executable under
 32: terms of your choice, provided that you also meet, for each linked
 33: independent module, the terms and conditions of the license of that
 34: module. An independent module is a module which is not derived from
 35: or based on this library. If you modify this library, you may extend
 36: this exception to your version of the library, but you are not
 37: obligated to do so. If you do not wish to do so, delete this
 38: exception statement from your version. */
 39: 
 40: 
 41:  package java.text;
 42: 
 43:  import gnu.java.text.AttributedFormatBuffer;
 44:  import gnu.java.text.FormatBuffer;
 45:  import gnu.java.text.FormatCharacterIterator;
 46:  import gnu.java.text.StringFormatBuffer;
 47: 
 48:  import java.io.IOException;
 49:  import java.io.InvalidObjectException;
 50:  import java.io.ObjectInputStream;
 51:  import java.util.ArrayList;
 52:  import java.util.Calendar;
 53:  import java.util.Date;
 54:  import java.util.GregorianCalendar;
 55:  import java.util.Iterator;
 56:  import java.util.Locale;
 57:  import java.util.TimeZone;
 58:  import java.util.regex.Matcher;
 59:  import java.util.regex.Pattern;
 60: 
 61:  /**
 62:  * SimpleDateFormat provides convenient methods for parsing and formatting
 63:  * dates using Gregorian calendars (see java.util.GregorianCalendar). 
 64:  */
 65:  public class SimpleDateFormat extends DateFormat 
 66: {
 67:  /** 
 68:  * This class is used by <code>SimpleDateFormat</code> as a
 69:  * compiled representation of a format string. The field
 70:  * ID, size, and character used are stored for each sequence
 71:  * of pattern characters.
 72:  */
 73:  private class CompiledField
 74:  {
 75:  /**
 76:  * The ID of the field within the local pattern characters.
 77:  * Package private for use in out class.
 78:  */
 79:  int field;
 80: 
 81:  /**
 82:  * The size of the character sequence.
 83:  * Package private for use in out class.
 84:  */
 85:  int size;
 86: 
 87:  /**
 88:  * The character used.
 89:  */
 90:  private char character;
 91: 
 92:  /** 
 93:  * Constructs a compiled field using the
 94:  * the given field ID, size and character
 95:  * values.
 96:  *
 97:  * @param f the field ID.
 98:  * @param s the size of the field.
 99:  * @param c the character used.
 100:  */
 101:  public CompiledField(int f, int s, char c)
 102:  {
 103:  field = f;
 104:  size = s;
 105:  character = c;
 106:  }
 107: 
 108:  /**
 109:  * Retrieves the ID of the field relative to
 110:  * the local pattern characters.
 111:  */
 112:  public int getField()
 113:  {
 114:  return field;
 115:  }
 116: 
 117:  /**
 118:  * Retrieves the size of the character sequence.
 119:  */
 120:  public int getSize()
 121:  {
 122:  return size;
 123:  }
 124: 
 125:  /**
 126:  * Retrieves the character used in the sequence.
 127:  */
 128:  public char getCharacter()
 129:  {
 130:  return character;
 131:  }
 132: 
 133:  /**
 134:  * Returns a <code>String</code> representation
 135:  * of the compiled field, primarily for debugging
 136:  * purposes.
 137:  *
 138:  * @return a <code>String</code> representation.
 139:  */
 140:  public String toString()
 141:  {
 142:  StringBuffer builder;
 143: 
 144:  builder = new StringBuffer(getClass().getName());
 145:  builder.append("[field=");
 146:  builder.append(field);
 147:  builder.append(", size=");
 148:  builder.append(size);
 149:  builder.append(", character=");
 150:  builder.append(character);
 151:  builder.append("]");
 152: 
 153:  return builder.toString();
 154:  }
 155:  }
 156: 
 157:  /**
 158:  * A list of <code>CompiledField</code>s,
 159:  * representing the compiled version of the pattern.
 160:  *
 161:  * @see CompiledField
 162:  * @serial Ignored.
 163:  */
 164:  private transient ArrayList tokens;
 165: 
 166:  /**
 167:  * The localised data used in formatting,
 168:  * such as the day and month names in the local
 169:  * language, and the localized pattern characters.
 170:  *
 171:  * @see DateFormatSymbols
 172:  * @serial The localisation data. May not be null.
 173:  */
 174:  private DateFormatSymbols formatData;
 175: 
 176:  /**
 177:  * The date representing the start of the century
 178:  * used for interpreting two digit years. For
 179:  * example, 24/10/2004 would cause two digit
 180:  * years to be interpreted as representing
 181:  * the years between 2004 and 2104.
 182:  *
 183:  * @see #get2DigitYearStart()
 184:  * @see #set2DigitYearStart(java.util.Date)
 185:  * @see Date
 186:  * @serial The start date of the century for parsing two digit years.
 187:  * May not be null.
 188:  */
 189:  private Date defaultCenturyStart;
 190: 
 191:  /**
 192:  * The year at which interpretation of two
 193:  * digit years starts.
 194:  *
 195:  * @see #get2DigitYearStart()
 196:  * @see #set2DigitYearStart(java.util.Date)
 197:  * @serial Ignored.
 198:  */
 199:  private transient int defaultCentury;
 200: 
 201:  /**
 202:  * The non-localized pattern string. This
 203:  * only ever contains the pattern characters
 204:  * stored in standardChars. Localized patterns
 205:  * are translated to this form.
 206:  *
 207:  * @see #applyPattern(String)
 208:  * @see #applyLocalizedPattern(String)
 209:  * @see #toPattern()
 210:  * @see #toLocalizedPattern()
 211:  * @serial The non-localized pattern string. May not be null.
 212:  */
 213:  private String pattern;
 214: 
 215:  /**
 216:  * The version of serialized data used by this class.
 217:  * Version 0 only includes the pattern and formatting
 218:  * data. Version 1 adds the start date for interpreting
 219:  * two digit years.
 220:  *
 221:  * @serial This specifies the version of the data being serialized.
 222:  * Version 0 (or no version) specifies just <code>pattern</code>
 223:  * and <code>formatData</code>. Version 1 adds
 224:  * the <code>defaultCenturyStart</code>. This implementation
 225:  * always writes out version 1 data.
 226:  */
 227:  private int serialVersionOnStream = 1; // 0 indicates JDK1.1.3 or earlier
 228: 
 229:  /**
 230:  * For compatability.
 231:  */
 232:  private static final long serialVersionUID = 4774881970558875024L;
 233: 
 234:  // This string is specified in the root of the CLDR. We set it here
 235:  // rather than doing a DateFormatSymbols(Locale.US).getLocalPatternChars()
 236:  // since someone could theoretically change those values (though unlikely).
 237:  private static final String standardChars = "GyMdkHmsSEDFwWahKzYeugAZ";
 238: 
 239:  /**
 240:  * Reads the serialized version of this object.
 241:  * If the serialized data is only version 0,
 242:  * then the date for the start of the century
 243:  * for interpreting two digit years is computed.
 244:  * The pattern is parsed and compiled following the process
 245:  * of reading in the serialized data.
 246:  *
 247:  * @param stream the object stream to read the data from.
 248:  * @throws IOException if an I/O error occurs.
 249:  * @throws ClassNotFoundException if the class of the serialized data
 250:  * could not be found.
 251:  * @throws InvalidObjectException if the pattern is invalid.
 252:  */ 
 253:  private void readObject(ObjectInputStream stream)
 254:  throws IOException, ClassNotFoundException
 255:  {
 256:  stream.defaultReadObject();
 257:  if (serialVersionOnStream < 1)
 258:  {
 259:  computeCenturyStart ();
 260:  serialVersionOnStream = 1;
 261:  }
 262:  else
 263:  // Ensure that defaultCentury gets set.
 264:  set2DigitYearStart(defaultCenturyStart);
 265: 
 266:  // Set up items normally taken care of by the constructor.
 267:  tokens = new ArrayList();
 268:  try
 269:  {
 270:  compileFormat(pattern);
 271:  }
 272:  catch (IllegalArgumentException e)
 273:  {
 274:  throw new InvalidObjectException("The stream pattern was invalid.");
 275:  }
 276:  }
 277: 
 278:  /**
 279:  * Compiles the supplied non-localized pattern into a form
 280:  * from which formatting and parsing can be performed.
 281:  * This also detects errors in the pattern, which will
 282:  * be raised on later use of the compiled data.
 283:  *
 284:  * @param pattern the non-localized pattern to compile.
 285:  * @throws IllegalArgumentException if the pattern is invalid.
 286:  */
 287:  private void compileFormat(String pattern) 
 288:  {
 289:  // Any alphabetical characters are treated as pattern characters
 290:  // unless enclosed in single quotes.
 291: 
 292:  char thisChar;
 293:  int pos;
 294:  int field;
 295:  CompiledField current = null;
 296: 
 297:  for (int i = 0; i < pattern.length(); i++)
 298:  {
 299:  thisChar = pattern.charAt(i);
 300:  field = standardChars.indexOf(thisChar);
 301:  if (field == -1)
 302:  {
 303:  current = null;
 304:  if ((thisChar >= 'A' && thisChar <= 'Z')
 305:  || (thisChar >= 'a' && thisChar <= 'z'))
 306:  {
 307:  // Not a valid letter
 308:  throw new IllegalArgumentException("Invalid letter "
 309:  + thisChar +
 310:  " encountered at character "
 311:  + i + ".");
 312:  }
 313:  else if (thisChar == '\'')
 314:  {
 315:  // Quoted text section; skip to next single quote
 316:  pos = pattern.indexOf('\'', i + 1);
 317:  // First look for '' -- meaning a single quote.
 318:  if (pos == i + 1)
 319:  tokens.add("'");
 320:  else
 321:  {
 322:  // Look for the terminating quote. However, if we
 323:  // see a '', that represents a literal quote and
 324:  // we must iterate.
 325:  StringBuffer buf = new StringBuffer();
 326:  int oldPos = i + 1;
 327:  do
 328:  {
 329:  if (pos == -1)
 330:  throw new IllegalArgumentException("Quotes starting at character "
 331:  + i +
 332:  " not closed.");
 333:  buf.append(pattern.substring(oldPos, pos));
 334:  if (pos + 1 >= pattern.length()
 335:  || pattern.charAt(pos + 1) != '\'')
 336:  break;
 337:  buf.append('\'');
 338:  oldPos = pos + 2;
 339:  pos = pattern.indexOf('\'', pos + 2);
 340:  }
 341:  while (true);
 342:  tokens.add(buf.toString());
 343:  }
 344:  i = pos;
 345:  }
 346:  else
 347:  {
 348:  // A special character
 349:  tokens.add(new Character(thisChar));
 350:  }
 351:  }
 352:  else
 353:  {
 354:  // A valid field
 355:  if ((current != null) && (field == current.field))
 356:  current.size++;
 357:  else
 358:  {
 359:  current = new CompiledField(field, 1, thisChar);
 360:  tokens.add(current);
 361:  }
 362:  }
 363:  }
 364:  }
 365: 
 366:  /**
 367:  * Returns a string representation of this
 368:  * class.
 369:  *
 370:  * @return a string representation of the <code>SimpleDateFormat</code>
 371:  * instance.
 372:  */
 373:  public String toString() 
 374:  {
 375:  StringBuffer output = new StringBuffer(getClass().getName());
 376:  output.append("[tokens=");
 377:  output.append(tokens);
 378:  output.append(", formatData=");
 379:  output.append(formatData);
 380:  output.append(", defaultCenturyStart=");
 381:  output.append(defaultCenturyStart);
 382:  output.append(", defaultCentury=");
 383:  output.append(defaultCentury);
 384:  output.append(", pattern=");
 385:  output.append(pattern);
 386:  output.append(", serialVersionOnStream=");
 387:  output.append(serialVersionOnStream);
 388:  output.append(", standardChars=");
 389:  output.append(standardChars);
 390:  output.append("]");
 391:  return output.toString();
 392:  }
 393: 
 394:  /**
 395:  * Constructs a SimpleDateFormat using the default pattern for
 396:  * the default locale.
 397:  */
 398:  public SimpleDateFormat() 
 399:  {
 400:  /*
 401:  * There does not appear to be a standard API for determining 
 402:  * what the default pattern for a locale is, so use package-scope
 403:  * variables in DateFormatSymbols to encapsulate this.
 404:  */
 405:  super();
 406:  Locale locale = Locale.getDefault();
 407:  calendar = new GregorianCalendar(locale);
 408:  computeCenturyStart();
 409:  tokens = new ArrayList();
 410:  formatData = new DateFormatSymbols(locale);
 411:  pattern = (formatData.dateFormats[DEFAULT] + ' '
 412:  + formatData.timeFormats[DEFAULT]);
 413:  compileFormat(pattern);
 414:  numberFormat = NumberFormat.getInstance(locale);
 415:  numberFormat.setGroupingUsed (false);
 416:  numberFormat.setParseIntegerOnly (true);
 417:  numberFormat.setMaximumFractionDigits (0);
 418:  }
 419:  
 420:  /**
 421:  * Creates a date formatter using the specified non-localized pattern,
 422:  * with the default DateFormatSymbols for the default locale.
 423:  *
 424:  * @param pattern the pattern to use.
 425:  * @throws NullPointerException if the pattern is null.
 426:  * @throws IllegalArgumentException if the pattern is invalid.
 427:  */
 428:  public SimpleDateFormat(String pattern) 
 429:  {
 430:  this(pattern, Locale.getDefault());
 431:  }
 432: 
 433:  /**
 434:  * Creates a date formatter using the specified non-localized pattern,
 435:  * with the default DateFormatSymbols for the given locale.
 436:  *
 437:  * @param pattern the non-localized pattern to use.
 438:  * @param locale the locale to use for the formatting symbols.
 439:  * @throws NullPointerException if the pattern is null.
 440:  * @throws IllegalArgumentException if the pattern is invalid.
 441:  */
 442:  public SimpleDateFormat(String pattern, Locale locale) 
 443:  {
 444:  super();
 445:  calendar = new GregorianCalendar(locale);
 446:  computeCenturyStart();
 447:  tokens = new ArrayList();
 448:  formatData = new DateFormatSymbols(locale);
 449:  compileFormat(pattern);
 450:  this.pattern = pattern;
 451:  numberFormat = NumberFormat.getInstance(locale);
 452:  numberFormat.setGroupingUsed (false);
 453:  numberFormat.setParseIntegerOnly (true);
 454:  numberFormat.setMaximumFractionDigits (0);
 455:  }
 456: 
 457:  /**
 458:  * Creates a date formatter using the specified non-localized
 459:  * pattern. The specified DateFormatSymbols will be used when
 460:  * formatting.
 461:  *
 462:  * @param pattern the non-localized pattern to use.
 463:  * @param formatData the formatting symbols to use.
 464:  * @throws NullPointerException if the pattern or formatData is null.
 465:  * @throws IllegalArgumentException if the pattern is invalid.
 466:  */
 467:  public SimpleDateFormat(String pattern, DateFormatSymbols formatData)
 468:  {
 469:  super();
 470:  calendar = new GregorianCalendar();
 471:  computeCenturyStart ();
 472:  tokens = new ArrayList();
 473:  if (formatData == null)
 474:  throw new NullPointerException("formatData");
 475:  this.formatData = formatData;
 476:  compileFormat(pattern);
 477:  this.pattern = pattern;
 478:  numberFormat = NumberFormat.getInstance();
 479:  numberFormat.setGroupingUsed (false);
 480:  numberFormat.setParseIntegerOnly (true);
 481:  numberFormat.setMaximumFractionDigits (0);
 482:  }
 483: 
 484:  /**
 485:  * This method returns a string with the formatting pattern being used
 486:  * by this object. This string is unlocalized.
 487:  *
 488:  * @return The format string.
 489:  */
 490:  public String toPattern()
 491:  {
 492:  return pattern;
 493:  }
 494: 
 495:  /**
 496:  * This method returns a string with the formatting pattern being used
 497:  * by this object. This string is localized.
 498:  *
 499:  * @return The format string.
 500:  */
 501:  public String toLocalizedPattern()
 502:  {
 503:  String localChars = formatData.getLocalPatternChars();
 504:  return translateLocalizedPattern(pattern, standardChars, localChars);
 505:  }
 506: 
 507:  /**
 508:  * This method sets the formatting pattern that should be used by this
 509:  * object. This string is not localized.
 510:  *
 511:  * @param pattern The new format pattern.
 512:  * @throws NullPointerException if the pattern is null.
 513:  * @throws IllegalArgumentException if the pattern is invalid.
 514:  */
 515:  public void applyPattern(String pattern)
 516:  {
 517:  tokens = new ArrayList();
 518:  compileFormat(pattern);
 519:  this.pattern = pattern;
 520:  }
 521: 
 522:  /**
 523:  * This method sets the formatting pattern that should be used by this
 524:  * object. This string is localized.
 525:  *
 526:  * @param pattern The new format pattern.
 527:  * @throws NullPointerException if the pattern is null.
 528:  * @throws IllegalArgumentException if the pattern is invalid.
 529:  */
 530:  public void applyLocalizedPattern(String pattern)
 531:  {
 532:  String localChars = formatData.getLocalPatternChars();
 533:  pattern = translateLocalizedPattern(pattern, localChars, standardChars);
 534:  applyPattern(pattern);
 535:  }
 536: 
 537:  /**
 538:  * Translates either from or to a localized variant of the pattern
 539:  * string. For example, in the German locale, 't' (for 'tag') is
 540:  * used instead of 'd' (for 'date'). This method translates
 541:  * a localized pattern (such as 'ttt') to a non-localized pattern
 542:  * (such as 'ddd'), or vice versa. Non-localized patterns use
 543:  * a standard set of characters, which match those of the U.S. English
 544:  * locale.
 545:  *
 546:  * @param pattern the pattern to translate.
 547:  * @param oldChars the old set of characters (used in the pattern).
 548:  * @param newChars the new set of characters (which will be used in the
 549:  * pattern).
 550:  * @return a version of the pattern using the characters in
 551:  * <code>newChars</code>.
 552:  */
 553:  private String translateLocalizedPattern(String pattern,
 554:  String oldChars, String newChars)
 555:  {
 556:  int len = pattern.length();
 557:  StringBuffer buf = new StringBuffer(len);
 558:  boolean quoted = false;
 559:  for (int i = 0; i < len; i++)
 560:  {
 561:  char ch = pattern.charAt(i);
 562:  if (ch == '\'')
 563:  quoted = ! quoted;
 564:  if (! quoted)
 565:  {
 566:  int j = oldChars.indexOf(ch);
 567:  if (j >= 0)
 568:  ch = newChars.charAt(j);
 569:  }
 570:  buf.append(ch);
 571:  }
 572:  return buf.toString();
 573:  }
 574: 
 575:  /** 
 576:  * Returns the start of the century used for two digit years.
 577:  *
 578:  * @return A <code>Date</code> representing the start of the century
 579:  * for two digit years.
 580:  */
 581:  public Date get2DigitYearStart()
 582:  {
 583:  return defaultCenturyStart;
 584:  }
 585: 
 586:  /**
 587:  * Sets the start of the century used for two digit years.
 588:  *
 589:  * @param date A <code>Date</code> representing the start of the century for
 590:  * two digit years.
 591:  */
 592:  public void set2DigitYearStart(Date date)
 593:  {
 594:  defaultCenturyStart = date;
 595:  calendar.clear();
 596:  calendar.setTime(date);
 597:  int year = calendar.get(Calendar.YEAR);
 598:  defaultCentury = year - (year % 100);
 599:  }
 600: 
 601:  /**
 602:  * This method returns a copy of the format symbol information used
 603:  * for parsing and formatting dates.
 604:  *
 605:  * @return a copy of the date format symbols.
 606:  */
 607:  public DateFormatSymbols getDateFormatSymbols()
 608:  {
 609:  return (DateFormatSymbols) formatData.clone();
 610:  }
 611: 
 612:  /**
 613:  * This method sets the format symbols information used for parsing
 614:  * and formatting dates.
 615:  *
 616:  * @param formatData The date format symbols.
 617:  * @throws NullPointerException if <code>formatData</code> is null.
 618:  */
 619:  public void setDateFormatSymbols(DateFormatSymbols formatData)
 620:  {
 621:  if (formatData == null)
 622:  {
 623:  throw new
 624:  NullPointerException("The supplied format data was null.");
 625:  }
 626:  this.formatData = formatData;
 627:  }
 628: 
 629:  /**
 630:  * This methods tests whether the specified object is equal to this
 631:  * object. This will be true if and only if the specified object:
 632:  * <p>
 633:  * <ul>
 634:  * <li>Is not <code>null</code>.</li>
 635:  * <li>Is an instance of <code>SimpleDateFormat</code>.</li>
 636:  * <li>Is equal to this object at the superclass (i.e., <code>DateFormat</code>)
 637:  * level.</li>
 638:  * <li>Has the same formatting pattern.</li>
 639:  * <li>Is using the same formatting symbols.</li>
 640:  * <li>Is using the same century for two digit years.</li>
 641:  * </ul>
 642:  *
 643:  * @param o The object to compare for equality against.
 644:  *
 645:  * @return <code>true</code> if the specified object is equal to this object,
 646:  * <code>false</code> otherwise.
 647:  */
 648:  public boolean equals(Object o)
 649:  {
 650:  if (!super.equals(o))
 651:  return false;
 652: 
 653:  if (!(o instanceof SimpleDateFormat))
 654:  return false;
 655: 
 656:  SimpleDateFormat sdf = (SimpleDateFormat)o;
 657: 
 658:  if (defaultCentury != sdf.defaultCentury)
 659:  return false;
 660: 
 661:  if (!toPattern().equals(sdf.toPattern()))
 662:  return false;
 663: 
 664:  if (!getDateFormatSymbols().equals(sdf.getDateFormatSymbols()))
 665:  return false;
 666: 
 667:  return true;
 668:  }
 669: 
 670:  /**
 671:  * This method returns a hash value for this object.
 672:  *
 673:  * @return A hash value for this object.
 674:  */
 675:  public int hashCode()
 676:  {
 677:  return super.hashCode() ^ toPattern().hashCode() ^ defaultCentury ^
 678:  getDateFormatSymbols().hashCode();
 679:  }
 680: 
 681: 
 682:  /**
 683:  * Formats the date input according to the format string in use,
 684:  * appending to the specified StringBuffer. The input StringBuffer
 685:  * is returned as output for convenience.
 686:  */
 687:  private void formatWithAttribute(Date date, FormatBuffer buffer, FieldPosition pos)
 688:  {
 689:  String temp;
 690:  AttributedCharacterIterator.Attribute attribute;
 691:  calendar.setTime(date);
 692: 
 693:  // go through vector, filling in fields where applicable, else toString
 694:  Iterator iter = tokens.iterator();
 695:  while (iter.hasNext())
 696:  {
 697:  Object o = iter.next();
 698:  if (o instanceof CompiledField)
 699:  {
 700:  CompiledField cf = (CompiledField) o;
 701:  int beginIndex = buffer.length();
 702:  
 703:  switch (cf.getField())
 704:  {
 705:  case ERA_FIELD:
 706:  buffer.append (formatData.eras[calendar.get (Calendar.ERA)], DateFormat.Field.ERA);
 707:  break;
 708:  case YEAR_FIELD:
 709:  // If we have two digits, then we truncate. Otherwise, we
 710:  // use the size of the pattern, and zero pad.
 711:  buffer.setDefaultAttribute (DateFormat.Field.YEAR);
 712:  if (cf.getSize() == 2)
 713:  {
 714:  temp = "00"+String.valueOf (calendar.get (Calendar.YEAR));
 715:  buffer.append (temp.substring (temp.length() - 2));
 716:  }
 717:  else
 718:  withLeadingZeros (calendar.get (Calendar.YEAR), cf.getSize(), buffer);
 719:  break;
 720:  case MONTH_FIELD:
 721:  buffer.setDefaultAttribute (DateFormat.Field.MONTH);
 722:  if (cf.getSize() < 3)
 723:  withLeadingZeros (calendar.get (Calendar.MONTH) + 1, cf.getSize(), buffer);
 724:  else if (cf.getSize() < 4)
 725:  buffer.append (formatData.shortMonths[calendar.get (Calendar.MONTH)]);
 726:  else
 727:  buffer.append (formatData.months[calendar.get (Calendar.MONTH)]);
 728:  break;
 729:  case DATE_FIELD:
 730:  buffer.setDefaultAttribute (DateFormat.Field.DAY_OF_MONTH);
 731:  withLeadingZeros (calendar.get (Calendar.DATE), cf.getSize(), buffer);
 732:  break;
 733:  case HOUR_OF_DAY1_FIELD: // 1-24
 734:  buffer.setDefaultAttribute(DateFormat.Field.HOUR_OF_DAY1);
 735:  withLeadingZeros ( ((calendar.get (Calendar.HOUR_OF_DAY) + 23) % 24) + 1, 
 736:  cf.getSize(), buffer);
 737:  break;
 738:  case HOUR_OF_DAY0_FIELD: // 0-23
 739:  buffer.setDefaultAttribute (DateFormat.Field.HOUR_OF_DAY0);
 740:  withLeadingZeros (calendar.get (Calendar.HOUR_OF_DAY), cf.getSize(), buffer);
 741:  break;
 742:  case MINUTE_FIELD:
 743:  buffer.setDefaultAttribute (DateFormat.Field.MINUTE);
 744:  withLeadingZeros (calendar.get (Calendar.MINUTE),
 745:  cf.getSize(), buffer);
 746:  break;
 747:  case SECOND_FIELD:
 748:  buffer.setDefaultAttribute (DateFormat.Field.SECOND);
 749:  withLeadingZeros(calendar.get (Calendar.SECOND), 
 750:  cf.getSize(), buffer);
 751:  break;
 752:  case MILLISECOND_FIELD:
 753:  buffer.setDefaultAttribute (DateFormat.Field.MILLISECOND);
 754:  withLeadingZeros (calendar.get (Calendar.MILLISECOND), cf.getSize(), buffer);
 755:  break;
 756:  case DAY_OF_WEEK_FIELD:
 757:  buffer.setDefaultAttribute (DateFormat.Field.DAY_OF_WEEK);
 758:  if (cf.getSize() < 4)
 759:  buffer.append (formatData.shortWeekdays[calendar.get (Calendar.DAY_OF_WEEK)]);
 760:  else
 761:  buffer.append (formatData.weekdays[calendar.get (Calendar.DAY_OF_WEEK)]);
 762:  break;
 763:  case DAY_OF_YEAR_FIELD:
 764:  buffer.setDefaultAttribute (DateFormat.Field.DAY_OF_YEAR);
 765:  withLeadingZeros (calendar.get (Calendar.DAY_OF_YEAR), cf.getSize(), buffer);
 766:  break;
 767:  case DAY_OF_WEEK_IN_MONTH_FIELD:
 768:  buffer.setDefaultAttribute (DateFormat.Field.DAY_OF_WEEK_IN_MONTH);
 769:  withLeadingZeros (calendar.get (Calendar.DAY_OF_WEEK_IN_MONTH), 
 770:  cf.getSize(), buffer);
 771:  break;
 772:  case WEEK_OF_YEAR_FIELD:
 773:  buffer.setDefaultAttribute (DateFormat.Field.WEEK_OF_YEAR);
 774:  withLeadingZeros (calendar.get (Calendar.WEEK_OF_YEAR),
 775:  cf.getSize(), buffer);
 776:  break;
 777:  case WEEK_OF_MONTH_FIELD:
 778:  buffer.setDefaultAttribute (DateFormat.Field.WEEK_OF_MONTH);
 779:  withLeadingZeros (calendar.get (Calendar.WEEK_OF_MONTH),
 780:  cf.getSize(), buffer);
 781:  break;
 782:  case AM_PM_FIELD:
 783:  buffer.setDefaultAttribute (DateFormat.Field.AM_PM);
 784:  buffer.append (formatData.ampms[calendar.get (Calendar.AM_PM)]);
 785:  break;
 786:  case HOUR1_FIELD: // 1-12
 787:  buffer.setDefaultAttribute (DateFormat.Field.HOUR1);
 788:  withLeadingZeros (((calendar.get (Calendar.HOUR) + 11) % 12) + 1,
 789:  cf.getSize(), buffer);
 790:  break;
 791:  case HOUR0_FIELD: // 0-11
 792:  buffer.setDefaultAttribute (DateFormat.Field.HOUR0);
 793:  withLeadingZeros (calendar.get (Calendar.HOUR), cf.getSize(), buffer);
 794:  break;
 795:  case TIMEZONE_FIELD:
 796:  buffer.setDefaultAttribute (DateFormat.Field.TIME_ZONE);
 797:  TimeZone zone = calendar.getTimeZone();
 798:  boolean isDST = calendar.get (Calendar.DST_OFFSET) != 0;
 799:  // FIXME: XXX: This should be a localized time zone.
 800:  String zoneID = zone.getDisplayName
 801:  (isDST, cf.getSize() > 3 ? TimeZone.LONG : TimeZone.SHORT);
 802:  buffer.append (zoneID);
 803:  break;
 804:  case RFC822_TIMEZONE_FIELD:
 805:  buffer.setDefaultAttribute(DateFormat.Field.RFC822_TIME_ZONE);
 806:  int pureMinutes = (calendar.get(Calendar.ZONE_OFFSET) +
 807:  calendar.get(Calendar.DST_OFFSET)) / (1000 * 60);
 808:  String sign = (pureMinutes < 0) ? "-" : "+";
 809:  pureMinutes = Math.abs(pureMinutes);
 810:  int hours = pureMinutes / 60;
 811:  int minutes = pureMinutes % 60;
 812:  buffer.append(sign);
 813:  withLeadingZeros(hours, 2, buffer);
 814:  withLeadingZeros(minutes, 2, buffer);
 815:  break;
 816:  default:
 817:  throw new IllegalArgumentException ("Illegal pattern character " +
 818:  cf.getCharacter());
 819:  }
 820:  if (pos != null && (buffer.getDefaultAttribute() == pos.getFieldAttribute()
 821:  || cf.getField() == pos.getField()))
 822:  {
 823:  pos.setBeginIndex(beginIndex);
 824:  pos.setEndIndex(buffer.length());
 825:  }
 826:  } 
 827:  else
 828:  { 
 829:  buffer.append(o.toString(), null);
 830:  }
 831:  }
 832:  }
 833:  
 834:  public StringBuffer format(Date date, StringBuffer buffer, FieldPosition pos)
 835:  {
 836:  formatWithAttribute(date, new StringFormatBuffer (buffer), pos);
 837: 
 838:  return buffer;
 839:  }
 840: 
 841:  public AttributedCharacterIterator formatToCharacterIterator(Object date)
 842:  throws IllegalArgumentException
 843:  {
 844:  if (date == null)
 845:  throw new NullPointerException("null argument");
 846:  if (!(date instanceof Date))
 847:  throw new IllegalArgumentException("argument should be an instance of java.util.Date");
 848: 
 849:  AttributedFormatBuffer buf = new AttributedFormatBuffer();
 850:  formatWithAttribute((Date)date, buf,
 851:  null);
 852:  buf.sync();
 853:  
 854:  return new FormatCharacterIterator(buf.getBuffer().toString(),
 855:  buf.getRanges(),
 856:  buf.getAttributes());
 857:  }
 858: 
 859:  private void withLeadingZeros(int value, int length, FormatBuffer buffer) 
 860:  {
 861:  String valStr = String.valueOf(value);
 862:  for (length -= valStr.length(); length > 0; length--)
 863:  buffer.append('0');
 864:  buffer.append(valStr);
 865:  }
 866: 
 867:  private boolean expect(String source, ParsePosition pos, char ch)
 868:  {
 869:  int x = pos.getIndex();
 870:  boolean r = x < source.length() && source.charAt(x) == ch;
 871:  if (r)
 872:  pos.setIndex(x + 1);
 873:  else
 874:  pos.setErrorIndex(x);
 875:  return r;
 876:  }
 877: 
 878:  /**
 879:  * This method parses the specified string into a date.
 880:  * 
 881:  * @param dateStr The date string to parse.
 882:  * @param pos The input and output parse position
 883:  *
 884:  * @return The parsed date, or <code>null</code> if the string cannot be
 885:  * parsed.
 886:  */
 887:  public Date parse (String dateStr, ParsePosition pos)
 888:  {
 889:  int fmt_index = 0;
 890:  int fmt_max = pattern.length();
 891: 
 892:  calendar.clear();
 893:  boolean saw_timezone = false;
 894:  int quote_start = -1;
 895:  boolean is2DigitYear = false;
 896:  try
 897:  {
 898:  for (; fmt_index < fmt_max; ++fmt_index)
 899:  {
 900:  char ch = pattern.charAt(fmt_index);
 901:  if (ch == '\'')
 902:  {
 903:  int index = pos.getIndex();
 904:  if (fmt_index < fmt_max - 1
 905:  && pattern.charAt(fmt_index + 1) == '\'')
 906:  {
 907:  if (! expect (dateStr, pos, ch))
 908:  return null;
 909:  ++fmt_index;
 910:  }
 911:  else
 912:  quote_start = quote_start < 0 ? fmt_index : -1;
 913:  continue;
 914:  }
 915:  
 916:  if (quote_start != -1
 917:  || ((ch < 'a' || ch > 'z')
 918:  && (ch < 'A' || ch > 'Z')))
 919:  {
 920:  if (quote_start == -1 && ch == ' ')
 921:  {
 922:  // A single unquoted space in the pattern may match
 923:  // any number of spaces in the input.
 924:  int index = pos.getIndex();
 925:  int save = index;
 926:  while (index < dateStr.length()
 927:  && Character.isWhitespace(dateStr.charAt(index)))
 928:  ++index;
 929:  if (index > save)
 930:  pos.setIndex(index);
 931:  else
 932:  {
 933:  // Didn't see any whitespace.
 934:  pos.setErrorIndex(index);
 935:  return null;
 936:  }
 937:  }
 938:  else if (! expect (dateStr, pos, ch))
 939:  return null;
 940:  continue;
 941:  }
 942:  
 943:  // We've arrived at a potential pattern character in the
 944:  // pattern.
 945:  int fmt_count = 1;
 946:  while (++fmt_index < fmt_max && pattern.charAt(fmt_index) == ch)
 947:  {
 948:  ++fmt_count;
 949:  }
 950:  
 951:  // We might need to limit the number of digits to parse in
 952:  // some cases. We look to the next pattern character to
 953:  // decide.
 954:  boolean limit_digits = false;
 955:  if (fmt_index < fmt_max
 956:  && standardChars.indexOf(pattern.charAt(fmt_index)) >= 0)
 957:  limit_digits = true;
 958:  --fmt_index;
 959:  
 960:  // We can handle most fields automatically: most either are
 961:  // numeric or are looked up in a string vector. In some cases
 962:  // we need an offset. When numeric, `offset' is added to the
 963:  // resulting value. When doing a string lookup, offset is the
 964:  // initial index into the string array.
 965:  int calendar_field;
 966:  boolean is_numeric = true;
 967:  int offset = 0;
 968:  boolean maybe2DigitYear = false;
 969:  boolean oneBasedHour = false;
 970:  boolean oneBasedHourOfDay = false;
 971:  Integer simpleOffset;
 972:  String[] set1 = null;
 973:  String[] set2 = null;
 974:  switch (ch)
 975:  {
 976:  case 'd':
 977:  calendar_field = Calendar.DATE;
 978:  break;
 979:  case 'D':
 980:  calendar_field = Calendar.DAY_OF_YEAR;
 981:  break;
 982:  case 'F':
 983:  calendar_field = Calendar.DAY_OF_WEEK_IN_MONTH;
 984:  break;
 985:  case 'E':
 986:  is_numeric = false;
 987:  offset = 1;
 988:  calendar_field = Calendar.DAY_OF_WEEK;
 989:  set1 = formatData.getWeekdays();
 990:  set2 = formatData.getShortWeekdays();
 991:  break;
 992:  case 'w':
 993:  calendar_field = Calendar.WEEK_OF_YEAR;
 994:  break;
 995:  case 'W':
 996:  calendar_field = Calendar.WEEK_OF_MONTH;
 997:  break;
 998:  case 'M':
 999:  calendar_field = Calendar.MONTH;
1000:  if (fmt_count <= 2)
1001:  offset = -1;
1002:  else
1003:  {
1004:  is_numeric = false;
1005:  set1 = formatData.getMonths();
1006:  set2 = formatData.getShortMonths();
1007:  }
1008:  break;
1009:  case 'y':
1010:  calendar_field = Calendar.YEAR;
1011:  if (fmt_count <= 2)
1012:  maybe2DigitYear = true;
1013:  break;
1014:  case 'K':
1015:  calendar_field = Calendar.HOUR;
1016:  break;
1017:  case 'h':
1018:  calendar_field = Calendar.HOUR;
1019:  oneBasedHour = true;
1020:  break;
1021:  case 'H':
1022:  calendar_field = Calendar.HOUR_OF_DAY;
1023:  break;
1024:  case 'k':
1025:  calendar_field = Calendar.HOUR_OF_DAY;
1026:  oneBasedHourOfDay = true;
1027:  break;
1028:  case 'm':
1029:  calendar_field = Calendar.MINUTE;
1030:  break;
1031:  case 's':
1032:  calendar_field = Calendar.SECOND;
1033:  break;
1034:  case 'S':
1035:  calendar_field = Calendar.MILLISECOND;
1036:  break;
1037:  case 'a':
1038:  is_numeric = false;
1039:  calendar_field = Calendar.AM_PM;
1040:  set1 = formatData.getAmPmStrings();
1041:  break;
1042:  case 'z':
1043:  case 'Z':
1044:  // We need a special case for the timezone, because it
1045:  // uses a different data structure than the other cases.
1046:  is_numeric = false;
1047:  calendar_field = Calendar.ZONE_OFFSET;
1048:  String[][] zoneStrings = formatData.getZoneStrings();
1049:  int zoneCount = zoneStrings.length;
1050:  int index = pos.getIndex();
1051:  boolean found_zone = false;
1052:  simpleOffset = computeOffset(dateStr.substring(index), pos);
1053:  if (simpleOffset != null)
1054:  {
1055:  found_zone = true;
1056:  saw_timezone = true;
1057:  calendar.set(Calendar.DST_OFFSET, 0);
1058:  offset = simpleOffset.intValue();
1059:  }
1060:  else
1061:  {
1062:  for (int j = 0; j < zoneCount; j++)
1063:  {
1064:  String[] strings = zoneStrings[j];
1065:  int k;
1066:  for (k = 0; k < strings.length; ++k)
1067:  {
1068:  if (dateStr.startsWith(strings[k], index))
1069:  break;
1070:  }
1071:  if (k != strings.length)
1072:  {
1073:  found_zone = true;
1074:  saw_timezone = true;
1075:  TimeZone tz = TimeZone.getTimeZone (strings[0]);
1076:  // Check if it's a DST zone or ordinary 
1077:  if(k == 3 || k == 4)
1078:  calendar.set (Calendar.DST_OFFSET, tz.getDSTSavings());
1079:  else
1080:  calendar.set (Calendar.DST_OFFSET, 0);
1081:  offset = tz.getRawOffset ();
1082:  pos.setIndex(index + strings[k].length());
1083:  break;
1084:  }
1085:  }
1086:  }
1087:  if (! found_zone)
1088:  {
1089:  pos.setErrorIndex(pos.getIndex());
1090:  return null;
1091:  }
1092:  break;
1093:  default:
1094:  pos.setErrorIndex(pos.getIndex());
1095:  return null;
1096:  }
1097:  
1098:  // Compute the value we should assign to the field.
1099:  int value;
1100:  int index = -1;
1101:  if (is_numeric)
1102:  {
1103:  numberFormat.setMinimumIntegerDigits(fmt_count);
1104:  if (maybe2DigitYear)
1105:  index = pos.getIndex();
1106:  Number n = null;
1107:  if (limit_digits)
1108:  {
1109:  // numberFormat.setMaximumIntegerDigits(fmt_count) may
1110:  // not work as expected. So we explicitly use substring
1111:  // of dateStr.
1112:  int origPos = pos.getIndex();
1113:  pos.setIndex(0);
1114:  n = numberFormat.parse(dateStr.substring(origPos, origPos + fmt_count), pos);
1115:  pos.setIndex(origPos + pos.getIndex());
1116:  }
1117:  else
1118:  n = numberFormat.parse(dateStr, pos);
1119:  if (pos == null || ! (n instanceof Long))
1120:  return null;
1121:  value = n.intValue() + offset;
1122:  }
1123:  else if (set1 != null)
1124:  {
1125:  index = pos.getIndex();
1126:  int i;
1127:  boolean found = false;
1128:  for (i = offset; i < set1.length; ++i)
1129:  {
1130:  if (set1[i] != null)
1131:  if (dateStr.toUpperCase().startsWith(set1[i].toUpperCase(),
1132:  index))
1133:  {
1134:  found = true;
1135:  pos.setIndex(index + set1[i].length());
1136:  break;
1137:  }
1138:  }
1139:  if (!found && set2 != null)
1140:  {
1141:  for (i = offset; i < set2.length; ++i)
1142:  {
1143:  if (set2[i] != null)
1144:  if (dateStr.toUpperCase().startsWith(set2[i].toUpperCase(),
1145:  index))
1146:  {
1147:  found = true;
1148:  pos.setIndex(index + set2[i].length());
1149:  break;
1150:  }
1151:  }
1152:  }
1153:  if (!found)
1154:  {
1155:  pos.setErrorIndex(index);
1156:  return null;
1157:  }
1158:  value = i;
1159:  }
1160:  else
1161:  value = offset;
1162:  
1163:  if (maybe2DigitYear)
1164:  {
1165:  // Parse into default century if the numeric year string has 
1166:  // exactly 2 digits.
1167:  int digit_count = pos.getIndex() - index;
1168:  if (digit_count == 2)
1169:  {
1170:  is2DigitYear = true;
1171:  value += defaultCentury;
1172:  }
1173:  }
1174:  
1175:  // Calendar uses 0-based hours. 
1176:  // I.e. 00:00 AM is midnight, not 12 AM or 24:00
1177:  if (oneBasedHour && value == 12)
1178:  value = 0;
1179: 
1180:  if (oneBasedHourOfDay && value == 24)
1181:  value = 0;
1182:  
1183:  // Assign the value and move on.
1184:  calendar.set(calendar_field, value);
1185:  }
1186:  
1187:  if (is2DigitYear)
1188:  {
1189:  // Apply the 80-20 heuristic to dermine the full year based on 
1190:  // defaultCenturyStart. 
1191:  int year = calendar.get(Calendar.YEAR);
1192:  if (calendar.getTime().compareTo(defaultCenturyStart) < 0)
1193:  calendar.set(Calendar.YEAR, year + 100); 
1194:  }
1195:  if (! saw_timezone)
1196:  {
1197:  // Use the real rules to determine whether or not this
1198:  // particular time is in daylight savings.
1199:  calendar.clear (Calendar.DST_OFFSET);
1200:  calendar.clear (Calendar.ZONE_OFFSET);
1201:  }
1202:  return calendar.getTime();
1203:  }
1204:  catch (IllegalArgumentException x)
1205:  {
1206:  pos.setErrorIndex(pos.getIndex());
1207:  return null;
1208:  }
1209:  }
1210: 
1211:  /**
1212:  * <p>
1213:  * Computes the time zone offset in milliseconds
1214:  * relative to GMT, based on the supplied
1215:  * <code>String</code> representation.
1216:  * </p>
1217:  * <p>
1218:  * The supplied <code>String</code> must be a three
1219:  * or four digit signed number, with an optional 'GMT'
1220:  * prefix. The first one or two digits represents the hours,
1221:  * while the last two represent the minutes. The
1222:  * two sets of digits can optionally be separated by
1223:  * ':'. The mandatory sign prefix (either '+' or '-')
1224:  * indicates the direction of the offset from GMT.
1225:  * </p>
1226:  * <p>
1227:  * For example, 'GMT+0200' specifies 2 hours after
1228:  * GMT, while '-05:00' specifies 5 hours prior to
1229:  * GMT. The special case of 'GMT' alone can be used
1230:  * to represent the offset, 0.
1231:  * </p>
1232:  * <p>
1233:  * If the <code>String</code> can not be parsed,
1234:  * the result will be null. The resulting offset
1235:  * is wrapped in an <code>Integer</code> object, in
1236:  * order to allow such failure to be represented.
1237:  * </p>
1238:  *
1239:  * @param zoneString a string in the form 
1240:  * (GMT)? sign hours : minutes
1241:  * where sign = '+' or '-', hours
1242:  * is a one or two digits representing
1243:  * a number between 0 and 23, and
1244:  * minutes is two digits representing
1245:  * a number between 0 and 59.
1246:  * @return the parsed offset, or null if parsing
1247:  * failed.
1248:  */
1249:  private Integer computeOffset(String zoneString, ParsePosition pos)
1250:  {
1251:  Pattern pattern = 
1252:  Pattern.compile("(GMT)?([+-])([012])?([0-9]):?([0-9]{2})");
1253:  Matcher matcher = pattern.matcher(zoneString);
1254: 
1255:  // Match from start, but ignore trailing parts
1256:  boolean hasAll = matcher.lookingAt();
1257:  try
1258:  {
1259:  // Do we have at least the sign, hour and minute?
1260:  matcher.group(2);
1261:  matcher.group(4);
1262:  matcher.group(5);
1263:  }
1264:  catch (IllegalStateException ise)
1265:  {
1266:  hasAll = false;
1267:  }
1268:  if (hasAll)
1269:  {
1270:  int sign = matcher.group(2).equals("+") ? 1 : -1;
1271:  int hour = Integer.parseInt(matcher.group(4));
1272:  if (!matcher.group(3).equals(""))
1273:  hour += (Integer.parseInt(matcher.group(3)) * 10);
1274:  int minutes = Integer.parseInt(matcher.group(5));
1275: 
1276:  if (hour > 23)
1277:  return null;
1278:  int offset = sign * ((hour * 60) + minutes) * 60000;
1279: 
1280:  // advance the index
1281:  pos.setIndex(pos.getIndex() + matcher.end());
1282:  return new Integer(offset);
1283:  }
1284:  else if (zoneString.startsWith("GMT"))
1285:  {
1286:  pos.setIndex(pos.getIndex() + 3);
1287:  return new Integer(0);
1288:  }
1289:  return null;
1290:  }
1291: 
1292:  // Compute the start of the current century as defined by
1293:  // get2DigitYearStart.
1294:  private void computeCenturyStart()
1295:  {
1296:  int year = calendar.get(Calendar.YEAR);
1297:  calendar.set(Calendar.YEAR, year - 80);
1298:  set2DigitYearStart(calendar.getTime());
1299:  }
1300: 
1301:  /**
1302:  * Returns a copy of this instance of
1303:  * <code>SimpleDateFormat</code>. The copy contains
1304:  * clones of the formatting symbols and the 2-digit
1305:  * year century start date.
1306:  */
1307:  public Object clone()
1308:  {
1309:  SimpleDateFormat clone = (SimpleDateFormat) super.clone();
1310:  clone.setDateFormatSymbols((DateFormatSymbols) formatData.clone());
1311:  clone.set2DigitYearStart((Date) defaultCenturyStart.clone());
1312:  return clone;
1313:  }
1314: 
1315: }
Overview Package Class Use Source Tree Index Deprecated About
GNU Classpath (0.95)

AltStyle によって変換されたページ (->オリジナル) /