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