24 */
25
26 /*
27 * (C) Copyright Taligent, Inc. 1996 - All Rights Reserved
28 * (C) Copyright IBM Corp. 1996-1998 - All Rights Reserved
29 *
30 * The original version of this source code and documentation is copyrighted
31 * and owned by Taligent, Inc., a wholly-owned subsidiary of IBM. These
32 * materials are provided under terms of a License Agreement between Taligent
33 * and Sun. This technology is protected by multiple US and International
34 * patents. This notice and attribution to Taligent may not be removed.
35 * Taligent is a registered trademark of Taligent, Inc.
36 *
37 */
38
39 package java.text;
40
41 import java.io.IOException;
42 import java.io.InvalidObjectException;
43 import java.io.ObjectInputStream;
44 import java.util.Calendar;
45 import java.util.Date;
46 import java.util.GregorianCalendar;
47 import java.util.Locale;
48 import java.util.Map;
49 import java.util.MissingResourceException;
50 import java.util.ResourceBundle;
51 import java.util.SimpleTimeZone;
52 import java.util.TimeZone;
53 import java.util.concurrent.ConcurrentHashMap;
54 import java.util.concurrent.ConcurrentMap;
55 import sun.util.calendar.CalendarUtils;
56 import sun.util.calendar.ZoneInfoFile;
57 import sun.util.resources.LocaleData;
58
59 import static java.text.DateFormatSymbols.*;
60
61 /**
62 * <code>SimpleDateFormat</code> is a concrete class for formatting and
63 * parsing dates in a locale-sensitive manner. It allows for formatting
64 * (date -> text), parsing (text -> date), and normalization.
65 *
66 * <p>
67 * <code>SimpleDateFormat</code> allows you to start by choosing
68 * any user-defined patterns for date-time formatting. However, you
69 * are encouraged to create a date-time formatter with either
70 * <code>getTimeInstance</code>, <code>getDateInstance</code>, or
71 * <code>getDateTimeInstance</code> in <code>DateFormat</code>. Each
72 * of these class methods can return a date/time formatter initialized
73 * with a default format pattern. You may modify the format pattern
74 * using the <code>applyPattern</code> methods as desired.
75 * For more information on using these methods, see
76 * {@link DateFormat}.
77 *
78 * <h4>Date and Time Patterns</h4>
79 * <p>
80 * Date and time formats are specified by <em>date and time pattern</em>
100 * <th align=left>Date or Time Component
101 * <th align=left>Presentation
102 * <th align=left>Examples
103 * <tr>
104 * <td><code>G</code>
105 * <td>Era designator
106 * <td><a href="#text">Text</a>
107 * <td><code>AD</code>
108 * <tr bgcolor="#eeeeff">
109 * <td><code>y</code>
110 * <td>Year
111 * <td><a href="#year">Year</a>
112 * <td><code>1996</code>; <code>96</code>
113 * <tr>
114 * <td><code>Y</code>
115 * <td>Week year
116 * <td><a href="#year">Year</a>
117 * <td><code>2009</code>; <code>09</code>
118 * <tr bgcolor="#eeeeff">
119 * <td><code>M</code>
120 * <td>Month in year
121 * <td><a href="#month">Month</a>
122 * <td><code>July</code>; <code>Jul</code>; <code>07</code>
123 * <tr>
124 * <td><code>w</code>
125 * <td>Week in year
126 * <td><a href="#number">Number</a>
127 * <td><code>27</code>
128 * <tr bgcolor="#eeeeff">
129 * <td><code>W</code>
130 * <td>Week in month
131 * <td><a href="#number">Number</a>
132 * <td><code>2</code>
133 * <tr>
134 * <td><code>D</code>
135 * <td>Day in year
136 * <td><a href="#number">Number</a>
137 * <td><code>189</code>
138 * <tr bgcolor="#eeeeff">
139 * <td><code>d</code>
140 * <td>Day in month
141 * <td><a href="#number">Number</a>
142 * <td><code>10</code>
143 * <tr>
144 * <td><code>F</code>
145 * <td>Day of week in month
146 * <td><a href="#number">Number</a>
147 * <td><code>2</code>
148 * <tr bgcolor="#eeeeff">
149 * <td><code>E</code>
150 * <td>Day name in week
151 * <td><a href="#text">Text</a>
152 * <td><code>Tuesday</code>; <code>Tue</code>
153 * <tr>
154 * <td><code>u</code>
155 * <td>Day number of week (1 = Monday, ..., 7 = Sunday)
156 * <td><a href="#number">Number</a>
157 * <td><code>1</code>
158 * <tr bgcolor="#eeeeff">
159 * <td><code>a</code>
160 * <td>Am/pm marker
161 * <td><a href="#text">Text</a>
162 * <td><code>PM</code>
163 * <tr>
164 * <td><code>H</code>
165 * <td>Hour in day (0-23)
166 * <td><a href="#number">Number</a>
167 * <td><code>0</code>
168 * <tr bgcolor="#eeeeff">
169 * <td><code>k</code>
170 * <td>Hour in day (1-24)
171 * <td><a href="#number">Number</a>
172 * <td><code>24</code>
173 * <tr>
174 * <td><code>K</code>
175 * <td>Hour in am/pm (0-11)
176 * <td><a href="#number">Number</a>
177 * <td><code>0</code>
178 * <tr bgcolor="#eeeeff">
179 * <td><code>h</code>
180 * <td>Hour in am/pm (1-12)
181 * <td><a href="#number">Number</a>
182 * <td><code>12</code>
183 * <tr>
184 * <td><code>m</code>
185 * <td>Minute in hour
186 * <td><a href="#number">Number</a>
187 * <td><code>30</code>
188 * <tr bgcolor="#eeeeff">
189 * <td><code>s</code>
190 * <td>Second in minute
191 * <td><a href="#number">Number</a>
192 * <td><code>55</code>
193 * <tr>
194 * <td><code>S</code>
195 * <td>Millisecond
196 * <td><a href="#number">Number</a>
197 * <td><code>978</code>
198 * <tr bgcolor="#eeeeff">
199 * <td><code>z</code>
200 * <td>Time zone
201 * <td><a href="#timezone">General time zone</a>
202 * <td><code>Pacific Standard Time</code>; <code>PST</code>; <code>GMT-08:00</code>
203 * <tr>
204 * <td><code>Z</code>
205 * <td>Time zone
206 * <td><a href="#rfc822timezone">RFC 822 time zone</a>
207 * <td><code>-0800</code>
208 * <tr bgcolor="#eeeeff">
209 * <td><code>X</code>
210 * <td>Time zone
211 * <td><a href="#iso8601timezone">ISO 8601 time zone</a>
212 * <td><code>-08</code>; <code>-0800</code>; <code>-08:00</code>
213 * </table>
214 * </blockquote>
215 * Pattern letters are usually repeated, as their number determines the
216 * exact presentation:
217 * <ul>
218 * <li><strong><a name="text">Text:</a></strong>
219 * For formatting, if the number of pattern letters is 4 or more,
220 * the full form is used; otherwise a short or abbreviated form
221 * is used if available.
222 * For parsing, both forms are accepted, independent of the number
223 * of pattern letters.<br><br></li>
224 * <li><strong><a name="number">Number:</a></strong>
225 * For formatting, the number of pattern letters is the minimum
226 * number of digits, and shorter numbers are zero-padded to this amount.
227 * For parsing, the number of pattern letters is ignored unless
228 * it's needed to separate two adjacent fields.<br><br></li>
253 * same pattern, as Jan 2, 3 AD. Likewise, "01/02/-3" is parsed as Jan 2, 4 BC.
254 * </ul>
255 * Otherwise, calendar system specific forms are applied.
256 * For both formatting and parsing, if the number of pattern
257 * letters is 4 or more, a calendar specific {@linkplain
258 * Calendar#LONG long form} is used. Otherwise, a calendar
259 * specific {@linkplain Calendar#SHORT short or abbreviated form}
260 * is used.<br>
261 * <br>
262 * If week year {@code 'Y'} is specified and the {@linkplain
263 * #getCalendar() calendar} doesn't support any <a
264 * href="../util/GregorianCalendar.html#week_year"> week
265 * years</a>, the calendar year ({@code 'y'}) is used instead. The
266 * support of week years can be tested with a call to {@link
267 * DateFormat#getCalendar() getCalendar()}.{@link
268 * java.util.Calendar#isWeekDateSupported()
269 * isWeekDateSupported()}.<br><br></li>
270 * <li><strong><a name="month">Month:</a></strong>
271 * If the number of pattern letters is 3 or more, the month is
272 * interpreted as <a href="#text">text</a>; otherwise,
273 * it is interpreted as a <a href="#number">number</a>.<br><br></li>
274 * <li><strong><a name="timezone">General time zone:</a></strong>
275 * Time zones are interpreted as <a href="#text">text</a> if they have
276 * names. For time zones representing a GMT offset value, the
277 * following syntax is used:
278 * <pre>
279 * <a name="GMTOffsetTimeZone"><i>GMTOffsetTimeZone:</i></a>
280 * <code>GMT</code> <i>Sign</i> <i>Hours</i> <code>:</code> <i>Minutes</i>
281 * <i>Sign:</i> one of
282 * <code>+ -</code>
283 * <i>Hours:</i>
284 * <i>Digit</i>
285 * <i>Digit</i> <i>Digit</i>
286 * <i>Minutes:</i>
287 * <i>Digit</i> <i>Digit</i>
288 * <i>Digit:</i> one of
289 * <code>0 1 2 3 4 5 6 7 8 9</code></pre>
290 * <i>Hours</i> must be between 0 and 23, and <i>Minutes</i> must be between
291 * 00 and 59. The format is locale independent and digits must be taken
292 * from the Basic Latin block of the Unicode standard.
293 * <p>For parsing, <a href="#rfc822timezone">RFC 822 time zones</a> are also
442
443 /**
444 * Saved numberFormat and pattern.
445 * @see SimpleDateFormat#checkNegativeNumberExpression
446 */
447 transient private NumberFormat originalNumberFormat;
448 transient private String originalNumberPattern;
449
450 /**
451 * The minus sign to be used with format and parse.
452 */
453 transient private char minusSign = '-';
454
455 /**
456 * True when a negative sign follows a number.
457 * (True as default in Arabic.)
458 */
459 transient private boolean hasFollowingMinusSign = false;
460
461 /**
462 * The compiled pattern.
463 */
464 transient private char[] compiledPattern;
465
466 /**
467 * Tags for the compiled pattern.
468 */
469 private final static int TAG_QUOTE_ASCII_CHAR = 100;
470 private final static int TAG_QUOTE_CHARS = 101;
471
472 /**
473 * Locale dependent digit zero.
474 * @see #zeroPaddingNumber
475 * @see java.text.DecimalFormatSymbols#getZeroDigit
476 */
477 transient private char zeroDigit;
478
479 /**
480 * The symbols used by this formatter for week names, month names,
481 * etc. May not be null.
485 private DateFormatSymbols formatData;
486
487 /**
488 * We map dates with two-digit years into the century starting at
489 * <code>defaultCenturyStart</code>, which may be any date. May
490 * not be null.
491 * @serial
492 * @since JDK1.1.4
493 */
494 private Date defaultCenturyStart;
495
496 transient private int defaultCenturyStartYear;
497
498 private static final int MILLIS_PER_MINUTE = 60 * 1000;
499
500 // For time zones that have no names, use strings GMT+minutes and
501 // GMT-minutes. For instance, in France the time zone is GMT+60.
502 private static final String GMT = "GMT";
503
504 /**
505 * Cache to hold the DateTimePatterns of a Locale.
506 */
507 private static final ConcurrentMap<Locale, String[]> cachedLocaleData
508 = new ConcurrentHashMap<Locale, String[]>(3);
509
510 /**
511 * Cache NumberFormat instances with Locale key.
512 */
513 private static final ConcurrentMap<Locale, NumberFormat> cachedNumberFormatData
514 = new ConcurrentHashMap<Locale, NumberFormat>(3);
515
516 /**
517 * The Locale used to instantiate this
518 * <code>SimpleDateFormat</code>. The value may be null if this object
519 * has been created by an older <code>SimpleDateFormat</code> and
520 * deserialized.
521 *
522 * @serial
523 * @since 1.6
524 */
525 private Locale locale;
526
527 /**
528 * Indicates whether this <code>SimpleDateFormat</code> should use
529 * the DateFormatSymbols. If true, the format and parse methods
530 * use the DateFormatSymbols values. If false, the format and
531 * parse methods call Calendar.getDisplayName or
532 * Calendar.getDisplayNames.
533 */
534 transient boolean useDateFormatSymbols;
535
536 /**
537 * Constructs a <code>SimpleDateFormat</code> using the default pattern and
538 * date format symbols for the default locale.
539 * <b>Note:</b> This constructor may not support all locales.
540 * For full coverage, use the factory methods in the {@link DateFormat}
541 * class.
542 */
543 public SimpleDateFormat() {
544 this(SHORT, SHORT, Locale.getDefault(Locale.Category.FORMAT));
545 }
546
547 /**
548 * Constructs a <code>SimpleDateFormat</code> using the given pattern and
549 * the default date format symbols for the default locale.
550 * <b>Note:</b> This constructor may not support all locales.
551 * For full coverage, use the factory methods in the {@link DateFormat}
552 * class.
553 *
554 * @param pattern the pattern describing the date and time format
555 * @exception NullPointerException if the given pattern is null
556 * @exception IllegalArgumentException if the given pattern is invalid
557 */
558 public SimpleDateFormat(String pattern)
559 {
560 this(pattern, Locale.getDefault(Locale.Category.FORMAT));
561 }
562
563 /**
564 * Constructs a <code>SimpleDateFormat</code> using the given pattern and
591 *
592 * @param pattern the pattern describing the date and time format
593 * @param formatSymbols the date format symbols to be used for formatting
594 * @exception NullPointerException if the given pattern or formatSymbols is null
595 * @exception IllegalArgumentException if the given pattern is invalid
596 */
597 public SimpleDateFormat(String pattern, DateFormatSymbols formatSymbols)
598 {
599 if (pattern == null || formatSymbols == null) {
600 throw new NullPointerException();
601 }
602
603 this.pattern = pattern;
604 this.formatData = (DateFormatSymbols) formatSymbols.clone();
605 this.locale = Locale.getDefault(Locale.Category.FORMAT);
606 initializeCalendar(this.locale);
607 initialize(this.locale);
608 useDateFormatSymbols = true;
609 }
610
611 /* Package-private, called by DateFormat factory methods */
612 SimpleDateFormat(int timeStyle, int dateStyle, Locale loc) {
613 if (loc == null) {
614 throw new NullPointerException();
615 }
616
617 this.locale = loc;
618 // initialize calendar and related fields
619 initializeCalendar(loc);
620
621 /* try the cache first */
622 String[] dateTimePatterns = cachedLocaleData.get(loc);
623 if (dateTimePatterns == null) { /* cache miss */
624 ResourceBundle r = LocaleData.getDateFormatData(loc);
625 if (!isGregorianCalendar()) {
626 try {
627 dateTimePatterns = r.getStringArray(getCalendarName() + ".DateTimePatterns");
628 } catch (MissingResourceException e) {
629 }
630 }
631 if (dateTimePatterns == null) {
632 dateTimePatterns = r.getStringArray("DateTimePatterns");
633 }
634 /* update cache */
635 cachedLocaleData.putIfAbsent(loc, dateTimePatterns);
636 }
637 formatData = DateFormatSymbols.getInstanceRef(loc);
638 if ((timeStyle >= 0) && (dateStyle >= 0)) {
639 Object[] dateTimeArgs = {dateTimePatterns[timeStyle],
640 dateTimePatterns[dateStyle + 4]};
641 pattern = MessageFormat.format(dateTimePatterns[8], dateTimeArgs);
642 }
643 else if (timeStyle >= 0) {
644 pattern = dateTimePatterns[timeStyle];
645 }
646 else if (dateStyle >= 0) {
647 pattern = dateTimePatterns[dateStyle + 4];
648 }
649 else {
650 throw new IllegalArgumentException("No date or time style specified");
651 }
652
653 initialize(loc);
654 }
655
656 /* Initialize compiledPattern and numberFormat fields */
657 private void initialize(Locale loc) {
658 // Verify and compile the given pattern.
659 compiledPattern = compile(pattern);
660
661 /* try the cache first */
662 numberFormat = cachedNumberFormatData.get(loc);
663 if (numberFormat == null) { /* cache miss */
664 numberFormat = NumberFormat.getIntegerInstance(loc);
665 numberFormat.setGroupingUsed(false);
666
667 /* update cache */
668 cachedNumberFormatData.putIfAbsent(loc, numberFormat);
669 }
670 numberFormat = (NumberFormat) numberFormat.clone();
671
672 initializeDefaultCentury();
673 }
674
675 private void initializeCalendar(Locale loc) {
733 * If Tag is a pattern_char_index, its Length is the number of
734 * pattern characters. For example, if the given pattern is
735 * "yyyy", Tag is 1 and Length is 4, followed by no data.
736 * <p>
737 * If Tag is TAG_QUOTE_CHARS, its Length is the number of char's
738 * following the TagField. For example, if the given pattern is
739 * "'o''clock'", Length is 7 followed by a char sequence of
740 * <code>o&nbs;'&nbs;c&nbs;l&nbs;o&nbs;c&nbs;k</code>.
741 * <p>
742 * TAG_QUOTE_ASCII_CHAR is a special tag and has an ASCII
743 * character in place of Length. For example, if the given pattern
744 * is "'o'", the TaggedData entry is
745 * <code>((TAG_QUOTE_ASCII_CHAR&nbs;<<&nbs;8)&nbs;|&nbs;'o')</code>.
746 *
747 * @exception NullPointerException if the given pattern is null
748 * @exception IllegalArgumentException if the given pattern is invalid
749 */
750 private char[] compile(String pattern) {
751 int length = pattern.length();
752 boolean inQuote = false;
753 StringBuilder compiledPattern = new StringBuilder(length * 2);
754 StringBuilder tmpBuffer = null;
755 int count = 0;
756 int lastTag = -1;
757
758 for (int i = 0; i < length; i++) {
759 char c = pattern.charAt(i);
760
761 if (c == '\'') {
762 // '' is treated as a single quote regardless of being
763 // in a quoted section.
764 if ((i + 1) < length) {
765 c = pattern.charAt(i + 1);
766 if (c == '\'') {
767 i++;
768 if (count != 0) {
769 encode(lastTag, count, compiledPattern);
770 lastTag = -1;
771 count = 0;
772 }
773 if (inQuote) {
774 tmpBuffer.append(c);
775 } else {
776 compiledPattern.append((char)(TAG_QUOTE_ASCII_CHAR << 8 | c));
777 }
778 continue;
779 }
780 }
781 if (!inQuote) {
782 if (count != 0) {
783 encode(lastTag, count, compiledPattern);
784 lastTag = -1;
785 count = 0;
786 }
787 if (tmpBuffer == null) {
788 tmpBuffer = new StringBuilder(length);
789 } else {
790 tmpBuffer.setLength(0);
791 }
792 inQuote = true;
793 } else {
794 int len = tmpBuffer.length();
795 if (len == 1) {
796 char ch = tmpBuffer.charAt(0);
797 if (ch < 128) {
798 compiledPattern.append((char)(TAG_QUOTE_ASCII_CHAR << 8 | ch));
799 } else {
800 compiledPattern.append((char)(TAG_QUOTE_CHARS << 8 | 1));
801 compiledPattern.append(ch);
802 }
803 } else {
804 encode(TAG_QUOTE_CHARS, len, compiledPattern);
805 compiledPattern.append(tmpBuffer);
806 }
807 inQuote = false;
808 }
809 continue;
810 }
811 if (inQuote) {
812 tmpBuffer.append(c);
813 continue;
814 }
815 if (!(c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z')) {
816 if (count != 0) {
817 encode(lastTag, count, compiledPattern);
818 lastTag = -1;
819 count = 0;
820 }
821 if (c < 128) {
822 // In most cases, c would be a delimiter, such as ':'.
823 compiledPattern.append((char)(TAG_QUOTE_ASCII_CHAR << 8 | c));
824 } else {
825 // Take any contiguous non-ASCII alphabet characters and
826 // put them in a single TAG_QUOTE_CHARS.
827 int j;
828 for (j = i + 1; j < length; j++) {
829 char d = pattern.charAt(j);
830 if (d == '\'' || (d >= 'a' && d <= 'z' || d >= 'A' && d <= 'Z')) {
831 break;
832 }
833 }
834 compiledPattern.append((char)(TAG_QUOTE_CHARS << 8 | (j - i)));
835 for (; i < j; i++) {
836 compiledPattern.append(pattern.charAt(i));
837 }
838 i--;
839 }
840 continue;
841 }
842
843 int tag;
844 if ((tag = DateFormatSymbols.patternChars.indexOf(c)) == -1) {
845 throw new IllegalArgumentException("Illegal pattern character " +
846 "'" + c + "'");
847 }
848 if (lastTag == -1 || lastTag == tag) {
849 lastTag = tag;
850 count++;
851 continue;
852 }
853 encode(lastTag, count, compiledPattern);
854 lastTag = tag;
855 count = 1;
856 }
857
858 if (inQuote) {
859 throw new IllegalArgumentException("Unterminated quote");
860 }
861
862 if (count != 0) {
863 encode(lastTag, count, compiledPattern);
864 }
865
866 // Copy the compiled pattern to a char array
867 int len = compiledPattern.length();
868 char[] r = new char[len];
869 compiledPattern.getChars(0, len, r, 0);
870 return r;
871 }
872
873 /**
874 * Encodes the given tag and length and puts encoded char(s) into buffer.
875 */
876 private static final void encode(int tag, int length, StringBuilder buffer) {
877 if (tag == PATTERN_ISO_ZONE && length >= 4) {
878 throw new IllegalArgumentException("invalid ISO 8601 format: length=" + length);
879 }
880 if (length < 255) {
881 buffer.append((char)(tag << 8 | length));
882 } else {
883 buffer.append((char)((tag << 8) | 0xff));
884 buffer.append((char)(length >>> 16));
885 buffer.append((char)(length & 0xffff));
886 }
887 }
888
889 /* Initialize the fields we use to disambiguate ambiguous years. Separate
890 * so we can call it from readObject().
891 */
892 private void initializeDefaultCentury() {
893 calendar.setTimeInMillis(System.currentTimeMillis());
894 calendar.add( Calendar.YEAR, -80 );
895 parseAmbiguousDatesAsAfter(calendar.getTime());
896 }
924 * @return the start of the 100-year period into which two digit years are
925 * parsed
926 * @see #set2DigitYearStart
927 * @since 1.2
928 */
929 public Date get2DigitYearStart() {
930 return (Date) defaultCenturyStart.clone();
931 }
932
933 /**
934 * Formats the given <code>Date</code> into a date/time string and appends
935 * the result to the given <code>StringBuffer</code>.
936 *
937 * @param date the date-time value to be formatted into a date-time string.
938 * @param toAppendTo where the new date-time text is to be appended.
939 * @param pos the formatting position. On input: an alignment field,
940 * if desired. On output: the offsets of the alignment field.
941 * @return the formatted date-time string.
942 * @exception NullPointerException if the given {@code date} is {@code null}.
943 */
944 public StringBuffer format(Date date, StringBuffer toAppendTo,
945 FieldPosition pos)
946 {
947 pos.beginIndex = pos.endIndex = 0;
948 return format(date, toAppendTo, pos.getFieldDelegate());
949 }
950
951 // Called from Format after creating a FieldDelegate
952 private StringBuffer format(Date date, StringBuffer toAppendTo,
953 FieldDelegate delegate) {
954 // Convert input date to time field list
955 calendar.setTime(date);
956
957 boolean useDateFormatSymbols = useDateFormatSymbols();
958
959 for (int i = 0; i < compiledPattern.length; ) {
960 int tag = compiledPattern[i] >>> 8;
961 int count = compiledPattern[i++] & 0xff;
962 if (count == 255) {
963 count = compiledPattern[i++] << 16;
982 return toAppendTo;
983 }
984
985 /**
986 * Formats an Object producing an <code>AttributedCharacterIterator</code>.
987 * You can use the returned <code>AttributedCharacterIterator</code>
988 * to build the resulting String, as well as to determine information
989 * about the resulting String.
990 * <p>
991 * Each attribute key of the AttributedCharacterIterator will be of type
992 * <code>DateFormat.Field</code>, with the corresponding attribute value
993 * being the same as the attribute key.
994 *
995 * @exception NullPointerException if obj is null.
996 * @exception IllegalArgumentException if the Format cannot format the
997 * given object, or if the Format's pattern string is invalid.
998 * @param obj The object to format
999 * @return AttributedCharacterIterator describing the formatted value.
1000 * @since 1.4
1001 */
1002 public AttributedCharacterIterator formatToCharacterIterator(Object obj) {
1003 StringBuffer sb = new StringBuffer();
1004 CharacterIteratorFieldDelegate delegate = new
1005 CharacterIteratorFieldDelegate();
1006
1007 if (obj instanceof Date) {
1008 format((Date)obj, sb, delegate);
1009 }
1010 else if (obj instanceof Number) {
1011 format(new Date(((Number)obj).longValue()), sb, delegate);
1012 }
1013 else if (obj == null) {
1014 throw new NullPointerException(
1015 "formatToCharacterIterator must be passed non-null object");
1016 }
1017 else {
1018 throw new IllegalArgumentException(
1019 "Cannot format given Object as a Date");
1020 }
1021 return delegate.getIterator(sb.toString());
1022 }
1023
1024 // Map index into pattern character string to Calendar field number
1025 private static final int[] PATTERN_INDEX_TO_CALENDAR_FIELD =
1026 {
1027 Calendar.ERA, Calendar.YEAR, Calendar.MONTH, Calendar.DATE,
1028 Calendar.HOUR_OF_DAY, Calendar.HOUR_OF_DAY, Calendar.MINUTE,
1029 Calendar.SECOND, Calendar.MILLISECOND, Calendar.DAY_OF_WEEK,
1030 Calendar.DAY_OF_YEAR, Calendar.DAY_OF_WEEK_IN_MONTH,
1031 Calendar.WEEK_OF_YEAR, Calendar.WEEK_OF_MONTH,
1032 Calendar.AM_PM, Calendar.HOUR, Calendar.HOUR, Calendar.ZONE_OFFSET,
1033 Calendar.ZONE_OFFSET,
1034 // Pseudo Calendar fields
1035 CalendarBuilder.WEEK_YEAR,
1036 CalendarBuilder.ISO_DAY_OF_WEEK,
1037 Calendar.ZONE_OFFSET
1038 };
1039
1040 // Map index into pattern character string to DateFormat field number
1041 private static final int[] PATTERN_INDEX_TO_DATE_FORMAT_FIELD = {
1042 DateFormat.ERA_FIELD, DateFormat.YEAR_FIELD, DateFormat.MONTH_FIELD,
1043 DateFormat.DATE_FIELD, DateFormat.HOUR_OF_DAY1_FIELD,
1044 DateFormat.HOUR_OF_DAY0_FIELD, DateFormat.MINUTE_FIELD,
1045 DateFormat.SECOND_FIELD, DateFormat.MILLISECOND_FIELD,
1046 DateFormat.DAY_OF_WEEK_FIELD, DateFormat.DAY_OF_YEAR_FIELD,
1047 DateFormat.DAY_OF_WEEK_IN_MONTH_FIELD, DateFormat.WEEK_OF_YEAR_FIELD,
1048 DateFormat.WEEK_OF_MONTH_FIELD, DateFormat.AM_PM_FIELD,
1049 DateFormat.HOUR1_FIELD, DateFormat.HOUR0_FIELD,
1050 DateFormat.TIMEZONE_FIELD, DateFormat.TIMEZONE_FIELD,
1051 DateFormat.YEAR_FIELD, DateFormat.DAY_OF_WEEK_FIELD,
1052 DateFormat.TIMEZONE_FIELD
1053 };
1054
1055 // Maps from DecimalFormatSymbols index to Field constant
1056 private static final Field[] PATTERN_INDEX_TO_DATE_FORMAT_FIELD_ID = {
1057 Field.ERA, Field.YEAR, Field.MONTH, Field.DAY_OF_MONTH,
1058 Field.HOUR_OF_DAY1, Field.HOUR_OF_DAY0, Field.MINUTE,
1059 Field.SECOND, Field.MILLISECOND, Field.DAY_OF_WEEK,
1060 Field.DAY_OF_YEAR, Field.DAY_OF_WEEK_IN_MONTH,
1061 Field.WEEK_OF_YEAR, Field.WEEK_OF_MONTH,
1062 Field.AM_PM, Field.HOUR1, Field.HOUR0, Field.TIME_ZONE,
1063 Field.TIME_ZONE,
1064 Field.YEAR, Field.DAY_OF_WEEK,
1065 Field.TIME_ZONE
1066 };
1067
1068 /**
1069 * Private member function that does the real date/time formatting.
1070 */
1071 private void subFormat(int patternCharIndex, int count,
1072 FieldDelegate delegate, StringBuffer buffer,
1073 boolean useDateFormatSymbols)
1074 {
1075 int maxIntCount = Integer.MAX_VALUE;
1076 String current = null;
1077 int beginOffset = buffer.length();
1078
1079 int field = PATTERN_INDEX_TO_CALENDAR_FIELD[patternCharIndex];
1080 int value;
1081 if (field == CalendarBuilder.WEEK_YEAR) {
1082 if (calendar.isWeekDateSupported()) {
1083 value = calendar.getWeekYear();
1084 } else {
1085 // use calendar year 'y' instead
1086 patternCharIndex = PATTERN_YEAR;
1087 field = PATTERN_INDEX_TO_CALENDAR_FIELD[patternCharIndex];
1088 value = calendar.get(field);
1089 }
1090 } else if (field == CalendarBuilder.ISO_DAY_OF_WEEK) {
1091 value = CalendarBuilder.toISODayOfWeek(calendar.get(Calendar.DAY_OF_WEEK));
1092 } else {
1093 value = calendar.get(field);
1094 }
1095
1096 int style = (count >= 4) ? Calendar.LONG : Calendar.SHORT;
1097 if (!useDateFormatSymbols && field != CalendarBuilder.ISO_DAY_OF_WEEK) {
1098 current = calendar.getDisplayName(field, style, locale);
1099 }
1100
1101 // Note: zeroPaddingNumber() assumes that maxDigits is either
1102 // 2 or maxIntCount. If we make any changes to this,
1103 // zeroPaddingNumber() must be fixed.
1104
1105 switch (patternCharIndex) {
1106 case PATTERN_ERA: // 'G'
1107 if (useDateFormatSymbols) {
1108 String[] eras = formatData.getEras();
1109 if (value < eras.length)
1110 current = eras[value];
1111 }
1112 if (current == null)
1113 current = "";
1114 break;
1115
1116 case PATTERN_WEEK_YEAR: // 'Y'
1117 case PATTERN_YEAR: // 'y'
1118 if (calendar instanceof GregorianCalendar) {
1119 if (count != 2)
1120 zeroPaddingNumber(value, count, maxIntCount, buffer);
1121 else // count == 2
1122 zeroPaddingNumber(value, 2, 2, buffer); // clip 1996 to 96
1123 } else {
1124 if (current == null) {
1125 zeroPaddingNumber(value, style == Calendar.LONG ? 1 : count,
1126 maxIntCount, buffer);
1127 }
1128 }
1129 break;
1130
1131 case PATTERN_MONTH: // 'M'
1132 if (useDateFormatSymbols) {
1133 String[] months;
1134 if (count >= 4) {
1135 months = formatData.getMonths();
1136 current = months[value];
1137 } else if (count == 3) {
1138 months = formatData.getShortMonths();
1139 current = months[value];
1140 }
1141 } else {
1142 if (count < 3) {
1143 current = null;
1144 }
1145 }
1146 if (current == null) {
1147 zeroPaddingNumber(value+1, count, maxIntCount, buffer);
1148 }
1149 break;
1150
1151 case PATTERN_HOUR_OF_DAY1: // 'k' 1-based. eg, 23:59 + 1 hour =>> 24:59
1152 if (current == null) {
1153 if (value == 0)
1154 zeroPaddingNumber(calendar.getMaximum(Calendar.HOUR_OF_DAY)+1,
1155 count, maxIntCount, buffer);
1156 else
1157 zeroPaddingNumber(value, count, maxIntCount, buffer);
1158 }
1159 break;
1160
1161 case PATTERN_DAY_OF_WEEK: // 'E'
1162 if (useDateFormatSymbols) {
1163 String[] weekdays;
1164 if (count >= 4) {
1165 weekdays = formatData.getWeekdays();
1166 current = weekdays[value];
1167 } else { // count < 4, use abbreviated form if exists
1168 weekdays = formatData.getShortWeekdays();
1169 current = weekdays[value];
1170 }
1171 }
1172 break;
1173
1174 case PATTERN_AM_PM: // 'a'
1175 if (useDateFormatSymbols) {
1176 String[] ampm = formatData.getAmPmStrings();
1177 current = ampm[value];
1178 }
1179 break;
1180
1181 case PATTERN_HOUR1: // 'h' 1-based. eg, 11PM + 1 hour =>> 12 AM
1182 if (current == null) {
1183 if (value == 0)
1184 zeroPaddingNumber(calendar.getLeastMaximum(Calendar.HOUR)+1,
1185 count, maxIntCount, buffer);
1186 else
1187 zeroPaddingNumber(value, count, maxIntCount, buffer);
1188 }
1189 break;
1190
1191 case PATTERN_ZONE_NAME: // 'z'
1192 if (current == null) {
1193 if (formatData.locale == null || formatData.isZoneStringsSet) {
1194 int zoneIndex =
1195 formatData.getZoneIndex(calendar.getTimeZone().getID());
1196 if (zoneIndex == -1) {
1197 value = calendar.get(Calendar.ZONE_OFFSET) +
1198 calendar.get(Calendar.DST_OFFSET);
1199 buffer.append(ZoneInfoFile.toCustomID(value));
1200 } else {
1201 int index = (calendar.get(Calendar.DST_OFFSET) == 0) ? 1: 3;
1202 if (count < 4) {
1203 // Use the short name
1204 index++;
1205 }
1206 String[][] zoneStrings = formatData.getZoneStringsWrapper();
1207 buffer.append(zoneStrings[zoneIndex][index]);
1208 }
1272 // case PATTERN_ISO_DAY_OF_WEEK: // 'u' pseudo field, Monday = 1, ..., Sunday = 7
1273 if (current == null) {
1274 zeroPaddingNumber(value, count, maxIntCount, buffer);
1275 }
1276 break;
1277 } // switch (patternCharIndex)
1278
1279 if (current != null) {
1280 buffer.append(current);
1281 }
1282
1283 int fieldID = PATTERN_INDEX_TO_DATE_FORMAT_FIELD[patternCharIndex];
1284 Field f = PATTERN_INDEX_TO_DATE_FORMAT_FIELD_ID[patternCharIndex];
1285
1286 delegate.formatted(fieldID, f, f, beginOffset, buffer.length(), buffer);
1287 }
1288
1289 /**
1290 * Formats a number with the specified minimum and maximum number of digits.
1291 */
1292 private final void zeroPaddingNumber(int value, int minDigits, int maxDigits, StringBuffer buffer)
1293 {
1294 // Optimization for 1, 2 and 4 digit numbers. This should
1295 // cover most cases of formatting date/time related items.
1296 // Note: This optimization code assumes that maxDigits is
1297 // either 2 or Integer.MAX_VALUE (maxIntCount in format()).
1298 try {
1299 if (zeroDigit == 0) {
1300 zeroDigit = ((DecimalFormat)numberFormat).getDecimalFormatSymbols().getZeroDigit();
1301 }
1302 if (value >= 0) {
1303 if (value < 100 && minDigits >= 1 && minDigits <= 2) {
1304 if (value < 10) {
1305 if (minDigits == 2) {
1306 buffer.append(zeroDigit);
1307 }
1308 buffer.append((char)(zeroDigit + value));
1309 } else {
1310 buffer.append((char)(zeroDigit + value / 10));
1311 buffer.append((char)(zeroDigit + value % 10));
1312 }
1354 * calendar} to produce a {@code Date}. All of the {@code
1355 * calendar}'s date-time fields are {@linkplain Calendar#clear()
1356 * cleared} before parsing, and the {@code calendar}'s default
1357 * values of the date-time fields are used for any missing
1358 * date-time information. For example, the year value of the
1359 * parsed {@code Date} is 1970 with {@link GregorianCalendar} if
1360 * no year value is given from the parsing operation. The {@code
1361 * TimeZone} value may be overwritten, depending on the given
1362 * pattern and the time zone value in {@code text}. Any {@code
1363 * TimeZone} value that has previously been set by a call to
1364 * {@link #setTimeZone(java.util.TimeZone) setTimeZone} may need
1365 * to be restored for further operations.
1366 *
1367 * @param text A <code>String</code>, part of which should be parsed.
1368 * @param pos A <code>ParsePosition</code> object with index and error
1369 * index information as described above.
1370 * @return A <code>Date</code> parsed from the string. In case of
1371 * error, returns null.
1372 * @exception NullPointerException if <code>text</code> or <code>pos</code> is null.
1373 */
1374 public Date parse(String text, ParsePosition pos)
1375 {
1376 checkNegativeNumberExpression();
1377
1378 int start = pos.index;
1379 int oldStart = start;
1380 int textLength = text.length();
1381
1382 boolean[] ambiguousYear = {false};
1383
1384 CalendarBuilder calb = new CalendarBuilder();
1385
1386 for (int i = 0; i < compiledPattern.length; ) {
1387 int tag = compiledPattern[i] >>> 8;
1388 int count = compiledPattern[i++] & 0xff;
1389 if (count == 255) {
1390 count = compiledPattern[i++] << 16;
1391 count |= compiledPattern[i++];
1392 }
1393
1487 return null;
1488 }
1489
1490 return parsedDate;
1491 }
1492
1493 /**
1494 * Private code-size reduction function used by subParse.
1495 * @param text the time text being parsed.
1496 * @param start where to start parsing.
1497 * @param field the date field being parsed.
1498 * @param data the string array to parsed.
1499 * @return the new start position if matching succeeded; a negative number
1500 * indicating matching failure, otherwise.
1501 */
1502 private int matchString(String text, int start, int field, String[] data, CalendarBuilder calb)
1503 {
1504 int i = 0;
1505 int count = data.length;
1506
1507 if (field == Calendar.DAY_OF_WEEK) i = 1;
1508
1509 // There may be multiple strings in the data[] array which begin with
1510 // the same prefix (e.g., Cerven and Cervenec (June and July) in Czech).
1511 // We keep track of the longest match, and return that. Note that this
1512 // unfortunately requires us to test all array elements.
1513 int bestMatchLength = 0, bestMatch = -1;
1514 for (; i<count; ++i)
1515 {
1516 int length = data[i].length();
1517 // Always compare if we have no match yet; otherwise only compare
1518 // against potentially better matches (longer strings).
1519 if (length > bestMatchLength &&
1520 text.regionMatches(true, start, data[i], 0, length))
1521 {
1522 bestMatch = i;
1523 bestMatchLength = length;
1524 }
1525 }
1526 if (bestMatch >= 0)
1527 {
1750 ParsePosition origPos,
1751 boolean useFollowingMinusSignAsDelimiter, CalendarBuilder calb) {
1752 Number number = null;
1753 int value = 0;
1754 ParsePosition pos = new ParsePosition(0);
1755 pos.index = start;
1756 if (patternCharIndex == PATTERN_WEEK_YEAR && !calendar.isWeekDateSupported()) {
1757 // use calendar year 'y' instead
1758 patternCharIndex = PATTERN_YEAR;
1759 }
1760 int field = PATTERN_INDEX_TO_CALENDAR_FIELD[patternCharIndex];
1761
1762 // If there are any spaces here, skip over them. If we hit the end
1763 // of the string, then fail.
1764 for (;;) {
1765 if (pos.index >= text.length()) {
1766 origPos.errorIndex = start;
1767 return -1;
1768 }
1769 char c = text.charAt(pos.index);
1770 if (c != ' ' && c != '\t') break;
1771 ++pos.index;
1772 }
1773
1774 parsing:
1775 {
1776 // We handle a few special cases here where we need to parse
1777 // a number value. We handle further, more generic cases below. We need
1778 // to handle some of them here because some fields require extra processing on
1779 // the parsed value.
1780 if (patternCharIndex == PATTERN_HOUR_OF_DAY1 ||
1781 patternCharIndex == PATTERN_HOUR1 ||
1782 (patternCharIndex == PATTERN_MONTH && count <= 2) ||
1783 patternCharIndex == PATTERN_YEAR ||
1784 patternCharIndex == PATTERN_WEEK_YEAR) {
1785 // It would be good to unify this with the obeyCount logic below,
1786 // but that's going to be difficult.
1787 if (obeyCount) {
1788 if ((start+count) > text.length()) {
1789 break parsing;
1790 }
1895 return index;
1896 }
1897 } else {
1898 Map<String, Integer> map = calendar.getDisplayNames(field,
1899 Calendar.ALL_STYLES,
1900 locale);
1901 if ((index = matchString(text, start, field, map, calb)) > 0) {
1902 return index;
1903 }
1904 }
1905 break parsing;
1906
1907 case PATTERN_HOUR_OF_DAY1: // 'k' 1-based. eg, 23:59 + 1 hour =>> 24:59
1908 if (!isLenient()) {
1909 // Validate the hour value in non-lenient
1910 if (value < 1 || value > 24) {
1911 break parsing;
1912 }
1913 }
1914 // [We computed 'value' above.]
1915 if (value == calendar.getMaximum(Calendar.HOUR_OF_DAY)+1)
1916 value = 0;
1917 calb.set(Calendar.HOUR_OF_DAY, value);
1918 return pos.index;
1919
1920 case PATTERN_DAY_OF_WEEK: // 'E'
1921 {
1922 if (useDateFormatSymbols) {
1923 // Want to be able to parse both short and long forms.
1924 // Try count == 4 (DDDD) first:
1925 int newStart = 0;
1926 if ((newStart=matchString(text, start, Calendar.DAY_OF_WEEK,
1927 formatData.getWeekdays(), calb)) > 0) {
1928 return newStart;
1929 }
1930 // DDDD failed, now try DDD
1931 if ((index = matchString(text, start, Calendar.DAY_OF_WEEK,
1932 formatData.getShortWeekdays(), calb)) > 0) {
1933 return index;
1934 }
1935 } else {
1936 int[] styles = { Calendar.LONG, Calendar.SHORT };
1949 if ((index = matchString(text, start, Calendar.AM_PM,
1950 formatData.getAmPmStrings(), calb)) > 0) {
1951 return index;
1952 }
1953 } else {
1954 Map<String,Integer> map = calendar.getDisplayNames(field, Calendar.ALL_STYLES, locale);
1955 if ((index = matchString(text, start, field, map, calb)) > 0) {
1956 return index;
1957 }
1958 }
1959 break parsing;
1960
1961 case PATTERN_HOUR1: // 'h' 1-based. eg, 11PM + 1 hour =>> 12 AM
1962 if (!isLenient()) {
1963 // Validate the hour value in non-lenient
1964 if (value < 1 || value > 12) {
1965 break parsing;
1966 }
1967 }
1968 // [We computed 'value' above.]
1969 if (value == calendar.getLeastMaximum(Calendar.HOUR)+1)
1970 value = 0;
1971 calb.set(Calendar.HOUR, value);
1972 return pos.index;
1973
1974 case PATTERN_ZONE_NAME: // 'z'
1975 case PATTERN_ZONE_VALUE: // 'Z'
1976 {
1977 int sign = 0;
1978 try {
1979 char c = text.charAt(pos.index);
1980 if (c == '+') {
1981 sign = 1;
1982 } else if (c == '-') {
1983 sign = -1;
1984 }
1985 if (sign == 0) {
1986 // Try parsing a custom time zone "GMT+hh:mm" or "GMT".
1987 if ((c == 'G' || c == 'g')
1988 && (text.length() - start) >= GMT.length()
1989 && text.regionMatches(true, start, GMT, 0, GMT.length())) {
1990 pos.index = start + GMT.length();
2094 (((pos.index < text.length()) &&
2095 (text.charAt(pos.index) != minusSign)) ||
2096 ((pos.index == text.length()) &&
2097 (text.charAt(pos.index-1) == minusSign)))) {
2098 value = -value;
2099 pos.index--;
2100 }
2101
2102 calb.set(field, value);
2103 return pos.index;
2104 }
2105 break parsing;
2106 }
2107 }
2108
2109 // Parsing failed.
2110 origPos.errorIndex = pos.index;
2111 return -1;
2112 }
2113
2114 private final String getCalendarName() {
2115 return calendar.getClass().getName();
2116 }
2117
2118 private boolean useDateFormatSymbols() {
2119 if (useDateFormatSymbols) {
2120 return true;
2121 }
2122 return isGregorianCalendar() || locale == null;
2123 }
2124
2125 private boolean isGregorianCalendar() {
2126 return "java.util.GregorianCalendar".equals(getCalendarName());
2127 }
2128
2129 /**
2130 * Translates a pattern, mapping each character in the from string to the
2131 * corresponding character in the to string.
2132 *
2133 * @exception IllegalArgumentException if the given pattern is invalid
2134 */
2135 private String translatePattern(String pattern, String from, String to) {
2136 StringBuilder result = new StringBuilder();
2137 boolean inQuote = false;
2138 for (int i = 0; i < pattern.length(); ++i) {
2139 char c = pattern.charAt(i);
2140 if (inQuote) {
2141 if (c == '\'')
2142 inQuote = false;
2143 }
2144 else {
2145 if (c == '\'')
2146 inQuote = true;
2147 else if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) {
2148 int ci = from.indexOf(c);
2149 if (ci >= 0) {
2150 // patternChars is longer than localPatternChars due
2151 // to serialization compatibility. The pattern letters
2152 // unsupported by localPatternChars pass through.
2153 if (ci < to.length()) {
2154 c = to.charAt(ci);
2155 }
2156 } else {
2157 throw new IllegalArgumentException("Illegal pattern " +
2158 " character '" +
2159 c + "'");
2160 }
2161 }
2162 }
2163 result.append(c);
2164 }
2165 if (inQuote)
2166 throw new IllegalArgumentException("Unfinished quote in pattern");
2167 return result.toString();
2168 }
2169
2170 /**
2171 * Returns a pattern string describing this date format.
2172 *
2173 * @return a pattern string describing this date format.
2174 */
2175 public String toPattern() {
2176 return pattern;
2177 }
2178
2179 /**
2180 * Returns a localized pattern string describing this date format.
2181 *
2182 * @return a localized pattern string describing this date format.
2183 */
2184 public String toLocalizedPattern() {
2185 return translatePattern(pattern,
2186 DateFormatSymbols.patternChars,
2187 formatData.getLocalPatternChars());
2188 }
2189
2190 /**
2191 * Applies the given pattern string to this date format.
2192 *
2193 * @param pattern the new date and time pattern for this date format
2194 * @exception NullPointerException if the given pattern is null
2195 * @exception IllegalArgumentException if the given pattern is invalid
2196 */
2197 public void applyPattern(String pattern)
2198 {
2199 compiledPattern = compile(pattern);
2200 this.pattern = pattern;
2201 }
2202
2203 /**
2204 * Applies the given localized pattern string to this date format.
2205 *
2206 * @param pattern a String to be mapped to the new date and time format
2207 * pattern for this format
2208 * @exception NullPointerException if the given pattern is null
2209 * @exception IllegalArgumentException if the given pattern is invalid
2210 */
2211 public void applyLocalizedPattern(String pattern) {
2212 String p = translatePattern(pattern,
2213 formatData.getLocalPatternChars(),
2214 DateFormatSymbols.patternChars);
2215 compiledPattern = compile(p);
2216 this.pattern = p;
2217 }
2218
2229
2230 /**
2231 * Sets the date and time format symbols of this date format.
2232 *
2233 * @param newFormatSymbols the new date and time format symbols
2234 * @exception NullPointerException if the given newFormatSymbols is null
2235 * @see #getDateFormatSymbols
2236 */
2237 public void setDateFormatSymbols(DateFormatSymbols newFormatSymbols)
2238 {
2239 this.formatData = (DateFormatSymbols)newFormatSymbols.clone();
2240 useDateFormatSymbols = true;
2241 }
2242
2243 /**
2244 * Creates a copy of this <code>SimpleDateFormat</code>. This also
2245 * clones the format's date format symbols.
2246 *
2247 * @return a clone of this <code>SimpleDateFormat</code>
2248 */
2249 public Object clone() {
2250 SimpleDateFormat other = (SimpleDateFormat) super.clone();
2251 other.formatData = (DateFormatSymbols) formatData.clone();
2252 return other;
2253 }
2254
2255 /**
2256 * Returns the hash code value for this <code>SimpleDateFormat</code> object.
2257 *
2258 * @return the hash code value for this <code>SimpleDateFormat</code> object.
2259 */
2260 public int hashCode()
2261 {
2262 return pattern.hashCode();
2263 // just enough fields for a reasonable distribution
2264 }
2265
2266 /**
2267 * Compares the given object with this <code>SimpleDateFormat</code> for
2268 * equality.
2269 *
2270 * @return true if the given object is equal to this
2271 * <code>SimpleDateFormat</code>
2272 */
2273 public boolean equals(Object obj)
2274 {
2275 if (!super.equals(obj)) return false; // super does class check
2276 SimpleDateFormat that = (SimpleDateFormat) obj;
2277 return (pattern.equals(that.pattern)
2278 && formatData.equals(that.formatData));
2279 }
2280
2281 /**
2282 * After reading an object from the input stream, the format
2283 * pattern in the object is verified.
2284 * <p>
2285 * @exception InvalidObjectException if the pattern is invalid
2286 */
2287 private void readObject(ObjectInputStream stream)
2288 throws IOException, ClassNotFoundException {
2289 stream.defaultReadObject();
2290
2291 try {
2292 compiledPattern = compile(pattern);
2293 } catch (Exception e) {
2294 throw new InvalidObjectException("invalid pattern");
2295 }
|
24 */
25
26 /*
27 * (C) Copyright Taligent, Inc. 1996 - All Rights Reserved
28 * (C) Copyright IBM Corp. 1996-1998 - All Rights Reserved
29 *
30 * The original version of this source code and documentation is copyrighted
31 * and owned by Taligent, Inc., a wholly-owned subsidiary of IBM. These
32 * materials are provided under terms of a License Agreement between Taligent
33 * and Sun. This technology is protected by multiple US and International
34 * patents. This notice and attribution to Taligent may not be removed.
35 * Taligent is a registered trademark of Taligent, Inc.
36 *
37 */
38
39 package java.text;
40
41 import java.io.IOException;
42 import java.io.InvalidObjectException;
43 import java.io.ObjectInputStream;
44 import static java.text.DateFormatSymbols.*;
45 import java.util.Calendar;
46 import java.util.Date;
47 import java.util.GregorianCalendar;
48 import java.util.Locale;
49 import java.util.Map;
50 import java.util.SimpleTimeZone;
51 import java.util.TimeZone;
52 import java.util.concurrent.ConcurrentHashMap;
53 import java.util.concurrent.ConcurrentMap;
54 import sun.util.locale.provider.LocaleProviderAdapter;
55 import sun.util.calendar.CalendarUtils;
56 import sun.util.calendar.ZoneInfoFile;
57
58 /**
59 * <code>SimpleDateFormat</code> is a concrete class for formatting and
60 * parsing dates in a locale-sensitive manner. It allows for formatting
61 * (date -> text), parsing (text -> date), and normalization.
62 *
63 * <p>
64 * <code>SimpleDateFormat</code> allows you to start by choosing
65 * any user-defined patterns for date-time formatting. However, you
66 * are encouraged to create a date-time formatter with either
67 * <code>getTimeInstance</code>, <code>getDateInstance</code>, or
68 * <code>getDateTimeInstance</code> in <code>DateFormat</code>. Each
69 * of these class methods can return a date/time formatter initialized
70 * with a default format pattern. You may modify the format pattern
71 * using the <code>applyPattern</code> methods as desired.
72 * For more information on using these methods, see
73 * {@link DateFormat}.
74 *
75 * <h4>Date and Time Patterns</h4>
76 * <p>
77 * Date and time formats are specified by <em>date and time pattern</em>
97 * <th align=left>Date or Time Component
98 * <th align=left>Presentation
99 * <th align=left>Examples
100 * <tr>
101 * <td><code>G</code>
102 * <td>Era designator
103 * <td><a href="#text">Text</a>
104 * <td><code>AD</code>
105 * <tr bgcolor="#eeeeff">
106 * <td><code>y</code>
107 * <td>Year
108 * <td><a href="#year">Year</a>
109 * <td><code>1996</code>; <code>96</code>
110 * <tr>
111 * <td><code>Y</code>
112 * <td>Week year
113 * <td><a href="#year">Year</a>
114 * <td><code>2009</code>; <code>09</code>
115 * <tr bgcolor="#eeeeff">
116 * <td><code>M</code>
117 * <td>Month in year (context sensitive)
118 * <td><a href="#month">Month</a>
119 * <td><code>July</code>; <code>Jul</code>; <code>07</code>
120 * <tr>
121 * <td><code>L</code>
122 * <td>Month in year (standalone form)
123 * <td><a href="#month">Month</a>
124 * <td><code>July</code>; <code>Jul</code>; <code>07</code>
125 * <tr bgcolor="#eeeeff">
126 * <td><code>w</code>
127 * <td>Week in year
128 * <td><a href="#number">Number</a>
129 * <td><code>27</code>
130 * <tr>
131 * <td><code>W</code>
132 * <td>Week in month
133 * <td><a href="#number">Number</a>
134 * <td><code>2</code>
135 * <tr bgcolor="#eeeeff">
136 * <td><code>D</code>
137 * <td>Day in year
138 * <td><a href="#number">Number</a>
139 * <td><code>189</code>
140 * <tr>
141 * <td><code>d</code>
142 * <td>Day in month
143 * <td><a href="#number">Number</a>
144 * <td><code>10</code>
145 * <tr bgcolor="#eeeeff">
146 * <td><code>F</code>
147 * <td>Day of week in month
148 * <td><a href="#number">Number</a>
149 * <td><code>2</code>
150 * <tr>
151 * <td><code>E</code>
152 * <td>Day name in week
153 * <td><a href="#text">Text</a>
154 * <td><code>Tuesday</code>; <code>Tue</code>
155 * <tr bgcolor="#eeeeff">
156 * <td><code>u</code>
157 * <td>Day number of week (1 = Monday, ..., 7 = Sunday)
158 * <td><a href="#number">Number</a>
159 * <td><code>1</code>
160 * <tr>
161 * <td><code>a</code>
162 * <td>Am/pm marker
163 * <td><a href="#text">Text</a>
164 * <td><code>PM</code>
165 * <tr bgcolor="#eeeeff">
166 * <td><code>H</code>
167 * <td>Hour in day (0-23)
168 * <td><a href="#number">Number</a>
169 * <td><code>0</code>
170 * <tr>
171 * <td><code>k</code>
172 * <td>Hour in day (1-24)
173 * <td><a href="#number">Number</a>
174 * <td><code>24</code>
175 * <tr bgcolor="#eeeeff">
176 * <td><code>K</code>
177 * <td>Hour in am/pm (0-11)
178 * <td><a href="#number">Number</a>
179 * <td><code>0</code>
180 * <tr>
181 * <td><code>h</code>
182 * <td>Hour in am/pm (1-12)
183 * <td><a href="#number">Number</a>
184 * <td><code>12</code>
185 * <tr bgcolor="#eeeeff">
186 * <td><code>m</code>
187 * <td>Minute in hour
188 * <td><a href="#number">Number</a>
189 * <td><code>30</code>
190 * <tr>
191 * <td><code>s</code>
192 * <td>Second in minute
193 * <td><a href="#number">Number</a>
194 * <td><code>55</code>
195 * <tr bgcolor="#eeeeff">
196 * <td><code>S</code>
197 * <td>Millisecond
198 * <td><a href="#number">Number</a>
199 * <td><code>978</code>
200 * <tr>
201 * <td><code>z</code>
202 * <td>Time zone
203 * <td><a href="#timezone">General time zone</a>
204 * <td><code>Pacific Standard Time</code>; <code>PST</code>; <code>GMT-08:00</code>
205 * <tr bgcolor="#eeeeff">
206 * <td><code>Z</code>
207 * <td>Time zone
208 * <td><a href="#rfc822timezone">RFC 822 time zone</a>
209 * <td><code>-0800</code>
210 * <tr>
211 * <td><code>X</code>
212 * <td>Time zone
213 * <td><a href="#iso8601timezone">ISO 8601 time zone</a>
214 * <td><code>-08</code>; <code>-0800</code>; <code>-08:00</code>
215 * </table>
216 * </blockquote>
217 * Pattern letters are usually repeated, as their number determines the
218 * exact presentation:
219 * <ul>
220 * <li><strong><a name="text">Text:</a></strong>
221 * For formatting, if the number of pattern letters is 4 or more,
222 * the full form is used; otherwise a short or abbreviated form
223 * is used if available.
224 * For parsing, both forms are accepted, independent of the number
225 * of pattern letters.<br><br></li>
226 * <li><strong><a name="number">Number:</a></strong>
227 * For formatting, the number of pattern letters is the minimum
228 * number of digits, and shorter numbers are zero-padded to this amount.
229 * For parsing, the number of pattern letters is ignored unless
230 * it's needed to separate two adjacent fields.<br><br></li>
255 * same pattern, as Jan 2, 3 AD. Likewise, "01/02/-3" is parsed as Jan 2, 4 BC.
256 * </ul>
257 * Otherwise, calendar system specific forms are applied.
258 * For both formatting and parsing, if the number of pattern
259 * letters is 4 or more, a calendar specific {@linkplain
260 * Calendar#LONG long form} is used. Otherwise, a calendar
261 * specific {@linkplain Calendar#SHORT short or abbreviated form}
262 * is used.<br>
263 * <br>
264 * If week year {@code 'Y'} is specified and the {@linkplain
265 * #getCalendar() calendar} doesn't support any <a
266 * href="../util/GregorianCalendar.html#week_year"> week
267 * years</a>, the calendar year ({@code 'y'}) is used instead. The
268 * support of week years can be tested with a call to {@link
269 * DateFormat#getCalendar() getCalendar()}.{@link
270 * java.util.Calendar#isWeekDateSupported()
271 * isWeekDateSupported()}.<br><br></li>
272 * <li><strong><a name="month">Month:</a></strong>
273 * If the number of pattern letters is 3 or more, the month is
274 * interpreted as <a href="#text">text</a>; otherwise,
275 * it is interpreted as a <a href="#number">number</a>.<br>
276 * <ul>
277 * <li>Letter <em>M</em> produces context-sensitive month names, such as the
278 * embedded form of names. If a {@code DateFormatSymbols} has been set
279 * explicitly with constructor {@link #SimpleDateFormat(String,
280 * DateFormatSymbols)} or method {@link
281 * #setDateFormatSymbols(DateFormatSymbols)}, the month names given by
282 * the {@code DateFormatSymbols} are used.</li>
283 * <li>Letter <em>L</em> produces the standalone form of month names.</li>
284 * </ul>
285 * <br></li>
286 * <li><strong><a name="timezone">General time zone:</a></strong>
287 * Time zones are interpreted as <a href="#text">text</a> if they have
288 * names. For time zones representing a GMT offset value, the
289 * following syntax is used:
290 * <pre>
291 * <a name="GMTOffsetTimeZone"><i>GMTOffsetTimeZone:</i></a>
292 * <code>GMT</code> <i>Sign</i> <i>Hours</i> <code>:</code> <i>Minutes</i>
293 * <i>Sign:</i> one of
294 * <code>+ -</code>
295 * <i>Hours:</i>
296 * <i>Digit</i>
297 * <i>Digit</i> <i>Digit</i>
298 * <i>Minutes:</i>
299 * <i>Digit</i> <i>Digit</i>
300 * <i>Digit:</i> one of
301 * <code>0 1 2 3 4 5 6 7 8 9</code></pre>
302 * <i>Hours</i> must be between 0 and 23, and <i>Minutes</i> must be between
303 * 00 and 59. The format is locale independent and digits must be taken
304 * from the Basic Latin block of the Unicode standard.
305 * <p>For parsing, <a href="#rfc822timezone">RFC 822 time zones</a> are also
454
455 /**
456 * Saved numberFormat and pattern.
457 * @see SimpleDateFormat#checkNegativeNumberExpression
458 */
459 transient private NumberFormat originalNumberFormat;
460 transient private String originalNumberPattern;
461
462 /**
463 * The minus sign to be used with format and parse.
464 */
465 transient private char minusSign = '-';
466
467 /**
468 * True when a negative sign follows a number.
469 * (True as default in Arabic.)
470 */
471 transient private boolean hasFollowingMinusSign = false;
472
473 /**
474 * True if standalone form needs to be used.
475 */
476 transient private boolean forceStandaloneForm = false;
477
478 /**
479 * The compiled pattern.
480 */
481 transient private char[] compiledPattern;
482
483 /**
484 * Tags for the compiled pattern.
485 */
486 private final static int TAG_QUOTE_ASCII_CHAR = 100;
487 private final static int TAG_QUOTE_CHARS = 101;
488
489 /**
490 * Locale dependent digit zero.
491 * @see #zeroPaddingNumber
492 * @see java.text.DecimalFormatSymbols#getZeroDigit
493 */
494 transient private char zeroDigit;
495
496 /**
497 * The symbols used by this formatter for week names, month names,
498 * etc. May not be null.
502 private DateFormatSymbols formatData;
503
504 /**
505 * We map dates with two-digit years into the century starting at
506 * <code>defaultCenturyStart</code>, which may be any date. May
507 * not be null.
508 * @serial
509 * @since JDK1.1.4
510 */
511 private Date defaultCenturyStart;
512
513 transient private int defaultCenturyStartYear;
514
515 private static final int MILLIS_PER_MINUTE = 60 * 1000;
516
517 // For time zones that have no names, use strings GMT+minutes and
518 // GMT-minutes. For instance, in France the time zone is GMT+60.
519 private static final String GMT = "GMT";
520
521 /**
522 * Cache NumberFormat instances with Locale key.
523 */
524 private static final ConcurrentMap<Locale, NumberFormat> cachedNumberFormatData
525 = new ConcurrentHashMap<>(3);
526
527 /**
528 * The Locale used to instantiate this
529 * <code>SimpleDateFormat</code>. The value may be null if this object
530 * has been created by an older <code>SimpleDateFormat</code> and
531 * deserialized.
532 *
533 * @serial
534 * @since 1.6
535 */
536 private Locale locale;
537
538 /**
539 * Indicates whether this <code>SimpleDateFormat</code> should use
540 * the DateFormatSymbols. If true, the format and parse methods
541 * use the DateFormatSymbols values. If false, the format and
542 * parse methods call Calendar.getDisplayName or
543 * Calendar.getDisplayNames.
544 */
545 transient boolean useDateFormatSymbols;
546
547 /**
548 * Constructs a <code>SimpleDateFormat</code> using the default pattern and
549 * date format symbols for the default locale.
550 * <b>Note:</b> This constructor may not support all locales.
551 * For full coverage, use the factory methods in the {@link DateFormat}
552 * class.
553 */
554 public SimpleDateFormat() {
555 this("", Locale.getDefault(Locale.Category.FORMAT));
556 applyPatternImpl(LocaleProviderAdapter.getResourceBundleBased().getLocaleResources(locale)
557 .getDateTimePattern(SHORT, SHORT, calendar));
558 }
559
560 /**
561 * Constructs a <code>SimpleDateFormat</code> using the given pattern and
562 * the default date format symbols for the default locale.
563 * <b>Note:</b> This constructor may not support all locales.
564 * For full coverage, use the factory methods in the {@link DateFormat}
565 * class.
566 *
567 * @param pattern the pattern describing the date and time format
568 * @exception NullPointerException if the given pattern is null
569 * @exception IllegalArgumentException if the given pattern is invalid
570 */
571 public SimpleDateFormat(String pattern)
572 {
573 this(pattern, Locale.getDefault(Locale.Category.FORMAT));
574 }
575
576 /**
577 * Constructs a <code>SimpleDateFormat</code> using the given pattern and
604 *
605 * @param pattern the pattern describing the date and time format
606 * @param formatSymbols the date format symbols to be used for formatting
607 * @exception NullPointerException if the given pattern or formatSymbols is null
608 * @exception IllegalArgumentException if the given pattern is invalid
609 */
610 public SimpleDateFormat(String pattern, DateFormatSymbols formatSymbols)
611 {
612 if (pattern == null || formatSymbols == null) {
613 throw new NullPointerException();
614 }
615
616 this.pattern = pattern;
617 this.formatData = (DateFormatSymbols) formatSymbols.clone();
618 this.locale = Locale.getDefault(Locale.Category.FORMAT);
619 initializeCalendar(this.locale);
620 initialize(this.locale);
621 useDateFormatSymbols = true;
622 }
623
624 /* Initialize compiledPattern and numberFormat fields */
625 private void initialize(Locale loc) {
626 // Verify and compile the given pattern.
627 compiledPattern = compile(pattern);
628
629 /* try the cache first */
630 numberFormat = cachedNumberFormatData.get(loc);
631 if (numberFormat == null) { /* cache miss */
632 numberFormat = NumberFormat.getIntegerInstance(loc);
633 numberFormat.setGroupingUsed(false);
634
635 /* update cache */
636 cachedNumberFormatData.putIfAbsent(loc, numberFormat);
637 }
638 numberFormat = (NumberFormat) numberFormat.clone();
639
640 initializeDefaultCentury();
641 }
642
643 private void initializeCalendar(Locale loc) {
701 * If Tag is a pattern_char_index, its Length is the number of
702 * pattern characters. For example, if the given pattern is
703 * "yyyy", Tag is 1 and Length is 4, followed by no data.
704 * <p>
705 * If Tag is TAG_QUOTE_CHARS, its Length is the number of char's
706 * following the TagField. For example, if the given pattern is
707 * "'o''clock'", Length is 7 followed by a char sequence of
708 * <code>o&nbs;'&nbs;c&nbs;l&nbs;o&nbs;c&nbs;k</code>.
709 * <p>
710 * TAG_QUOTE_ASCII_CHAR is a special tag and has an ASCII
711 * character in place of Length. For example, if the given pattern
712 * is "'o'", the TaggedData entry is
713 * <code>((TAG_QUOTE_ASCII_CHAR&nbs;<<&nbs;8)&nbs;|&nbs;'o')</code>.
714 *
715 * @exception NullPointerException if the given pattern is null
716 * @exception IllegalArgumentException if the given pattern is invalid
717 */
718 private char[] compile(String pattern) {
719 int length = pattern.length();
720 boolean inQuote = false;
721 StringBuilder compiledCode = new StringBuilder(length * 2);
722 StringBuilder tmpBuffer = null;
723 int count = 0, tagcount = 0;
724 int lastTag = -1, prevTag = -1;
725
726 for (int i = 0; i < length; i++) {
727 char c = pattern.charAt(i);
728
729 if (c == '\'') {
730 // '' is treated as a single quote regardless of being
731 // in a quoted section.
732 if ((i + 1) < length) {
733 c = pattern.charAt(i + 1);
734 if (c == '\'') {
735 i++;
736 if (count != 0) {
737 encode(lastTag, count, compiledCode);
738 tagcount++;
739 prevTag = lastTag;
740 lastTag = -1;
741 count = 0;
742 }
743 if (inQuote) {
744 tmpBuffer.append(c);
745 } else {
746 compiledCode.append((char)(TAG_QUOTE_ASCII_CHAR << 8 | c));
747 }
748 continue;
749 }
750 }
751 if (!inQuote) {
752 if (count != 0) {
753 encode(lastTag, count, compiledCode);
754 tagcount++;
755 prevTag = lastTag;
756 lastTag = -1;
757 count = 0;
758 }
759 if (tmpBuffer == null) {
760 tmpBuffer = new StringBuilder(length);
761 } else {
762 tmpBuffer.setLength(0);
763 }
764 inQuote = true;
765 } else {
766 int len = tmpBuffer.length();
767 if (len == 1) {
768 char ch = tmpBuffer.charAt(0);
769 if (ch < 128) {
770 compiledCode.append((char)(TAG_QUOTE_ASCII_CHAR << 8 | ch));
771 } else {
772 compiledCode.append((char)(TAG_QUOTE_CHARS << 8 | 1));
773 compiledCode.append(ch);
774 }
775 } else {
776 encode(TAG_QUOTE_CHARS, len, compiledCode);
777 compiledCode.append(tmpBuffer);
778 }
779 inQuote = false;
780 }
781 continue;
782 }
783 if (inQuote) {
784 tmpBuffer.append(c);
785 continue;
786 }
787 if (!(c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z')) {
788 if (count != 0) {
789 encode(lastTag, count, compiledCode);
790 tagcount++;
791 prevTag = lastTag;
792 lastTag = -1;
793 count = 0;
794 }
795 if (c < 128) {
796 // In most cases, c would be a delimiter, such as ':'.
797 compiledCode.append((char)(TAG_QUOTE_ASCII_CHAR << 8 | c));
798 } else {
799 // Take any contiguous non-ASCII alphabet characters and
800 // put them in a single TAG_QUOTE_CHARS.
801 int j;
802 for (j = i + 1; j < length; j++) {
803 char d = pattern.charAt(j);
804 if (d == '\'' || (d >= 'a' && d <= 'z' || d >= 'A' && d <= 'Z')) {
805 break;
806 }
807 }
808 compiledCode.append((char)(TAG_QUOTE_CHARS << 8 | (j - i)));
809 for (; i < j; i++) {
810 compiledCode.append(pattern.charAt(i));
811 }
812 i--;
813 }
814 continue;
815 }
816
817 int tag;
818 if ((tag = DateFormatSymbols.patternChars.indexOf(c)) == -1) {
819 throw new IllegalArgumentException("Illegal pattern character " +
820 "'" + c + "'");
821 }
822 if (lastTag == -1 || lastTag == tag) {
823 lastTag = tag;
824 count++;
825 continue;
826 }
827 encode(lastTag, count, compiledCode);
828 tagcount++;
829 prevTag = lastTag;
830 lastTag = tag;
831 count = 1;
832 }
833
834 if (inQuote) {
835 throw new IllegalArgumentException("Unterminated quote");
836 }
837
838 if (count != 0) {
839 encode(lastTag, count, compiledCode);
840 tagcount++;
841 prevTag = lastTag;
842 }
843
844 forceStandaloneForm = (tagcount == 1 && prevTag == PATTERN_MONTH);
845
846 // Copy the compiled pattern to a char array
847 int len = compiledCode.length();
848 char[] r = new char[len];
849 compiledCode.getChars(0, len, r, 0);
850 return r;
851 }
852
853 /**
854 * Encodes the given tag and length and puts encoded char(s) into buffer.
855 */
856 private static void encode(int tag, int length, StringBuilder buffer) {
857 if (tag == PATTERN_ISO_ZONE && length >= 4) {
858 throw new IllegalArgumentException("invalid ISO 8601 format: length=" + length);
859 }
860 if (length < 255) {
861 buffer.append((char)(tag << 8 | length));
862 } else {
863 buffer.append((char)((tag << 8) | 0xff));
864 buffer.append((char)(length >>> 16));
865 buffer.append((char)(length & 0xffff));
866 }
867 }
868
869 /* Initialize the fields we use to disambiguate ambiguous years. Separate
870 * so we can call it from readObject().
871 */
872 private void initializeDefaultCentury() {
873 calendar.setTimeInMillis(System.currentTimeMillis());
874 calendar.add( Calendar.YEAR, -80 );
875 parseAmbiguousDatesAsAfter(calendar.getTime());
876 }
904 * @return the start of the 100-year period into which two digit years are
905 * parsed
906 * @see #set2DigitYearStart
907 * @since 1.2
908 */
909 public Date get2DigitYearStart() {
910 return (Date) defaultCenturyStart.clone();
911 }
912
913 /**
914 * Formats the given <code>Date</code> into a date/time string and appends
915 * the result to the given <code>StringBuffer</code>.
916 *
917 * @param date the date-time value to be formatted into a date-time string.
918 * @param toAppendTo where the new date-time text is to be appended.
919 * @param pos the formatting position. On input: an alignment field,
920 * if desired. On output: the offsets of the alignment field.
921 * @return the formatted date-time string.
922 * @exception NullPointerException if the given {@code date} is {@code null}.
923 */
924 @Override
925 public StringBuffer format(Date date, StringBuffer toAppendTo,
926 FieldPosition pos)
927 {
928 pos.beginIndex = pos.endIndex = 0;
929 return format(date, toAppendTo, pos.getFieldDelegate());
930 }
931
932 // Called from Format after creating a FieldDelegate
933 private StringBuffer format(Date date, StringBuffer toAppendTo,
934 FieldDelegate delegate) {
935 // Convert input date to time field list
936 calendar.setTime(date);
937
938 boolean useDateFormatSymbols = useDateFormatSymbols();
939
940 for (int i = 0; i < compiledPattern.length; ) {
941 int tag = compiledPattern[i] >>> 8;
942 int count = compiledPattern[i++] & 0xff;
943 if (count == 255) {
944 count = compiledPattern[i++] << 16;
963 return toAppendTo;
964 }
965
966 /**
967 * Formats an Object producing an <code>AttributedCharacterIterator</code>.
968 * You can use the returned <code>AttributedCharacterIterator</code>
969 * to build the resulting String, as well as to determine information
970 * about the resulting String.
971 * <p>
972 * Each attribute key of the AttributedCharacterIterator will be of type
973 * <code>DateFormat.Field</code>, with the corresponding attribute value
974 * being the same as the attribute key.
975 *
976 * @exception NullPointerException if obj is null.
977 * @exception IllegalArgumentException if the Format cannot format the
978 * given object, or if the Format's pattern string is invalid.
979 * @param obj The object to format
980 * @return AttributedCharacterIterator describing the formatted value.
981 * @since 1.4
982 */
983 @Override
984 public AttributedCharacterIterator formatToCharacterIterator(Object obj) {
985 StringBuffer sb = new StringBuffer();
986 CharacterIteratorFieldDelegate delegate = new
987 CharacterIteratorFieldDelegate();
988
989 if (obj instanceof Date) {
990 format((Date)obj, sb, delegate);
991 }
992 else if (obj instanceof Number) {
993 format(new Date(((Number)obj).longValue()), sb, delegate);
994 }
995 else if (obj == null) {
996 throw new NullPointerException(
997 "formatToCharacterIterator must be passed non-null object");
998 }
999 else {
1000 throw new IllegalArgumentException(
1001 "Cannot format given Object as a Date");
1002 }
1003 return delegate.getIterator(sb.toString());
1004 }
1005
1006 // Map index into pattern character string to Calendar field number
1007 private static final int[] PATTERN_INDEX_TO_CALENDAR_FIELD = {
1008 Calendar.ERA,
1009 Calendar.YEAR,
1010 Calendar.MONTH,
1011 Calendar.DATE,
1012 Calendar.HOUR_OF_DAY,
1013 Calendar.HOUR_OF_DAY,
1014 Calendar.MINUTE,
1015 Calendar.SECOND,
1016 Calendar.MILLISECOND,
1017 Calendar.DAY_OF_WEEK,
1018 Calendar.DAY_OF_YEAR,
1019 Calendar.DAY_OF_WEEK_IN_MONTH,
1020 Calendar.WEEK_OF_YEAR,
1021 Calendar.WEEK_OF_MONTH,
1022 Calendar.AM_PM,
1023 Calendar.HOUR,
1024 Calendar.HOUR,
1025 Calendar.ZONE_OFFSET,
1026 Calendar.ZONE_OFFSET,
1027 CalendarBuilder.WEEK_YEAR, // Pseudo Calendar field
1028 CalendarBuilder.ISO_DAY_OF_WEEK, // Pseudo Calendar field
1029 Calendar.ZONE_OFFSET,
1030 Calendar.MONTH
1031 };
1032
1033 // Map index into pattern character string to DateFormat field number
1034 private static final int[] PATTERN_INDEX_TO_DATE_FORMAT_FIELD = {
1035 DateFormat.ERA_FIELD,
1036 DateFormat.YEAR_FIELD,
1037 DateFormat.MONTH_FIELD,
1038 DateFormat.DATE_FIELD,
1039 DateFormat.HOUR_OF_DAY1_FIELD,
1040 DateFormat.HOUR_OF_DAY0_FIELD,
1041 DateFormat.MINUTE_FIELD,
1042 DateFormat.SECOND_FIELD,
1043 DateFormat.MILLISECOND_FIELD,
1044 DateFormat.DAY_OF_WEEK_FIELD,
1045 DateFormat.DAY_OF_YEAR_FIELD,
1046 DateFormat.DAY_OF_WEEK_IN_MONTH_FIELD,
1047 DateFormat.WEEK_OF_YEAR_FIELD,
1048 DateFormat.WEEK_OF_MONTH_FIELD,
1049 DateFormat.AM_PM_FIELD,
1050 DateFormat.HOUR1_FIELD,
1051 DateFormat.HOUR0_FIELD,
1052 DateFormat.TIMEZONE_FIELD,
1053 DateFormat.TIMEZONE_FIELD,
1054 DateFormat.YEAR_FIELD,
1055 DateFormat.DAY_OF_WEEK_FIELD,
1056 DateFormat.TIMEZONE_FIELD,
1057 DateFormat.MONTH_FIELD
1058 };
1059
1060 // Maps from DecimalFormatSymbols index to Field constant
1061 private static final Field[] PATTERN_INDEX_TO_DATE_FORMAT_FIELD_ID = {
1062 Field.ERA,
1063 Field.YEAR,
1064 Field.MONTH,
1065 Field.DAY_OF_MONTH,
1066 Field.HOUR_OF_DAY1,
1067 Field.HOUR_OF_DAY0,
1068 Field.MINUTE,
1069 Field.SECOND,
1070 Field.MILLISECOND,
1071 Field.DAY_OF_WEEK,
1072 Field.DAY_OF_YEAR,
1073 Field.DAY_OF_WEEK_IN_MONTH,
1074 Field.WEEK_OF_YEAR,
1075 Field.WEEK_OF_MONTH,
1076 Field.AM_PM,
1077 Field.HOUR1,
1078 Field.HOUR0,
1079 Field.TIME_ZONE,
1080 Field.TIME_ZONE,
1081 Field.YEAR,
1082 Field.DAY_OF_WEEK,
1083 Field.TIME_ZONE,
1084 Field.MONTH
1085 };
1086
1087 /**
1088 * Private member function that does the real date/time formatting.
1089 */
1090 private void subFormat(int patternCharIndex, int count,
1091 FieldDelegate delegate, StringBuffer buffer,
1092 boolean useDateFormatSymbols)
1093 {
1094 int maxIntCount = Integer.MAX_VALUE;
1095 String current = null;
1096 int beginOffset = buffer.length();
1097
1098 int field = PATTERN_INDEX_TO_CALENDAR_FIELD[patternCharIndex];
1099 int value;
1100 if (field == CalendarBuilder.WEEK_YEAR) {
1101 if (calendar.isWeekDateSupported()) {
1102 value = calendar.getWeekYear();
1103 } else {
1104 // use calendar year 'y' instead
1105 patternCharIndex = PATTERN_YEAR;
1106 field = PATTERN_INDEX_TO_CALENDAR_FIELD[patternCharIndex];
1107 value = calendar.get(field);
1108 }
1109 } else if (field == CalendarBuilder.ISO_DAY_OF_WEEK) {
1110 value = CalendarBuilder.toISODayOfWeek(calendar.get(Calendar.DAY_OF_WEEK));
1111 } else {
1112 value = calendar.get(field);
1113 }
1114
1115 int style = (count >= 4) ? Calendar.LONG : Calendar.SHORT;
1116 if (!useDateFormatSymbols && field < Calendar.ZONE_OFFSET
1117 && patternCharIndex != PATTERN_MONTH_STANDALONE) {
1118 current = calendar.getDisplayName(field, style, locale);
1119 }
1120
1121 // Note: zeroPaddingNumber() assumes that maxDigits is either
1122 // 2 or maxIntCount. If we make any changes to this,
1123 // zeroPaddingNumber() must be fixed.
1124
1125 switch (patternCharIndex) {
1126 case PATTERN_ERA: // 'G'
1127 if (useDateFormatSymbols) {
1128 String[] eras = formatData.getEras();
1129 if (value < eras.length) {
1130 current = eras[value];
1131 }
1132 }
1133 if (current == null) {
1134 current = "";
1135 }
1136 break;
1137
1138 case PATTERN_WEEK_YEAR: // 'Y'
1139 case PATTERN_YEAR: // 'y'
1140 if (calendar instanceof GregorianCalendar) {
1141 if (count != 2) {
1142 zeroPaddingNumber(value, count, maxIntCount, buffer);
1143 } else {
1144 zeroPaddingNumber(value, 2, 2, buffer);
1145 } // clip 1996 to 96
1146 } else {
1147 if (current == null) {
1148 zeroPaddingNumber(value, style == Calendar.LONG ? 1 : count,
1149 maxIntCount, buffer);
1150 }
1151 }
1152 break;
1153
1154 case PATTERN_MONTH: // 'M' (context seinsive)
1155 if (useDateFormatSymbols) {
1156 String[] months;
1157 if (count >= 4) {
1158 months = formatData.getMonths();
1159 current = months[value];
1160 } else if (count == 3) {
1161 months = formatData.getShortMonths();
1162 current = months[value];
1163 }
1164 } else {
1165 if (count < 3) {
1166 current = null;
1167 } else if (forceStandaloneForm) {
1168 current = calendar.getDisplayName(field, style | 0x8000, locale);
1169 if (current == null) {
1170 current = calendar.getDisplayName(field, style, locale);
1171 }
1172 }
1173 }
1174 if (current == null) {
1175 zeroPaddingNumber(value+1, count, maxIntCount, buffer);
1176 }
1177 break;
1178
1179 case PATTERN_MONTH_STANDALONE: // 'L'
1180 assert current == null;
1181 if (locale == null) {
1182 String[] months;
1183 if (count >= 4) {
1184 months = formatData.getMonths();
1185 current = months[value];
1186 } else if (count == 3) {
1187 months = formatData.getShortMonths();
1188 current = months[value];
1189 }
1190 } else {
1191 if (count >= 3) {
1192 current = calendar.getDisplayName(field, style | 0x8000, locale);
1193 }
1194 }
1195 if (current == null) {
1196 zeroPaddingNumber(value+1, count, maxIntCount, buffer);
1197 }
1198 break;
1199
1200 case PATTERN_HOUR_OF_DAY1: // 'k' 1-based. eg, 23:59 + 1 hour =>> 24:59
1201 if (current == null) {
1202 if (value == 0) {
1203 zeroPaddingNumber(calendar.getMaximum(Calendar.HOUR_OF_DAY) + 1,
1204 count, maxIntCount, buffer);
1205 } else {
1206 zeroPaddingNumber(value, count, maxIntCount, buffer);
1207 }
1208 }
1209 break;
1210
1211 case PATTERN_DAY_OF_WEEK: // 'E'
1212 if (useDateFormatSymbols) {
1213 String[] weekdays;
1214 if (count >= 4) {
1215 weekdays = formatData.getWeekdays();
1216 current = weekdays[value];
1217 } else { // count < 4, use abbreviated form if exists
1218 weekdays = formatData.getShortWeekdays();
1219 current = weekdays[value];
1220 }
1221 }
1222 break;
1223
1224 case PATTERN_AM_PM: // 'a'
1225 if (useDateFormatSymbols) {
1226 String[] ampm = formatData.getAmPmStrings();
1227 current = ampm[value];
1228 }
1229 break;
1230
1231 case PATTERN_HOUR1: // 'h' 1-based. eg, 11PM + 1 hour =>> 12 AM
1232 if (current == null) {
1233 if (value == 0) {
1234 zeroPaddingNumber(calendar.getLeastMaximum(Calendar.HOUR) + 1,
1235 count, maxIntCount, buffer);
1236 } else {
1237 zeroPaddingNumber(value, count, maxIntCount, buffer);
1238 }
1239 }
1240 break;
1241
1242 case PATTERN_ZONE_NAME: // 'z'
1243 if (current == null) {
1244 if (formatData.locale == null || formatData.isZoneStringsSet) {
1245 int zoneIndex =
1246 formatData.getZoneIndex(calendar.getTimeZone().getID());
1247 if (zoneIndex == -1) {
1248 value = calendar.get(Calendar.ZONE_OFFSET) +
1249 calendar.get(Calendar.DST_OFFSET);
1250 buffer.append(ZoneInfoFile.toCustomID(value));
1251 } else {
1252 int index = (calendar.get(Calendar.DST_OFFSET) == 0) ? 1: 3;
1253 if (count < 4) {
1254 // Use the short name
1255 index++;
1256 }
1257 String[][] zoneStrings = formatData.getZoneStringsWrapper();
1258 buffer.append(zoneStrings[zoneIndex][index]);
1259 }
1323 // case PATTERN_ISO_DAY_OF_WEEK: // 'u' pseudo field, Monday = 1, ..., Sunday = 7
1324 if (current == null) {
1325 zeroPaddingNumber(value, count, maxIntCount, buffer);
1326 }
1327 break;
1328 } // switch (patternCharIndex)
1329
1330 if (current != null) {
1331 buffer.append(current);
1332 }
1333
1334 int fieldID = PATTERN_INDEX_TO_DATE_FORMAT_FIELD[patternCharIndex];
1335 Field f = PATTERN_INDEX_TO_DATE_FORMAT_FIELD_ID[patternCharIndex];
1336
1337 delegate.formatted(fieldID, f, f, beginOffset, buffer.length(), buffer);
1338 }
1339
1340 /**
1341 * Formats a number with the specified minimum and maximum number of digits.
1342 */
1343 private void zeroPaddingNumber(int value, int minDigits, int maxDigits, StringBuffer buffer)
1344 {
1345 // Optimization for 1, 2 and 4 digit numbers. This should
1346 // cover most cases of formatting date/time related items.
1347 // Note: This optimization code assumes that maxDigits is
1348 // either 2 or Integer.MAX_VALUE (maxIntCount in format()).
1349 try {
1350 if (zeroDigit == 0) {
1351 zeroDigit = ((DecimalFormat)numberFormat).getDecimalFormatSymbols().getZeroDigit();
1352 }
1353 if (value >= 0) {
1354 if (value < 100 && minDigits >= 1 && minDigits <= 2) {
1355 if (value < 10) {
1356 if (minDigits == 2) {
1357 buffer.append(zeroDigit);
1358 }
1359 buffer.append((char)(zeroDigit + value));
1360 } else {
1361 buffer.append((char)(zeroDigit + value / 10));
1362 buffer.append((char)(zeroDigit + value % 10));
1363 }
1405 * calendar} to produce a {@code Date}. All of the {@code
1406 * calendar}'s date-time fields are {@linkplain Calendar#clear()
1407 * cleared} before parsing, and the {@code calendar}'s default
1408 * values of the date-time fields are used for any missing
1409 * date-time information. For example, the year value of the
1410 * parsed {@code Date} is 1970 with {@link GregorianCalendar} if
1411 * no year value is given from the parsing operation. The {@code
1412 * TimeZone} value may be overwritten, depending on the given
1413 * pattern and the time zone value in {@code text}. Any {@code
1414 * TimeZone} value that has previously been set by a call to
1415 * {@link #setTimeZone(java.util.TimeZone) setTimeZone} may need
1416 * to be restored for further operations.
1417 *
1418 * @param text A <code>String</code>, part of which should be parsed.
1419 * @param pos A <code>ParsePosition</code> object with index and error
1420 * index information as described above.
1421 * @return A <code>Date</code> parsed from the string. In case of
1422 * error, returns null.
1423 * @exception NullPointerException if <code>text</code> or <code>pos</code> is null.
1424 */
1425 @Override
1426 public Date parse(String text, ParsePosition pos)
1427 {
1428 checkNegativeNumberExpression();
1429
1430 int start = pos.index;
1431 int oldStart = start;
1432 int textLength = text.length();
1433
1434 boolean[] ambiguousYear = {false};
1435
1436 CalendarBuilder calb = new CalendarBuilder();
1437
1438 for (int i = 0; i < compiledPattern.length; ) {
1439 int tag = compiledPattern[i] >>> 8;
1440 int count = compiledPattern[i++] & 0xff;
1441 if (count == 255) {
1442 count = compiledPattern[i++] << 16;
1443 count |= compiledPattern[i++];
1444 }
1445
1539 return null;
1540 }
1541
1542 return parsedDate;
1543 }
1544
1545 /**
1546 * Private code-size reduction function used by subParse.
1547 * @param text the time text being parsed.
1548 * @param start where to start parsing.
1549 * @param field the date field being parsed.
1550 * @param data the string array to parsed.
1551 * @return the new start position if matching succeeded; a negative number
1552 * indicating matching failure, otherwise.
1553 */
1554 private int matchString(String text, int start, int field, String[] data, CalendarBuilder calb)
1555 {
1556 int i = 0;
1557 int count = data.length;
1558
1559 if (field == Calendar.DAY_OF_WEEK) {
1560 i = 1;
1561 }
1562
1563 // There may be multiple strings in the data[] array which begin with
1564 // the same prefix (e.g., Cerven and Cervenec (June and July) in Czech).
1565 // We keep track of the longest match, and return that. Note that this
1566 // unfortunately requires us to test all array elements.
1567 int bestMatchLength = 0, bestMatch = -1;
1568 for (; i<count; ++i)
1569 {
1570 int length = data[i].length();
1571 // Always compare if we have no match yet; otherwise only compare
1572 // against potentially better matches (longer strings).
1573 if (length > bestMatchLength &&
1574 text.regionMatches(true, start, data[i], 0, length))
1575 {
1576 bestMatch = i;
1577 bestMatchLength = length;
1578 }
1579 }
1580 if (bestMatch >= 0)
1581 {
1804 ParsePosition origPos,
1805 boolean useFollowingMinusSignAsDelimiter, CalendarBuilder calb) {
1806 Number number = null;
1807 int value = 0;
1808 ParsePosition pos = new ParsePosition(0);
1809 pos.index = start;
1810 if (patternCharIndex == PATTERN_WEEK_YEAR && !calendar.isWeekDateSupported()) {
1811 // use calendar year 'y' instead
1812 patternCharIndex = PATTERN_YEAR;
1813 }
1814 int field = PATTERN_INDEX_TO_CALENDAR_FIELD[patternCharIndex];
1815
1816 // If there are any spaces here, skip over them. If we hit the end
1817 // of the string, then fail.
1818 for (;;) {
1819 if (pos.index >= text.length()) {
1820 origPos.errorIndex = start;
1821 return -1;
1822 }
1823 char c = text.charAt(pos.index);
1824 if (c != ' ' && c != '\t') {
1825 break;
1826 }
1827 ++pos.index;
1828 }
1829
1830 parsing:
1831 {
1832 // We handle a few special cases here where we need to parse
1833 // a number value. We handle further, more generic cases below. We need
1834 // to handle some of them here because some fields require extra processing on
1835 // the parsed value.
1836 if (patternCharIndex == PATTERN_HOUR_OF_DAY1 ||
1837 patternCharIndex == PATTERN_HOUR1 ||
1838 (patternCharIndex == PATTERN_MONTH && count <= 2) ||
1839 patternCharIndex == PATTERN_YEAR ||
1840 patternCharIndex == PATTERN_WEEK_YEAR) {
1841 // It would be good to unify this with the obeyCount logic below,
1842 // but that's going to be difficult.
1843 if (obeyCount) {
1844 if ((start+count) > text.length()) {
1845 break parsing;
1846 }
1951 return index;
1952 }
1953 } else {
1954 Map<String, Integer> map = calendar.getDisplayNames(field,
1955 Calendar.ALL_STYLES,
1956 locale);
1957 if ((index = matchString(text, start, field, map, calb)) > 0) {
1958 return index;
1959 }
1960 }
1961 break parsing;
1962
1963 case PATTERN_HOUR_OF_DAY1: // 'k' 1-based. eg, 23:59 + 1 hour =>> 24:59
1964 if (!isLenient()) {
1965 // Validate the hour value in non-lenient
1966 if (value < 1 || value > 24) {
1967 break parsing;
1968 }
1969 }
1970 // [We computed 'value' above.]
1971 if (value == calendar.getMaximum(Calendar.HOUR_OF_DAY) + 1) {
1972 value = 0;
1973 }
1974 calb.set(Calendar.HOUR_OF_DAY, value);
1975 return pos.index;
1976
1977 case PATTERN_DAY_OF_WEEK: // 'E'
1978 {
1979 if (useDateFormatSymbols) {
1980 // Want to be able to parse both short and long forms.
1981 // Try count == 4 (DDDD) first:
1982 int newStart = 0;
1983 if ((newStart=matchString(text, start, Calendar.DAY_OF_WEEK,
1984 formatData.getWeekdays(), calb)) > 0) {
1985 return newStart;
1986 }
1987 // DDDD failed, now try DDD
1988 if ((index = matchString(text, start, Calendar.DAY_OF_WEEK,
1989 formatData.getShortWeekdays(), calb)) > 0) {
1990 return index;
1991 }
1992 } else {
1993 int[] styles = { Calendar.LONG, Calendar.SHORT };
2006 if ((index = matchString(text, start, Calendar.AM_PM,
2007 formatData.getAmPmStrings(), calb)) > 0) {
2008 return index;
2009 }
2010 } else {
2011 Map<String,Integer> map = calendar.getDisplayNames(field, Calendar.ALL_STYLES, locale);
2012 if ((index = matchString(text, start, field, map, calb)) > 0) {
2013 return index;
2014 }
2015 }
2016 break parsing;
2017
2018 case PATTERN_HOUR1: // 'h' 1-based. eg, 11PM + 1 hour =>> 12 AM
2019 if (!isLenient()) {
2020 // Validate the hour value in non-lenient
2021 if (value < 1 || value > 12) {
2022 break parsing;
2023 }
2024 }
2025 // [We computed 'value' above.]
2026 if (value == calendar.getLeastMaximum(Calendar.HOUR) + 1) {
2027 value = 0;
2028 }
2029 calb.set(Calendar.HOUR, value);
2030 return pos.index;
2031
2032 case PATTERN_ZONE_NAME: // 'z'
2033 case PATTERN_ZONE_VALUE: // 'Z'
2034 {
2035 int sign = 0;
2036 try {
2037 char c = text.charAt(pos.index);
2038 if (c == '+') {
2039 sign = 1;
2040 } else if (c == '-') {
2041 sign = -1;
2042 }
2043 if (sign == 0) {
2044 // Try parsing a custom time zone "GMT+hh:mm" or "GMT".
2045 if ((c == 'G' || c == 'g')
2046 && (text.length() - start) >= GMT.length()
2047 && text.regionMatches(true, start, GMT, 0, GMT.length())) {
2048 pos.index = start + GMT.length();
2152 (((pos.index < text.length()) &&
2153 (text.charAt(pos.index) != minusSign)) ||
2154 ((pos.index == text.length()) &&
2155 (text.charAt(pos.index-1) == minusSign)))) {
2156 value = -value;
2157 pos.index--;
2158 }
2159
2160 calb.set(field, value);
2161 return pos.index;
2162 }
2163 break parsing;
2164 }
2165 }
2166
2167 // Parsing failed.
2168 origPos.errorIndex = pos.index;
2169 return -1;
2170 }
2171
2172 /**
2173 * Returns true if the DateFormatSymbols has been set explicitly or locale
2174 * is null.
2175 */
2176 private boolean useDateFormatSymbols() {
2177 return useDateFormatSymbols || locale == null;
2178 }
2179
2180 /**
2181 * Translates a pattern, mapping each character in the from string to the
2182 * corresponding character in the to string.
2183 *
2184 * @exception IllegalArgumentException if the given pattern is invalid
2185 */
2186 private String translatePattern(String pattern, String from, String to) {
2187 StringBuilder result = new StringBuilder();
2188 boolean inQuote = false;
2189 for (int i = 0; i < pattern.length(); ++i) {
2190 char c = pattern.charAt(i);
2191 if (inQuote) {
2192 if (c == '\'') {
2193 inQuote = false;
2194 }
2195 }
2196 else {
2197 if (c == '\'') {
2198 inQuote = true;
2199 } else if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) {
2200 int ci = from.indexOf(c);
2201 if (ci >= 0) {
2202 // patternChars is longer than localPatternChars due
2203 // to serialization compatibility. The pattern letters
2204 // unsupported by localPatternChars pass through.
2205 if (ci < to.length()) {
2206 c = to.charAt(ci);
2207 }
2208 } else {
2209 throw new IllegalArgumentException("Illegal pattern " +
2210 " character '" +
2211 c + "'");
2212 }
2213 }
2214 }
2215 result.append(c);
2216 }
2217 if (inQuote) {
2218 throw new IllegalArgumentException("Unfinished quote in pattern");
2219 }
2220 return result.toString();
2221 }
2222
2223 /**
2224 * Returns a pattern string describing this date format.
2225 *
2226 * @return a pattern string describing this date format.
2227 */
2228 public String toPattern() {
2229 return pattern;
2230 }
2231
2232 /**
2233 * Returns a localized pattern string describing this date format.
2234 *
2235 * @return a localized pattern string describing this date format.
2236 */
2237 public String toLocalizedPattern() {
2238 return translatePattern(pattern,
2239 DateFormatSymbols.patternChars,
2240 formatData.getLocalPatternChars());
2241 }
2242
2243 /**
2244 * Applies the given pattern string to this date format.
2245 *
2246 * @param pattern the new date and time pattern for this date format
2247 * @exception NullPointerException if the given pattern is null
2248 * @exception IllegalArgumentException if the given pattern is invalid
2249 */
2250 public void applyPattern(String pattern)
2251 {
2252 applyPatternImpl(pattern);
2253 }
2254
2255 private void applyPatternImpl(String pattern) {
2256 compiledPattern = compile(pattern);
2257 this.pattern = pattern;
2258 }
2259
2260 /**
2261 * Applies the given localized pattern string to this date format.
2262 *
2263 * @param pattern a String to be mapped to the new date and time format
2264 * pattern for this format
2265 * @exception NullPointerException if the given pattern is null
2266 * @exception IllegalArgumentException if the given pattern is invalid
2267 */
2268 public void applyLocalizedPattern(String pattern) {
2269 String p = translatePattern(pattern,
2270 formatData.getLocalPatternChars(),
2271 DateFormatSymbols.patternChars);
2272 compiledPattern = compile(p);
2273 this.pattern = p;
2274 }
2275
2286
2287 /**
2288 * Sets the date and time format symbols of this date format.
2289 *
2290 * @param newFormatSymbols the new date and time format symbols
2291 * @exception NullPointerException if the given newFormatSymbols is null
2292 * @see #getDateFormatSymbols
2293 */
2294 public void setDateFormatSymbols(DateFormatSymbols newFormatSymbols)
2295 {
2296 this.formatData = (DateFormatSymbols)newFormatSymbols.clone();
2297 useDateFormatSymbols = true;
2298 }
2299
2300 /**
2301 * Creates a copy of this <code>SimpleDateFormat</code>. This also
2302 * clones the format's date format symbols.
2303 *
2304 * @return a clone of this <code>SimpleDateFormat</code>
2305 */
2306 @Override
2307 public Object clone() {
2308 SimpleDateFormat other = (SimpleDateFormat) super.clone();
2309 other.formatData = (DateFormatSymbols) formatData.clone();
2310 return other;
2311 }
2312
2313 /**
2314 * Returns the hash code value for this <code>SimpleDateFormat</code> object.
2315 *
2316 * @return the hash code value for this <code>SimpleDateFormat</code> object.
2317 */
2318 @Override
2319 public int hashCode()
2320 {
2321 return pattern.hashCode();
2322 // just enough fields for a reasonable distribution
2323 }
2324
2325 /**
2326 * Compares the given object with this <code>SimpleDateFormat</code> for
2327 * equality.
2328 *
2329 * @return true if the given object is equal to this
2330 * <code>SimpleDateFormat</code>
2331 */
2332 @Override
2333 public boolean equals(Object obj)
2334 {
2335 if (!super.equals(obj)) {
2336 return false; // super does class check
2337 }
2338 SimpleDateFormat that = (SimpleDateFormat) obj;
2339 return (pattern.equals(that.pattern)
2340 && formatData.equals(that.formatData));
2341 }
2342
2343 /**
2344 * After reading an object from the input stream, the format
2345 * pattern in the object is verified.
2346 * <p>
2347 * @exception InvalidObjectException if the pattern is invalid
2348 */
2349 private void readObject(ObjectInputStream stream)
2350 throws IOException, ClassNotFoundException {
2351 stream.defaultReadObject();
2352
2353 try {
2354 compiledPattern = compile(pattern);
2355 } catch (Exception e) {
2356 throw new InvalidObjectException("invalid pattern");
2357 }
|