Remove old autovect-branch by moving to "dead" directory.
[official-gcc.git] / old-autovect-branch / libjava / classpath / java / text / SimpleDateFormat.java
blobc0c4cf68ceabc6b29778818e531c84d6afe58dcb
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.
6 This file is part of GNU Classpath.
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.
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.
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.
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.
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. */
41 package java.text;
43 import gnu.java.text.AttributedFormatBuffer;
44 import gnu.java.text.FormatBuffer;
45 import gnu.java.text.FormatCharacterIterator;
46 import gnu.java.text.StringFormatBuffer;
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;
61 /**
62 * SimpleDateFormat provides convenient methods for parsing and formatting
63 * dates using Gregorian calendars (see java.util.GregorianCalendar).
65 public class SimpleDateFormat extends DateFormat
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.
73 private class CompiledField
75 /**
76 * The ID of the field within the local pattern characters.
77 * Package private for use in out class.
79 int field;
81 /**
82 * The size of the character sequence.
83 * Package private for use in out class.
85 int size;
87 /**
88 * The character used.
90 private char character;
92 /**
93 * Constructs a compiled field using the
94 * the given field ID, size and character
95 * values.
97 * @param f the field ID.
98 * @param s the size of the field.
99 * @param c the character used.
101 public CompiledField(int f, int s, char c)
103 field = f;
104 size = s;
105 character = c;
109 * Retrieves the ID of the field relative to
110 * the local pattern characters.
112 public int getField()
114 return field;
118 * Retrieves the size of the character sequence.
120 public int getSize()
122 return size;
126 * Retrieves the character used in the sequence.
128 public char getCharacter()
130 return character;
134 * Returns a <code>String</code> representation
135 * of the compiled field, primarily for debugging
136 * purposes.
138 * @return a <code>String</code> representation.
140 public String toString()
142 StringBuffer builder;
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("]");
153 return builder.toString();
158 * A list of <code>CompiledField</code>s,
159 * representing the compiled version of the pattern.
161 * @see CompiledField
162 * @serial Ignored.
164 private transient ArrayList tokens;
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.
171 * @see DateFormatSymbols
172 * @serial The localisation data. May not be null.
174 private DateFormatSymbols formatData;
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.
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.
189 private Date defaultCenturyStart;
192 * The year at which interpretation of two
193 * digit years starts.
195 * @see #get2DigitYearStart()
196 * @see #set2DigitYearStart(java.util.Date)
197 * @serial Ignored.
199 private transient int defaultCentury;
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.
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.
213 private String pattern;
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.
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.
227 private int serialVersionOnStream = 1; // 0 indicates JDK1.1.3 or earlier
230 * For compatability.
232 private static final long serialVersionUID = 4774881970558875024L;
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";
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.
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.
253 private void readObject(ObjectInputStream stream)
254 throws IOException, ClassNotFoundException
256 stream.defaultReadObject();
257 if (serialVersionOnStream < 1)
259 computeCenturyStart ();
260 serialVersionOnStream = 1;
262 else
263 // Ensure that defaultCentury gets set.
264 set2DigitYearStart(defaultCenturyStart);
266 // Set up items normally taken care of by the constructor.
267 tokens = new ArrayList();
270 compileFormat(pattern);
272 catch (IllegalArgumentException e)
274 throw new InvalidObjectException("The stream pattern was invalid.");
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.
284 * @param pattern the non-localized pattern to compile.
285 * @throws IllegalArgumentException if the pattern is invalid.
287 private void compileFormat(String pattern)
289 // Any alphabetical characters are treated as pattern characters
290 // unless enclosed in single quotes.
292 char thisChar;
293 int pos;
294 int field;
295 CompiledField current = null;
297 for (int i = 0; i < pattern.length(); i++)
299 thisChar = pattern.charAt(i);
300 field = standardChars.indexOf(thisChar);
301 if (field == -1)
303 current = null;
304 if ((thisChar >= 'A' && thisChar <= 'Z')
305 || (thisChar >= 'a' && thisChar <= 'z'))
307 // Not a valid letter
308 throw new IllegalArgumentException("Invalid letter "
309 + thisChar +
310 "encountered at character "
311 + i + ".");
313 else if (thisChar == '\'')
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
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;
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);
341 while (true);
342 tokens.add(buf.toString());
344 i = pos;
346 else
348 // A special character
349 tokens.add(new Character(thisChar));
352 else
354 // A valid field
355 if ((current != null) && (field == current.field))
356 current.size++;
357 else
359 current = new CompiledField(field, 1, thisChar);
360 tokens.add(current);
367 * Returns a string representation of this
368 * class.
370 * @return a string representation of the <code>SimpleDateFormat</code>
371 * instance.
373 public String toString()
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();
395 * Constructs a SimpleDateFormat using the default pattern for
396 * the default locale.
398 public SimpleDateFormat()
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.
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);
421 * Creates a date formatter using the specified non-localized pattern,
422 * with the default DateFormatSymbols for the default locale.
424 * @param pattern the pattern to use.
425 * @throws NullPointerException if the pattern is null.
426 * @throws IllegalArgumentException if the pattern is invalid.
428 public SimpleDateFormat(String pattern)
430 this(pattern, Locale.getDefault());
434 * Creates a date formatter using the specified non-localized pattern,
435 * with the default DateFormatSymbols for the given locale.
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.
442 public SimpleDateFormat(String pattern, Locale locale)
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);
458 * Creates a date formatter using the specified non-localized
459 * pattern. The specified DateFormatSymbols will be used when
460 * formatting.
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.
467 public SimpleDateFormat(String pattern, DateFormatSymbols formatData)
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);
485 * This method returns a string with the formatting pattern being used
486 * by this object. This string is unlocalized.
488 * @return The format string.
490 public String toPattern()
492 return pattern;
496 * This method returns a string with the formatting pattern being used
497 * by this object. This string is localized.
499 * @return The format string.
501 public String toLocalizedPattern()
503 String localChars = formatData.getLocalPatternChars();
504 return translateLocalizedPattern(pattern, standardChars, localChars);
508 * This method sets the formatting pattern that should be used by this
509 * object. This string is not localized.
511 * @param pattern The new format pattern.
512 * @throws NullPointerException if the pattern is null.
513 * @throws IllegalArgumentException if the pattern is invalid.
515 public void applyPattern(String pattern)
517 tokens = new ArrayList();
518 compileFormat(pattern);
519 this.pattern = pattern;
523 * This method sets the formatting pattern that should be used by this
524 * object. This string is localized.
526 * @param pattern The new format pattern.
527 * @throws NullPointerException if the pattern is null.
528 * @throws IllegalArgumentException if the pattern is invalid.
530 public void applyLocalizedPattern(String pattern)
532 String localChars = formatData.getLocalPatternChars();
533 pattern = translateLocalizedPattern(pattern, localChars, standardChars);
534 applyPattern(pattern);
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.
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>.
553 private String translateLocalizedPattern(String pattern,
554 String oldChars, String newChars)
556 int len = pattern.length();
557 StringBuffer buf = new StringBuffer(len);
558 boolean quoted = false;
559 for (int i = 0; i < len; i++)
561 char ch = pattern.charAt(i);
562 if (ch == '\'')
563 quoted = ! quoted;
564 if (! quoted)
566 int j = oldChars.indexOf(ch);
567 if (j >= 0)
568 ch = newChars.charAt(j);
570 buf.append(ch);
572 return buf.toString();
575 /**
576 * Returns the start of the century used for two digit years.
578 * @return A <code>Date</code> representing the start of the century
579 * for two digit years.
581 public Date get2DigitYearStart()
583 return defaultCenturyStart;
587 * Sets the start of the century used for two digit years.
589 * @param date A <code>Date</code> representing the start of the century for
590 * two digit years.
592 public void set2DigitYearStart(Date date)
594 defaultCenturyStart = date;
595 calendar.clear();
596 calendar.setTime(date);
597 int year = calendar.get(Calendar.YEAR);
598 defaultCentury = year - (year % 100);
602 * This method returns a copy of the format symbol information used
603 * for parsing and formatting dates.
605 * @return a copy of the date format symbols.
607 public DateFormatSymbols getDateFormatSymbols()
609 return (DateFormatSymbols) formatData.clone();
613 * This method sets the format symbols information used for parsing
614 * and formatting dates.
616 * @param formatData The date format symbols.
617 * @throws NullPointerException if <code>formatData</code> is null.
619 public void setDateFormatSymbols(DateFormatSymbols formatData)
621 if (formatData == null)
623 throw new
624 NullPointerException("The supplied format data was null.");
626 this.formatData = formatData;
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>
643 * @param o The object to compare for equality against.
645 * @return <code>true</code> if the specified object is equal to this object,
646 * <code>false</code> otherwise.
648 public boolean equals(Object o)
650 if (!super.equals(o))
651 return false;
653 if (!(o instanceof SimpleDateFormat))
654 return false;
656 SimpleDateFormat sdf = (SimpleDateFormat)o;
658 if (defaultCentury != sdf.defaultCentury)
659 return false;
661 if (!toPattern().equals(sdf.toPattern()))
662 return false;
664 if (!getDateFormatSymbols().equals(sdf.getDateFormatSymbols()))
665 return false;
667 return true;
671 * This method returns a hash value for this object.
673 * @return A hash value for this object.
675 public int hashCode()
677 return super.hashCode() ^ toPattern().hashCode() ^ defaultCentury ^
678 getDateFormatSymbols().hashCode();
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.
687 private void formatWithAttribute(Date date, FormatBuffer buffer, FieldPosition pos)
689 String temp;
690 AttributedCharacterIterator.Attribute attribute;
691 calendar.setTime(date);
693 // go through vector, filling in fields where applicable, else toString
694 Iterator iter = tokens.iterator();
695 while (iter.hasNext())
697 Object o = iter.next();
698 if (o instanceof CompiledField)
700 CompiledField cf = (CompiledField) o;
701 int beginIndex = buffer.length();
703 switch (cf.getField())
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)
714 temp = "00"+String.valueOf (calendar.get (Calendar.YEAR));
715 buffer.append (temp.substring (temp.length() - 2));
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 int hours = pureMinutes / 60;
810 int minutes = pureMinutes % 60;
811 buffer.append(sign);
812 withLeadingZeros(hours, 2, buffer);
813 withLeadingZeros(minutes, 2, buffer);
814 break;
815 default:
816 throw new IllegalArgumentException ("Illegal pattern character " +
817 cf.getCharacter());
819 if (pos != null && (buffer.getDefaultAttribute() == pos.getFieldAttribute()
820 || cf.getField() == pos.getField()))
822 pos.setBeginIndex(beginIndex);
823 pos.setEndIndex(buffer.length());
826 else
828 buffer.append(o.toString(), null);
833 public StringBuffer format(Date date, StringBuffer buffer, FieldPosition pos)
835 formatWithAttribute(date, new StringFormatBuffer (buffer), pos);
837 return buffer;
840 public AttributedCharacterIterator formatToCharacterIterator(Object date)
841 throws IllegalArgumentException
843 if (date == null)
844 throw new NullPointerException("null argument");
845 if (!(date instanceof Date))
846 throw new IllegalArgumentException("argument should be an instance of java.util.Date");
848 AttributedFormatBuffer buf = new AttributedFormatBuffer();
849 formatWithAttribute((Date)date, buf,
850 null);
851 buf.sync();
853 return new FormatCharacterIterator(buf.getBuffer().toString(),
854 buf.getRanges(),
855 buf.getAttributes());
858 private void withLeadingZeros(int value, int length, FormatBuffer buffer)
860 String valStr = String.valueOf(value);
861 for (length -= valStr.length(); length > 0; length--)
862 buffer.append('0');
863 buffer.append(valStr);
866 private boolean expect(String source, ParsePosition pos, char ch)
868 int x = pos.getIndex();
869 boolean r = x < source.length() && source.charAt(x) == ch;
870 if (r)
871 pos.setIndex(x + 1);
872 else
873 pos.setErrorIndex(x);
874 return r;
878 * This method parses the specified string into a date.
880 * @param dateStr The date string to parse.
881 * @param pos The input and output parse position
883 * @return The parsed date, or <code>null</code> if the string cannot be
884 * parsed.
886 public Date parse (String dateStr, ParsePosition pos)
888 int fmt_index = 0;
889 int fmt_max = pattern.length();
891 calendar.clear();
892 boolean saw_timezone = false;
893 int quote_start = -1;
894 boolean is2DigitYear = false;
897 for (; fmt_index < fmt_max; ++fmt_index)
899 char ch = pattern.charAt(fmt_index);
900 if (ch == '\'')
902 int index = pos.getIndex();
903 if (fmt_index < fmt_max - 1
904 && pattern.charAt(fmt_index + 1) == '\'')
906 if (! expect (dateStr, pos, ch))
907 return null;
908 ++fmt_index;
910 else
911 quote_start = quote_start < 0 ? fmt_index : -1;
912 continue;
915 if (quote_start != -1
916 || ((ch < 'a' || ch > 'z')
917 && (ch < 'A' || ch > 'Z')))
919 if (! expect (dateStr, pos, ch))
920 return null;
921 continue;
924 // We've arrived at a potential pattern character in the
925 // pattern.
926 int fmt_count = 1;
927 while (++fmt_index < fmt_max && pattern.charAt(fmt_index) == ch)
929 ++fmt_count;
932 // We might need to limit the number of digits to parse in
933 // some cases. We look to the next pattern character to
934 // decide.
935 boolean limit_digits = false;
936 if (fmt_index < fmt_max
937 && standardChars.indexOf(pattern.charAt(fmt_index)) >= 0)
938 limit_digits = true;
939 --fmt_index;
941 // We can handle most fields automatically: most either are
942 // numeric or are looked up in a string vector. In some cases
943 // we need an offset. When numeric, `offset' is added to the
944 // resulting value. When doing a string lookup, offset is the
945 // initial index into the string array.
946 int calendar_field;
947 boolean is_numeric = true;
948 int offset = 0;
949 boolean maybe2DigitYear = false;
950 boolean oneBasedHour = false;
951 boolean oneBasedHourOfDay = false;
952 Integer simpleOffset;
953 String[] set1 = null;
954 String[] set2 = null;
955 switch (ch)
957 case 'd':
958 calendar_field = Calendar.DATE;
959 break;
960 case 'D':
961 calendar_field = Calendar.DAY_OF_YEAR;
962 break;
963 case 'F':
964 calendar_field = Calendar.DAY_OF_WEEK_IN_MONTH;
965 break;
966 case 'E':
967 is_numeric = false;
968 offset = 1;
969 calendar_field = Calendar.DAY_OF_WEEK;
970 set1 = formatData.getWeekdays();
971 set2 = formatData.getShortWeekdays();
972 break;
973 case 'w':
974 calendar_field = Calendar.WEEK_OF_YEAR;
975 break;
976 case 'W':
977 calendar_field = Calendar.WEEK_OF_MONTH;
978 break;
979 case 'M':
980 calendar_field = Calendar.MONTH;
981 if (fmt_count <= 2)
982 offset = -1;
983 else
985 is_numeric = false;
986 set1 = formatData.getMonths();
987 set2 = formatData.getShortMonths();
989 break;
990 case 'y':
991 calendar_field = Calendar.YEAR;
992 if (fmt_count <= 2)
993 maybe2DigitYear = true;
994 break;
995 case 'K':
996 calendar_field = Calendar.HOUR;
997 break;
998 case 'h':
999 calendar_field = Calendar.HOUR;
1000 oneBasedHour = true;
1001 break;
1002 case 'H':
1003 calendar_field = Calendar.HOUR_OF_DAY;
1004 break;
1005 case 'k':
1006 calendar_field = Calendar.HOUR_OF_DAY;
1007 oneBasedHourOfDay = true;
1008 break;
1009 case 'm':
1010 calendar_field = Calendar.MINUTE;
1011 break;
1012 case 's':
1013 calendar_field = Calendar.SECOND;
1014 break;
1015 case 'S':
1016 calendar_field = Calendar.MILLISECOND;
1017 break;
1018 case 'a':
1019 is_numeric = false;
1020 calendar_field = Calendar.AM_PM;
1021 set1 = formatData.getAmPmStrings();
1022 break;
1023 case 'z':
1024 case 'Z':
1025 // We need a special case for the timezone, because it
1026 // uses a different data structure than the other cases.
1027 is_numeric = false;
1028 calendar_field = Calendar.ZONE_OFFSET;
1029 String[][] zoneStrings = formatData.getZoneStrings();
1030 int zoneCount = zoneStrings.length;
1031 int index = pos.getIndex();
1032 boolean found_zone = false;
1033 simpleOffset = computeOffset(dateStr.substring(index), pos);
1034 if (simpleOffset != null)
1036 found_zone = true;
1037 saw_timezone = true;
1038 calendar.set(Calendar.DST_OFFSET, 0);
1039 offset = simpleOffset.intValue();
1041 else
1043 for (int j = 0; j < zoneCount; j++)
1045 String[] strings = zoneStrings[j];
1046 int k;
1047 for (k = 0; k < strings.length; ++k)
1049 if (dateStr.startsWith(strings[k], index))
1050 break;
1052 if (k != strings.length)
1054 found_zone = true;
1055 saw_timezone = true;
1056 TimeZone tz = TimeZone.getTimeZone (strings[0]);
1057 // Check if it's a DST zone or ordinary
1058 if(k == 3 || k == 4)
1059 calendar.set (Calendar.DST_OFFSET, tz.getDSTSavings());
1060 else
1061 calendar.set (Calendar.DST_OFFSET, 0);
1062 offset = tz.getRawOffset ();
1063 pos.setIndex(index + strings[k].length());
1064 break;
1068 if (! found_zone)
1070 pos.setErrorIndex(pos.getIndex());
1071 return null;
1073 break;
1074 default:
1075 pos.setErrorIndex(pos.getIndex());
1076 return null;
1079 // Compute the value we should assign to the field.
1080 int value;
1081 int index = -1;
1082 if (is_numeric)
1084 numberFormat.setMinimumIntegerDigits(fmt_count);
1085 if (limit_digits)
1086 numberFormat.setMaximumIntegerDigits(fmt_count);
1087 if (maybe2DigitYear)
1088 index = pos.getIndex();
1089 Number n = numberFormat.parse(dateStr, pos);
1090 if (pos == null || ! (n instanceof Long))
1091 return null;
1092 value = n.intValue() + offset;
1094 else if (set1 != null)
1096 index = pos.getIndex();
1097 int i;
1098 boolean found = false;
1099 for (i = offset; i < set1.length; ++i)
1101 if (set1[i] != null)
1102 if (dateStr.toUpperCase().startsWith(set1[i].toUpperCase(),
1103 index))
1105 found = true;
1106 pos.setIndex(index + set1[i].length());
1107 break;
1110 if (!found && set2 != null)
1112 for (i = offset; i < set2.length; ++i)
1114 if (set2[i] != null)
1115 if (dateStr.toUpperCase().startsWith(set2[i].toUpperCase(),
1116 index))
1118 found = true;
1119 pos.setIndex(index + set2[i].length());
1120 break;
1124 if (!found)
1126 pos.setErrorIndex(index);
1127 return null;
1129 value = i;
1131 else
1132 value = offset;
1134 if (maybe2DigitYear)
1136 // Parse into default century if the numeric year string has
1137 // exactly 2 digits.
1138 int digit_count = pos.getIndex() - index;
1139 if (digit_count == 2)
1141 is2DigitYear = true;
1142 value += defaultCentury;
1146 // Calendar uses 0-based hours.
1147 // I.e. 00:00 AM is midnight, not 12 AM or 24:00
1148 if (oneBasedHour && value == 12)
1149 value = 0;
1151 if (oneBasedHourOfDay && value == 24)
1152 value = 0;
1154 // Assign the value and move on.
1155 calendar.set(calendar_field, value);
1158 if (is2DigitYear)
1160 // Apply the 80-20 heuristic to dermine the full year based on
1161 // defaultCenturyStart.
1162 int year = calendar.get(Calendar.YEAR);
1163 if (calendar.getTime().compareTo(defaultCenturyStart) < 0)
1164 calendar.set(Calendar.YEAR, year + 100);
1166 if (! saw_timezone)
1168 // Use the real rules to determine whether or not this
1169 // particular time is in daylight savings.
1170 calendar.clear (Calendar.DST_OFFSET);
1171 calendar.clear (Calendar.ZONE_OFFSET);
1173 return calendar.getTime();
1175 catch (IllegalArgumentException x)
1177 pos.setErrorIndex(pos.getIndex());
1178 return null;
1183 * <p>
1184 * Computes the time zone offset in milliseconds
1185 * relative to GMT, based on the supplied
1186 * <code>String</code> representation.
1187 * </p>
1188 * <p>
1189 * The supplied <code>String</code> must be a three
1190 * or four digit signed number, with an optional 'GMT'
1191 * prefix. The first one or two digits represents the hours,
1192 * while the last two represent the minutes. The
1193 * two sets of digits can optionally be separated by
1194 * ':'. The mandatory sign prefix (either '+' or '-')
1195 * indicates the direction of the offset from GMT.
1196 * </p>
1197 * <p>
1198 * For example, 'GMT+0200' specifies 2 hours after
1199 * GMT, while '-05:00' specifies 5 hours prior to
1200 * GMT. The special case of 'GMT' alone can be used
1201 * to represent the offset, 0.
1202 * </p>
1203 * <p>
1204 * If the <code>String</code> can not be parsed,
1205 * the result will be null. The resulting offset
1206 * is wrapped in an <code>Integer</code> object, in
1207 * order to allow such failure to be represented.
1208 * </p>
1210 * @param zoneString a string in the form
1211 * (GMT)? sign hours : minutes
1212 * where sign = '+' or '-', hours
1213 * is a one or two digits representing
1214 * a number between 0 and 23, and
1215 * minutes is two digits representing
1216 * a number between 0 and 59.
1217 * @return the parsed offset, or null if parsing
1218 * failed.
1220 private Integer computeOffset(String zoneString, ParsePosition pos)
1222 Pattern pattern =
1223 Pattern.compile("(GMT)?([+-])([012])?([0-9]):?([0-9]{2})");
1224 Matcher matcher = pattern.matcher(zoneString);
1226 // Match from start, but ignore trailing parts
1227 boolean hasAll = matcher.lookingAt();
1230 // Do we have at least the sign, hour and minute?
1231 matcher.group(2);
1232 matcher.group(4);
1233 matcher.group(5);
1235 catch (IllegalStateException ise)
1237 hasAll = false;
1239 if (hasAll)
1241 int sign = matcher.group(2).equals("+") ? 1 : -1;
1242 int hour = Integer.parseInt(matcher.group(4));
1243 if (!matcher.group(3).equals(""))
1244 hour += (Integer.parseInt(matcher.group(3)) * 10);
1245 int minutes = Integer.parseInt(matcher.group(5));
1247 if (hour > 23)
1248 return null;
1249 int offset = sign * ((hour * 60) + minutes) * 60000;
1251 // advance the index
1252 pos.setIndex(pos.getIndex() + matcher.end());
1253 return new Integer(offset);
1255 else if (zoneString.startsWith("GMT"))
1257 pos.setIndex(pos.getIndex() + 3);
1258 return new Integer(0);
1260 return null;
1263 // Compute the start of the current century as defined by
1264 // get2DigitYearStart.
1265 private void computeCenturyStart()
1267 int year = calendar.get(Calendar.YEAR);
1268 calendar.set(Calendar.YEAR, year - 80);
1269 set2DigitYearStart(calendar.getTime());
1273 * Returns a copy of this instance of
1274 * <code>SimpleDateFormat</code>. The copy contains
1275 * clones of the formatting symbols and the 2-digit
1276 * year century start date.
1278 public Object clone()
1280 SimpleDateFormat clone = (SimpleDateFormat) super.clone();
1281 clone.setDateFormatSymbols((DateFormatSymbols) formatData.clone());
1282 clone.set2DigitYearStart((Date) defaultCenturyStart.clone());
1283 return clone;