OSDN Git Service

* java/text/SimpleDateFormat (parse): Revert patch of 2003-09-25.
[pf3gnuchains/gcc-fork.git] / libjava / java / text / SimpleDateFormat.java
1 /* SimpleDateFormat.java -- A class for parsing/formating simple 
2    date constructs
3    Copyright (C) 1998, 1999, 2000, 2001, 2003 Free Software Foundation, Inc.
4
5 This file is part of GNU Classpath.
6
7 GNU Classpath is free software; you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation; either version 2, or (at your option)
10 any later version.
11  
12 GNU Classpath is distributed in the hope that it will be useful, but
13 WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15 General Public License for more details.
16
17 You should have received a copy of the GNU General Public License
18 along with GNU Classpath; see the file COPYING.  If not, write to the
19 Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
20 02111-1307 USA.
21
22 Linking this library statically or dynamically with other modules is
23 making a combined work based on this library.  Thus, the terms and
24 conditions of the GNU General Public License cover the whole
25 combination.
26
27 As a special exception, the copyright holders of this library give you
28 permission to link this library with independent modules to produce an
29 executable, regardless of the license terms of these independent
30 modules, and to copy and distribute the resulting executable under
31 terms of your choice, provided that you also meet, for each linked
32 independent module, the terms and conditions of the license of that
33 module.  An independent module is a module which is not derived from
34 or based on this library.  If you modify this library, you may extend
35 this exception to your version of the library, but you are not
36 obligated to do so.  If you do not wish to do so, delete this
37 exception statement from your version. */
38
39
40 package java.text;
41
42 import java.util.Calendar;
43 import java.util.Date;
44 import java.util.Enumeration;
45 import java.util.GregorianCalendar;
46 import java.util.Locale;
47 import java.util.TimeZone;
48 import java.util.SimpleTimeZone;
49 import java.util.Vector;
50 import java.io.ObjectInputStream;
51 import java.io.IOException;
52
53 /**
54  * SimpleDateFormat provides convenient methods for parsing and formatting
55  * dates using Gregorian calendars (see java.util.GregorianCalendar). 
56  */
57 public class SimpleDateFormat extends DateFormat 
58 {
59   /** A pair class used by SimpleDateFormat as a compiled representation
60    *  of a format string.
61    */
62   private class FieldSizePair 
63   {
64     public int field;
65     public int size;
66
67     /** Constructs a pair with the given field and size values */
68     public FieldSizePair(int f, int s) {
69       field = f;
70       size = s;
71     }
72   }
73
74   private transient Vector tokens;
75   private DateFormatSymbols formatData;  // formatData
76   private Date defaultCenturyStart;
77   private transient int defaultCentury;
78   private String pattern;
79   private int serialVersionOnStream = 1; // 0 indicates JDK1.1.3 or earlier
80   private static final long serialVersionUID = 4774881970558875024L;
81
82   // This string is specified in the JCL.  We set it here rather than
83   // do a DateFormatSymbols(Locale.US).getLocalPatternChars() since
84   // someone could theoretically change those values (though unlikely).
85   private static final String standardChars = "GyMdkHmsSEDFwWahKz";
86
87   private void readObject(ObjectInputStream stream)
88     throws IOException, ClassNotFoundException
89   {
90     stream.defaultReadObject();
91     if (serialVersionOnStream < 1)
92       {
93         computeCenturyStart ();
94         serialVersionOnStream = 1;
95       }
96     else
97       // Ensure that defaultCentury gets set.
98       set2DigitYearStart(defaultCenturyStart);
99
100     // Set up items normally taken care of by the constructor.
101     tokens = new Vector();
102     compileFormat(pattern);
103   }
104
105   private void compileFormat(String pattern) 
106   {
107     // Any alphabetical characters are treated as pattern characters
108     // unless enclosed in single quotes.
109
110     char thisChar;
111     int pos;
112     int field;
113     FieldSizePair current = null;
114
115     for (int i=0; i<pattern.length(); i++) {
116       thisChar = pattern.charAt(i);
117       field = formatData.getLocalPatternChars().indexOf(thisChar);
118       if (field == -1) {
119         current = null;
120         if (Character.isLetter(thisChar)) {
121           // Not a valid letter
122           tokens.addElement(new FieldSizePair(-1,0));
123         } else if (thisChar == '\'') {
124           // Quoted text section; skip to next single quote
125           pos = pattern.indexOf('\'',i+1);
126           if (pos == -1) {
127             // This ought to be an exception, but spec does not
128             // let us throw one.
129             tokens.addElement(new FieldSizePair(-1,0));
130           }
131           if ((pos+1 < pattern.length()) && (pattern.charAt(pos+1) == '\'')) {
132             tokens.addElement(pattern.substring(i+1,pos+1));
133           } else {
134             tokens.addElement(pattern.substring(i+1,pos));
135           }
136           i = pos;
137         } else {
138           // A special character
139           tokens.addElement(new Character(thisChar));
140         }
141       } else {
142         // A valid field
143         if ((current != null) && (field == current.field)) {
144           current.size++;
145         } else {
146           current = new FieldSizePair(field,1);
147           tokens.addElement(current);
148         }
149       }
150     }
151   }
152     
153   public String toString() 
154   {
155     StringBuffer output = new StringBuffer();
156     Enumeration e = tokens.elements();
157     while (e.hasMoreElements()) {
158       output.append(e.nextElement().toString());
159     }
160     return output.toString();
161   }
162       
163   /**
164    * Constructs a SimpleDateFormat using the default pattern for
165    * the default locale.
166    */
167   public SimpleDateFormat() 
168   {
169     /*
170      * There does not appear to be a standard API for determining 
171      * what the default pattern for a locale is, so use package-scope
172      * variables in DateFormatSymbols to encapsulate this.
173      */
174     super();
175     Locale locale = Locale.getDefault();
176     calendar = new GregorianCalendar(locale);
177     computeCenturyStart();
178     tokens = new Vector();
179     formatData = new DateFormatSymbols(locale);
180     pattern = (formatData.dateFormats[DEFAULT] + ' '
181                + formatData.timeFormats[DEFAULT]);
182     compileFormat(pattern);
183     numberFormat = NumberFormat.getInstance(locale);
184     numberFormat.setGroupingUsed (false);
185     numberFormat.setParseIntegerOnly (true);
186   }
187   
188   /**
189    * Creates a date formatter using the specified pattern, with the default
190    * DateFormatSymbols for the default locale.
191    */
192   public SimpleDateFormat(String pattern) 
193   {
194     this(pattern, Locale.getDefault());
195   }
196
197   /**
198    * Creates a date formatter using the specified pattern, with the default
199    * DateFormatSymbols for the given locale.
200    */
201   public SimpleDateFormat(String pattern, Locale locale) 
202   {
203     super();
204     calendar = new GregorianCalendar(locale);
205     computeCenturyStart();
206     tokens = new Vector();
207     formatData = new DateFormatSymbols(locale);
208     compileFormat(pattern);
209     this.pattern = pattern;
210     numberFormat = NumberFormat.getInstance(locale);
211     numberFormat.setGroupingUsed (false);
212     numberFormat.setParseIntegerOnly (true);
213   }
214
215   /**
216    * Creates a date formatter using the specified pattern. The
217    * specified DateFormatSymbols will be used when formatting.
218    */
219   public SimpleDateFormat(String pattern, DateFormatSymbols formatData)
220   {
221     super();
222     calendar = new GregorianCalendar();
223     computeCenturyStart ();
224     tokens = new Vector();
225     this.formatData = formatData;
226     compileFormat(pattern);
227     this.pattern = pattern;
228     numberFormat = NumberFormat.getInstance();
229     numberFormat.setGroupingUsed (false);
230     numberFormat.setParseIntegerOnly (true);
231   }
232
233   // What is the difference between localized and unlocalized?  The
234   // docs don't say.
235
236   /**
237    * This method returns a string with the formatting pattern being used
238    * by this object.  This string is unlocalized.
239    *
240    * @return The format string.
241    */
242   public String toPattern()
243   {
244     return pattern;
245   }
246
247   /**
248    * This method returns a string with the formatting pattern being used
249    * by this object.  This string is localized.
250    *
251    * @return The format string.
252    */
253   public String toLocalizedPattern()
254   {
255     String localChars = formatData.getLocalPatternChars();
256     return applyLocalizedPattern (pattern, standardChars, localChars);
257   }
258
259   /**
260    * This method sets the formatting pattern that should be used by this
261    * object.  This string is not localized.
262    *
263    * @param pattern The new format pattern.
264    */
265   public void applyPattern(String pattern)
266   {
267     tokens = new Vector();
268     compileFormat(pattern);
269     this.pattern = pattern;
270   }
271
272   /**
273    * This method sets the formatting pattern that should be used by this
274    * object.  This string is localized.
275    *
276    * @param pattern The new format pattern.
277    */
278   public void applyLocalizedPattern(String pattern)
279   {
280     String localChars = formatData.getLocalPatternChars();
281     pattern = applyLocalizedPattern (pattern, localChars, standardChars);
282     applyPattern(pattern);
283   }
284
285   private String applyLocalizedPattern(String pattern,
286                                        String oldChars, String newChars)
287   {
288     int len = pattern.length();
289     StringBuffer buf = new StringBuffer(len);
290     boolean quoted = false;
291     for (int i = 0;  i < len;  i++)
292       {
293         char ch = pattern.charAt(i);
294         if (ch == '\'')
295           quoted = ! quoted;
296         if (! quoted)
297           {
298             int j = oldChars.indexOf(ch);
299             if (j >= 0)
300               ch = newChars.charAt(j);
301           }
302         buf.append(ch);
303       }
304     return buf.toString();
305   }
306
307   /** 
308    * Returns the start of the century used for two digit years.
309    *
310    * @return A <code>Date</code> representing the start of the century
311    * for two digit years.
312    */
313   public Date get2DigitYearStart()
314   {
315     return defaultCenturyStart;
316   }
317
318   /**
319    * Sets the start of the century used for two digit years.
320    *
321    * @param date A <code>Date</code> representing the start of the century for
322    * two digit years.
323    */
324   public void set2DigitYearStart(Date date)
325   {
326     defaultCenturyStart = date;
327     calendar.clear();
328     calendar.setTime(date);
329     int year = calendar.get(Calendar.YEAR);
330     defaultCentury = year - (year % 100);
331   }
332
333   /**
334    * This method returns the format symbol information used for parsing
335    * and formatting dates.
336    *
337    * @return The date format symbols.
338    */
339   public DateFormatSymbols getDateFormatSymbols()
340   {
341     return formatData;
342   }
343
344   /**
345    * This method sets the format symbols information used for parsing
346    * and formatting dates.
347    *
348    * @param formatData The date format symbols.
349    */
350    public void setDateFormatSymbols(DateFormatSymbols formatData)
351    {
352      this.formatData = formatData;
353    }
354
355   /**
356    * This methods tests whether the specified object is equal to this
357    * object.  This will be true if and only if the specified object:
358    * <p>
359    * <ul>
360    * <li>Is not <code>null</code>.
361    * <li>Is an instance of <code>SimpleDateFormat</code>.
362    * <li>Is equal to this object at the superclass (i.e., <code>DateFormat</code>)
363    *     level.
364    * <li>Has the same formatting pattern.
365    * <li>Is using the same formatting symbols.
366    * <li>Is using the same century for two digit years.
367    * </ul>
368    *
369    * @param obj The object to compare for equality against.
370    *
371    * @return <code>true</code> if the specified object is equal to this object,
372    * <code>false</code> otherwise.
373    */
374   public boolean equals(Object o)
375   {
376     if (o == null)
377       return false;
378
379     if (!super.equals(o))
380       return false;
381
382     if (!(o instanceof SimpleDateFormat))
383       return false;
384
385     SimpleDateFormat sdf = (SimpleDateFormat)o;
386
387     if (defaultCentury != sdf.defaultCentury)
388       return false;
389
390     if (!toPattern().equals(sdf.toPattern()))
391       return false;
392
393     if (!getDateFormatSymbols().equals(sdf.getDateFormatSymbols()))
394       return false;
395
396     return true;
397   }
398
399   /**
400    * This method returns a hash value for this object.
401    *
402    * @return A hash value for this object.
403    */
404   public int hashCode()
405   {
406     return super.hashCode() ^ toPattern().hashCode() ^ defaultCentury ^
407       getDateFormatSymbols().hashCode();
408   }
409
410
411   /**
412    * Formats the date input according to the format string in use,
413    * appending to the specified StringBuffer.  The input StringBuffer
414    * is returned as output for convenience.
415    */
416   public StringBuffer format(Date date, StringBuffer buffer, FieldPosition pos)
417   {
418     String temp;
419     calendar.setTime(date);
420     
421     // go through vector, filling in fields where applicable, else toString
422     Enumeration e = tokens.elements();
423     while (e.hasMoreElements()) {
424       Object o = e.nextElement();
425       if (o instanceof FieldSizePair) {
426         FieldSizePair p = (FieldSizePair) o;
427         int beginIndex = buffer.length();
428         switch (p.field) {
429         case ERA_FIELD:
430           buffer.append(formatData.eras[calendar.get(Calendar.ERA)]);
431           break;
432         case YEAR_FIELD:
433           // If we have two digits, then we truncate.  Otherwise, we
434           // use the size of the pattern, and zero pad.
435           if (p.size == 2)
436             {
437               temp = String.valueOf(calendar.get(Calendar.YEAR));
438               buffer.append(temp.substring(temp.length() - 2));
439             }
440           else
441             withLeadingZeros(calendar.get(Calendar.YEAR), p.size, buffer);
442           break;
443         case MONTH_FIELD:
444           if (p.size < 3)
445             withLeadingZeros(calendar.get(Calendar.MONTH)+1,p.size,buffer);
446           else if (p.size < 4)
447             buffer.append(formatData.shortMonths[calendar.get(Calendar.MONTH)]);
448           else
449             buffer.append(formatData.months[calendar.get(Calendar.MONTH)]);
450           break;
451         case DATE_FIELD:
452           withLeadingZeros(calendar.get(Calendar.DATE),p.size,buffer);
453           break;
454         case HOUR_OF_DAY1_FIELD: // 1-24
455           withLeadingZeros(((calendar.get(Calendar.HOUR_OF_DAY)+23)%24)+1,p.size,buffer);
456           break;
457         case HOUR_OF_DAY0_FIELD: // 0-23
458           withLeadingZeros(calendar.get(Calendar.HOUR_OF_DAY),p.size,buffer);
459           break;
460         case MINUTE_FIELD:
461           withLeadingZeros(calendar.get(Calendar.MINUTE),p.size,buffer);
462           break;
463         case SECOND_FIELD:
464           withLeadingZeros(calendar.get(Calendar.SECOND),p.size,buffer);
465           break;
466         case MILLISECOND_FIELD:
467           withLeadingZeros(calendar.get(Calendar.MILLISECOND),p.size,buffer);
468           break;
469         case DAY_OF_WEEK_FIELD:
470           if (p.size < 4)
471             buffer.append(formatData.shortWeekdays[calendar.get(Calendar.DAY_OF_WEEK)]);
472           else
473             buffer.append(formatData.weekdays[calendar.get(Calendar.DAY_OF_WEEK)]);
474           break;
475         case DAY_OF_YEAR_FIELD:
476           withLeadingZeros(calendar.get(Calendar.DAY_OF_YEAR),p.size,buffer);
477           break;
478         case DAY_OF_WEEK_IN_MONTH_FIELD:
479           withLeadingZeros(calendar.get(Calendar.DAY_OF_WEEK_IN_MONTH),p.size,buffer);
480           break;
481         case WEEK_OF_YEAR_FIELD:
482           withLeadingZeros(calendar.get(Calendar.WEEK_OF_YEAR),p.size,buffer);
483           break;
484         case WEEK_OF_MONTH_FIELD:
485           withLeadingZeros(calendar.get(Calendar.WEEK_OF_MONTH),p.size,buffer);
486           break;
487         case AM_PM_FIELD:
488           buffer.append(formatData.ampms[calendar.get(Calendar.AM_PM)]);
489           break;
490         case HOUR1_FIELD: // 1-12
491           withLeadingZeros(((calendar.get(Calendar.HOUR)+11)%12)+1,p.size,buffer);
492           break;
493         case HOUR0_FIELD: // 0-11
494           withLeadingZeros(calendar.get(Calendar.HOUR),p.size,buffer);
495           break;
496         case TIMEZONE_FIELD:
497           TimeZone zone = calendar.getTimeZone();
498           boolean isDST = calendar.get(Calendar.DST_OFFSET) != 0;
499           // FIXME: XXX: This should be a localized time zone.
500           String zoneID = zone.getDisplayName(isDST, p.size > 3 ? TimeZone.LONG : TimeZone.SHORT);
501           buffer.append(zoneID);
502           break;
503         default:
504           throw new IllegalArgumentException("Illegal pattern character");
505         }
506         if (pos != null && p.field == pos.getField())
507           {
508             pos.setBeginIndex(beginIndex);
509             pos.setEndIndex(buffer.length());
510           }
511       } else {
512         buffer.append(o.toString());
513       }
514     }
515     return buffer;
516   }
517
518   private void withLeadingZeros(int value, int length, StringBuffer buffer) 
519   {
520     String valStr = String.valueOf(value);
521     for (length -= valStr.length(); length > 0; length--)
522       buffer.append('0');
523     buffer.append(valStr);
524   }
525
526   private final boolean expect (String source, ParsePosition pos, char ch)
527   {
528     int x = pos.getIndex();
529     boolean r = x < source.length() && source.charAt(x) == ch;
530     if (r)
531       pos.setIndex(x + 1);
532     else
533       pos.setErrorIndex(x);
534     return r;
535   }
536
537   /**
538    * This method parses the specified string into a date.
539    * 
540    * @param dateStr The date string to parse.
541    * @param pos The input and output parse position
542    *
543    * @return The parsed date, or <code>null</code> if the string cannot be
544    * parsed.
545    */
546   public Date parse (String dateStr, ParsePosition pos)
547   {
548     int fmt_index = 0;
549     int fmt_max = pattern.length();
550
551     calendar.clear();
552     boolean saw_timezone = false;
553     int quote_start = -1;
554     boolean is2DigitYear = false;
555     for (; fmt_index < fmt_max; ++fmt_index)
556       {
557         char ch = pattern.charAt(fmt_index);
558         if (ch == '\'')
559           {
560             int index = pos.getIndex();
561             if (fmt_index < fmt_max - 1
562                 && pattern.charAt(fmt_index + 1) == '\'')
563               {
564                 if (! expect (dateStr, pos, ch))
565                   return null;
566                 ++fmt_index;
567               }
568             else
569               quote_start = quote_start < 0 ? fmt_index : -1;
570             continue;
571           }
572
573         if (quote_start != -1
574             || ((ch < 'a' || ch > 'z')
575                 && (ch < 'A' || ch > 'Z')))
576           {
577             if (! expect (dateStr, pos, ch))
578               return null;
579             continue;
580           }
581
582         // We've arrived at a potential pattern character in the
583         // pattern.
584         int first = fmt_index;
585         while (++fmt_index < fmt_max && pattern.charAt(fmt_index) == ch)
586           ;
587         int fmt_count = fmt_index - first;
588
589         // We might need to limit the number of digits to parse in
590         // some cases.  We look to the next pattern character to
591         // decide.
592         boolean limit_digits = false;
593         if (fmt_index < fmt_max
594             && standardChars.indexOf(pattern.charAt(fmt_index)) >= 0)
595           limit_digits = true;
596         --fmt_index;
597
598         // We can handle most fields automatically: most either are
599         // numeric or are looked up in a string vector.  In some cases
600         // we need an offset.  When numeric, `offset' is added to the
601         // resulting value.  When doing a string lookup, offset is the
602         // initial index into the string array.
603         int calendar_field;
604         boolean is_numeric = true;
605         String[] match = null;
606         int offset = 0;
607         boolean maybe2DigitYear = false;
608         switch (ch)
609           {
610           case 'd':
611             calendar_field = Calendar.DATE;
612             break;
613           case 'D':
614             calendar_field = Calendar.DAY_OF_YEAR;
615             break;
616           case 'F':
617             calendar_field = Calendar.DAY_OF_WEEK_IN_MONTH;
618             break;
619           case 'E':
620             is_numeric = false;
621             offset = 1;
622             calendar_field = Calendar.DAY_OF_WEEK;
623             match = (fmt_count <= 3
624                      ? formatData.getShortWeekdays()
625                      : formatData.getWeekdays());
626             break;
627           case 'w':
628             calendar_field = Calendar.WEEK_OF_YEAR;
629             break;
630           case 'W':
631             calendar_field = Calendar.WEEK_OF_MONTH;
632             break;
633           case 'M':
634             calendar_field = Calendar.MONTH;
635             if (fmt_count <= 2)
636               offset = -1;
637             else
638               {
639                 is_numeric = false;
640                 match = (fmt_count <= 3
641                          ? formatData.getShortMonths()
642                          : formatData.getMonths());
643               }
644             break;
645           case 'y':
646             calendar_field = Calendar.YEAR;
647             if (fmt_count <= 2)
648               maybe2DigitYear = true;
649             break;
650           case 'K':
651             calendar_field = Calendar.HOUR;
652             break;
653           case 'h':
654             calendar_field = Calendar.HOUR;
655             break;
656           case 'H':
657             calendar_field = Calendar.HOUR_OF_DAY;
658             break;
659           case 'k':
660             calendar_field = Calendar.HOUR_OF_DAY;
661             break;
662           case 'm':
663             calendar_field = Calendar.MINUTE;
664             break;
665           case 's':
666             calendar_field = Calendar.SECOND;
667             break;
668           case 'S':
669             calendar_field = Calendar.MILLISECOND;
670             break;
671           case 'a':
672             is_numeric = false;
673             calendar_field = Calendar.AM_PM;
674             match = formatData.getAmPmStrings();
675             break;
676           case 'z':
677             // We need a special case for the timezone, because it
678             // uses a different data structure than the other cases.
679             is_numeric = false;
680             calendar_field = Calendar.DST_OFFSET;
681             String[][] zoneStrings = formatData.getZoneStrings();
682             int zoneCount = zoneStrings.length;
683             int index = pos.getIndex();
684             boolean found_zone = false;
685             for (int j = 0;  j < zoneCount;  j++)
686               {
687                 String[] strings = zoneStrings[j];
688                 int k;
689                 for (k = 1; k < strings.length; ++k)
690                   {
691                     if (dateStr.startsWith(strings[k], index))
692                       break;
693                   }
694                 if (k != strings.length)
695                   {
696                     found_zone = true;
697                     saw_timezone = true;
698                     TimeZone tz = TimeZone.getTimeZone (strings[0]);
699                     calendar.set (Calendar.ZONE_OFFSET, tz.getRawOffset ());
700                     offset = 0;
701                     if (k > 2 && tz instanceof SimpleTimeZone)
702                       {
703                         SimpleTimeZone stz = (SimpleTimeZone) tz;
704                         offset = stz.getDSTSavings ();
705                       }
706                     pos.setIndex(index + strings[k].length());
707                     break;
708                   }
709               }
710             if (! found_zone)
711               {
712                 pos.setErrorIndex(pos.getIndex());
713                 return null;
714               }
715             break;
716           default:
717             pos.setErrorIndex(pos.getIndex());
718             return null;
719           }
720
721         // Compute the value we should assign to the field.
722         int value;
723         int index = -1;
724         if (is_numeric)
725           {
726             numberFormat.setMinimumIntegerDigits(fmt_count);
727             if (limit_digits)
728               numberFormat.setMaximumIntegerDigits(fmt_count);
729             if (maybe2DigitYear)
730               index = pos.getIndex();
731             Number n = numberFormat.parse(dateStr, pos);
732             if (pos == null || ! (n instanceof Long))
733               return null;
734             value = n.intValue() + offset;
735           }
736         else if (match != null)
737           {
738             index = pos.getIndex();
739             int i;
740             for (i = offset; i < match.length; ++i)
741               {
742                 if (dateStr.startsWith(match[i], index))
743                   break;
744               }
745             if (i == match.length)
746               {
747                 pos.setErrorIndex(index);
748                 return null;
749               }
750             pos.setIndex(index + match[i].length());
751             value = i;
752           }
753         else
754           value = offset;
755           
756         if (maybe2DigitYear)
757           {
758             // Parse into default century if the numeric year string has 
759             // exactly 2 digits.
760             int digit_count = pos.getIndex() - index;
761             if (digit_count == 2)
762               is2DigitYear = true;
763           }
764
765         // Assign the value and move on.
766         calendar.set(calendar_field, value);
767       }
768     
769     if (is2DigitYear)
770       {
771         // Apply the 80-20 heuristic to dermine the full year based on 
772         // defaultCenturyStart. 
773         int year = defaultCentury + calendar.get(Calendar.YEAR);
774         calendar.set(Calendar.YEAR, year);
775         if (calendar.getTime().compareTo(defaultCenturyStart) < 0)
776           calendar.set(Calendar.YEAR, year + 100);      
777       }
778
779     try
780       {
781         if (! saw_timezone)
782           {
783             // Use the real rules to determine whether or not this
784             // particular time is in daylight savings.
785             calendar.clear (Calendar.DST_OFFSET);
786             calendar.clear (Calendar.ZONE_OFFSET);
787           }
788         return calendar.getTime();
789       }
790     catch (IllegalArgumentException x)
791       {
792         pos.setErrorIndex(pos.getIndex());
793         return null;
794       }
795   }
796
797   // Compute the start of the current century as defined by
798   // get2DigitYearStart.
799   private void computeCenturyStart()
800   {
801     int year = calendar.get(Calendar.YEAR);
802     calendar.set(Calendar.YEAR, year - 80);
803     set2DigitYearStart(calendar.getTime());
804   }
805 }