OSDN Git Service

2003-12-02 Ito Kazumitsu <kaz@maczuka.gcd.org>
[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.ArrayList;
43 import java.util.Calendar;
44 import java.util.Date;
45 import java.util.GregorianCalendar;
46 import java.util.Iterator;
47 import java.util.Locale;
48 import java.util.TimeZone;
49 import java.util.SimpleTimeZone;
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 ArrayList 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 ArrayList();
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 ((thisChar >= 'A' && thisChar <= 'Z')
121             || (thisChar >= 'a' && thisChar <= 'z')) {
122           // Not a valid letter
123           tokens.add(new FieldSizePair(-1,0));
124         } else if (thisChar == '\'') {
125           // Quoted text section; skip to next single quote
126           pos = pattern.indexOf('\'',i+1);
127           if (pos == -1) {
128             // This ought to be an exception, but spec does not
129             // let us throw one.
130             tokens.add(new FieldSizePair(-1,0));
131           }
132           if ((pos+1 < pattern.length()) && (pattern.charAt(pos+1) == '\'')) {
133             tokens.add(pattern.substring(i+1,pos+1));
134           } else {
135             tokens.add(pattern.substring(i+1,pos));
136           }
137           i = pos;
138         } else {
139           // A special character
140           tokens.add(new Character(thisChar));
141         }
142       } else {
143         // A valid field
144         if ((current != null) && (field == current.field)) {
145           current.size++;
146         } else {
147           current = new FieldSizePair(field,1);
148           tokens.add(current);
149         }
150       }
151     }
152   }
153
154   public String toString() 
155   {
156     StringBuffer output = new StringBuffer();
157     Iterator i = tokens.iterator();
158     while (i.hasNext()) {
159       output.append(i.next().toString());
160     }
161     return output.toString();
162   }
163
164   /**
165    * Constructs a SimpleDateFormat using the default pattern for
166    * the default locale.
167    */
168   public SimpleDateFormat() 
169   {
170     /*
171      * There does not appear to be a standard API for determining 
172      * what the default pattern for a locale is, so use package-scope
173      * variables in DateFormatSymbols to encapsulate this.
174      */
175     super();
176     Locale locale = Locale.getDefault();
177     calendar = new GregorianCalendar(locale);
178     computeCenturyStart();
179     tokens = new ArrayList();
180     formatData = new DateFormatSymbols(locale);
181     pattern = (formatData.dateFormats[DEFAULT] + ' '
182                + formatData.timeFormats[DEFAULT]);
183     compileFormat(pattern);
184     numberFormat = NumberFormat.getInstance(locale);
185     numberFormat.setGroupingUsed (false);
186     numberFormat.setParseIntegerOnly (true);
187   }
188   
189   /**
190    * Creates a date formatter using the specified pattern, with the default
191    * DateFormatSymbols for the default locale.
192    */
193   public SimpleDateFormat(String pattern) 
194   {
195     this(pattern, Locale.getDefault());
196   }
197
198   /**
199    * Creates a date formatter using the specified pattern, with the default
200    * DateFormatSymbols for the given locale.
201    */
202   public SimpleDateFormat(String pattern, Locale locale) 
203   {
204     super();
205     calendar = new GregorianCalendar(locale);
206     computeCenturyStart();
207     tokens = new ArrayList();
208     formatData = new DateFormatSymbols(locale);
209     compileFormat(pattern);
210     this.pattern = pattern;
211     numberFormat = NumberFormat.getInstance(locale);
212     numberFormat.setGroupingUsed (false);
213     numberFormat.setParseIntegerOnly (true);
214   }
215
216   /**
217    * Creates a date formatter using the specified pattern. The
218    * specified DateFormatSymbols will be used when formatting.
219    */
220   public SimpleDateFormat(String pattern, DateFormatSymbols formatData)
221   {
222     super();
223     calendar = new GregorianCalendar();
224     computeCenturyStart ();
225     tokens = new ArrayList();
226     this.formatData = formatData;
227     compileFormat(pattern);
228     this.pattern = pattern;
229     numberFormat = NumberFormat.getInstance();
230     numberFormat.setGroupingUsed (false);
231     numberFormat.setParseIntegerOnly (true);
232   }
233
234   // What is the difference between localized and unlocalized?  The
235   // docs don't say.
236
237   /**
238    * This method returns a string with the formatting pattern being used
239    * by this object.  This string is unlocalized.
240    *
241    * @return The format string.
242    */
243   public String toPattern()
244   {
245     return pattern;
246   }
247
248   /**
249    * This method returns a string with the formatting pattern being used
250    * by this object.  This string is localized.
251    *
252    * @return The format string.
253    */
254   public String toLocalizedPattern()
255   {
256     String localChars = formatData.getLocalPatternChars();
257     return applyLocalizedPattern (pattern, standardChars, localChars);
258   }
259
260   /**
261    * This method sets the formatting pattern that should be used by this
262    * object.  This string is not localized.
263    *
264    * @param pattern The new format pattern.
265    */
266   public void applyPattern(String pattern)
267   {
268     tokens = new ArrayList();
269     compileFormat(pattern);
270     this.pattern = pattern;
271   }
272
273   /**
274    * This method sets the formatting pattern that should be used by this
275    * object.  This string is localized.
276    *
277    * @param pattern The new format pattern.
278    */
279   public void applyLocalizedPattern(String pattern)
280   {
281     String localChars = formatData.getLocalPatternChars();
282     pattern = applyLocalizedPattern (pattern, localChars, standardChars);
283     applyPattern(pattern);
284   }
285
286   private String applyLocalizedPattern(String pattern,
287                                        String oldChars, String newChars)
288   {
289     int len = pattern.length();
290     StringBuffer buf = new StringBuffer(len);
291     boolean quoted = false;
292     for (int i = 0;  i < len;  i++)
293       {
294         char ch = pattern.charAt(i);
295         if (ch == '\'')
296           quoted = ! quoted;
297         if (! quoted)
298           {
299             int j = oldChars.indexOf(ch);
300             if (j >= 0)
301               ch = newChars.charAt(j);
302           }
303         buf.append(ch);
304       }
305     return buf.toString();
306   }
307
308   /** 
309    * Returns the start of the century used for two digit years.
310    *
311    * @return A <code>Date</code> representing the start of the century
312    * for two digit years.
313    */
314   public Date get2DigitYearStart()
315   {
316     return defaultCenturyStart;
317   }
318
319   /**
320    * Sets the start of the century used for two digit years.
321    *
322    * @param date A <code>Date</code> representing the start of the century for
323    * two digit years.
324    */
325   public void set2DigitYearStart(Date date)
326   {
327     defaultCenturyStart = date;
328     calendar.clear();
329     calendar.setTime(date);
330     int year = calendar.get(Calendar.YEAR);
331     defaultCentury = year - (year % 100);
332   }
333
334   /**
335    * This method returns the format symbol information used for parsing
336    * and formatting dates.
337    *
338    * @return The date format symbols.
339    */
340   public DateFormatSymbols getDateFormatSymbols()
341   {
342     return formatData;
343   }
344
345   /**
346    * This method sets the format symbols information used for parsing
347    * and formatting dates.
348    *
349    * @param formatData The date format symbols.
350    */
351    public void setDateFormatSymbols(DateFormatSymbols formatData)
352    {
353      this.formatData = formatData;
354    }
355
356   /**
357    * This methods tests whether the specified object is equal to this
358    * object.  This will be true if and only if the specified object:
359    * <p>
360    * <ul>
361    * <li>Is not <code>null</code>.
362    * <li>Is an instance of <code>SimpleDateFormat</code>.
363    * <li>Is equal to this object at the superclass (i.e., <code>DateFormat</code>)
364    *     level.
365    * <li>Has the same formatting pattern.
366    * <li>Is using the same formatting symbols.
367    * <li>Is using the same century for two digit years.
368    * </ul>
369    *
370    * @param obj The object to compare for equality against.
371    *
372    * @return <code>true</code> if the specified object is equal to this object,
373    * <code>false</code> otherwise.
374    */
375   public boolean equals(Object o)
376   {
377     if (!super.equals(o))
378       return false;
379
380     if (!(o instanceof SimpleDateFormat))
381       return false;
382
383     SimpleDateFormat sdf = (SimpleDateFormat)o;
384
385     if (defaultCentury != sdf.defaultCentury)
386       return false;
387
388     if (!toPattern().equals(sdf.toPattern()))
389       return false;
390
391     if (!getDateFormatSymbols().equals(sdf.getDateFormatSymbols()))
392       return false;
393
394     return true;
395   }
396
397   /**
398    * This method returns a hash value for this object.
399    *
400    * @return A hash value for this object.
401    */
402   public int hashCode()
403   {
404     return super.hashCode() ^ toPattern().hashCode() ^ defaultCentury ^
405       getDateFormatSymbols().hashCode();
406   }
407
408
409   /**
410    * Formats the date input according to the format string in use,
411    * appending to the specified StringBuffer.  The input StringBuffer
412    * is returned as output for convenience.
413    */
414   public StringBuffer format(Date date, StringBuffer buffer, FieldPosition pos)
415   {
416     String temp;
417     calendar.setTime(date);
418     
419     // go through ArrayList, filling in fields where applicable, else toString
420     Iterator i = tokens.iterator();
421     while (i.hasNext()) {
422       Object o = i.next();
423       if (o instanceof FieldSizePair) {
424         FieldSizePair p = (FieldSizePair) o;
425         int beginIndex = buffer.length();
426         switch (p.field) {
427         case ERA_FIELD:
428           buffer.append(formatData.eras[calendar.get(Calendar.ERA)]);
429           break;
430         case YEAR_FIELD:
431           // If we have two digits, then we truncate.  Otherwise, we
432           // use the size of the pattern, and zero pad.
433           if (p.size == 2)
434             {
435               temp = String.valueOf(calendar.get(Calendar.YEAR));
436               buffer.append(temp.substring(temp.length() - 2));
437             }
438           else
439             withLeadingZeros(calendar.get(Calendar.YEAR), p.size, buffer);
440           break;
441         case MONTH_FIELD:
442           if (p.size < 3)
443             withLeadingZeros(calendar.get(Calendar.MONTH)+1,p.size,buffer);
444           else if (p.size < 4)
445             buffer.append(formatData.shortMonths[calendar.get(Calendar.MONTH)]);
446           else
447             buffer.append(formatData.months[calendar.get(Calendar.MONTH)]);
448           break;
449         case DATE_FIELD:
450           withLeadingZeros(calendar.get(Calendar.DATE),p.size,buffer);
451           break;
452         case HOUR_OF_DAY1_FIELD: // 1-24
453           withLeadingZeros(((calendar.get(Calendar.HOUR_OF_DAY)+23)%24)+1,p.size,buffer);
454           break;
455         case HOUR_OF_DAY0_FIELD: // 0-23
456           withLeadingZeros(calendar.get(Calendar.HOUR_OF_DAY),p.size,buffer);
457           break;
458         case MINUTE_FIELD:
459           withLeadingZeros(calendar.get(Calendar.MINUTE),p.size,buffer);
460           break;
461         case SECOND_FIELD:
462           withLeadingZeros(calendar.get(Calendar.SECOND),p.size,buffer);
463           break;
464         case MILLISECOND_FIELD:
465           withLeadingZeros(calendar.get(Calendar.MILLISECOND),p.size,buffer);
466           break;
467         case DAY_OF_WEEK_FIELD:
468           if (p.size < 4)
469             buffer.append(formatData.shortWeekdays[calendar.get(Calendar.DAY_OF_WEEK)]);
470           else
471             buffer.append(formatData.weekdays[calendar.get(Calendar.DAY_OF_WEEK)]);
472           break;
473         case DAY_OF_YEAR_FIELD:
474           withLeadingZeros(calendar.get(Calendar.DAY_OF_YEAR),p.size,buffer);
475           break;
476         case DAY_OF_WEEK_IN_MONTH_FIELD:
477           withLeadingZeros(calendar.get(Calendar.DAY_OF_WEEK_IN_MONTH),p.size,buffer);
478           break;
479         case WEEK_OF_YEAR_FIELD:
480           withLeadingZeros(calendar.get(Calendar.WEEK_OF_YEAR),p.size,buffer);
481           break;
482         case WEEK_OF_MONTH_FIELD:
483           withLeadingZeros(calendar.get(Calendar.WEEK_OF_MONTH),p.size,buffer);
484           break;
485         case AM_PM_FIELD:
486           buffer.append(formatData.ampms[calendar.get(Calendar.AM_PM)]);
487           break;
488         case HOUR1_FIELD: // 1-12
489           withLeadingZeros(((calendar.get(Calendar.HOUR)+11)%12)+1,p.size,buffer);
490           break;
491         case HOUR0_FIELD: // 0-11
492           withLeadingZeros(calendar.get(Calendar.HOUR),p.size,buffer);
493           break;
494         case TIMEZONE_FIELD:
495           TimeZone zone = calendar.getTimeZone();
496           boolean isDST = calendar.get(Calendar.DST_OFFSET) != 0;
497           // FIXME: XXX: This should be a localized time zone.
498           String zoneID = zone.getDisplayName(isDST, p.size > 3 ? TimeZone.LONG : TimeZone.SHORT);
499           buffer.append(zoneID);
500           break;
501         default:
502           throw new IllegalArgumentException("Illegal pattern character");
503         }
504         if (pos != null && p.field == pos.getField())
505           {
506             pos.setBeginIndex(beginIndex);
507             pos.setEndIndex(buffer.length());
508           }
509       } else {
510         buffer.append(o.toString());
511       }
512     }
513     return buffer;
514   }
515
516   private void withLeadingZeros(int value, int length, StringBuffer buffer) 
517   {
518     String valStr = String.valueOf(value);
519     for (length -= valStr.length(); length > 0; length--)
520       buffer.append('0');
521     buffer.append(valStr);
522   }
523
524   private final boolean expect (String source, ParsePosition pos, char ch)
525   {
526     int x = pos.getIndex();
527     boolean r = x < source.length() && source.charAt(x) == ch;
528     if (r)
529       pos.setIndex(x + 1);
530     else
531       pos.setErrorIndex(x);
532     return r;
533   }
534
535   /**
536    * This method parses the specified string into a date.
537    * 
538    * @param dateStr The date string to parse.
539    * @param pos The input and output parse position
540    *
541    * @return The parsed date, or <code>null</code> if the string cannot be
542    * parsed.
543    */
544   public Date parse (String dateStr, ParsePosition pos)
545   {
546     int fmt_index = 0;
547     int fmt_max = pattern.length();
548
549     calendar.clear();
550     boolean saw_timezone = false;
551     int quote_start = -1;
552     boolean is2DigitYear = false;
553     for (; fmt_index < fmt_max; ++fmt_index)
554       {
555         char ch = pattern.charAt(fmt_index);
556         if (ch == '\'')
557           {
558             int index = pos.getIndex();
559             if (fmt_index < fmt_max - 1
560                 && pattern.charAt(fmt_index + 1) == '\'')
561               {
562                 if (! expect (dateStr, pos, ch))
563                   return null;
564                 ++fmt_index;
565               }
566             else
567               quote_start = quote_start < 0 ? fmt_index : -1;
568             continue;
569           }
570
571         if (quote_start != -1
572             || ((ch < 'a' || ch > 'z')
573                 && (ch < 'A' || ch > 'Z')))
574           {
575             if (! expect (dateStr, pos, ch))
576               return null;
577             continue;
578           }
579
580         // We've arrived at a potential pattern character in the
581         // pattern.
582         int first = fmt_index;
583         while (++fmt_index < fmt_max && pattern.charAt(fmt_index) == ch)
584           ;
585         int fmt_count = fmt_index - first;
586
587         // We might need to limit the number of digits to parse in
588         // some cases.  We look to the next pattern character to
589         // decide.
590         boolean limit_digits = false;
591         if (fmt_index < fmt_max
592             && standardChars.indexOf(pattern.charAt(fmt_index)) >= 0)
593           limit_digits = true;
594         --fmt_index;
595
596         // We can handle most fields automatically: most either are
597         // numeric or are looked up in a string vector.  In some cases
598         // we need an offset.  When numeric, `offset' is added to the
599         // resulting value.  When doing a string lookup, offset is the
600         // initial index into the string array.
601         int calendar_field;
602         boolean is_numeric = true;
603         String[] match = null;
604         int offset = 0;
605         boolean maybe2DigitYear = false;
606         switch (ch)
607           {
608           case 'd':
609             calendar_field = Calendar.DATE;
610             break;
611           case 'D':
612             calendar_field = Calendar.DAY_OF_YEAR;
613             break;
614           case 'F':
615             calendar_field = Calendar.DAY_OF_WEEK_IN_MONTH;
616             break;
617           case 'E':
618             is_numeric = false;
619             offset = 1;
620             calendar_field = Calendar.DAY_OF_WEEK;
621             match = (fmt_count <= 3
622                      ? formatData.getShortWeekdays()
623                      : formatData.getWeekdays());
624             break;
625           case 'w':
626             calendar_field = Calendar.WEEK_OF_YEAR;
627             break;
628           case 'W':
629             calendar_field = Calendar.WEEK_OF_MONTH;
630             break;
631           case 'M':
632             calendar_field = Calendar.MONTH;
633             if (fmt_count <= 2)
634               offset = -1;
635             else
636               {
637                 is_numeric = false;
638                 match = (fmt_count <= 3
639                          ? formatData.getShortMonths()
640                          : formatData.getMonths());
641               }
642             break;
643           case 'y':
644             calendar_field = Calendar.YEAR;
645             if (fmt_count <= 2)
646               maybe2DigitYear = true;
647             break;
648           case 'K':
649             calendar_field = Calendar.HOUR;
650             break;
651           case 'h':
652             calendar_field = Calendar.HOUR;
653             break;
654           case 'H':
655             calendar_field = Calendar.HOUR_OF_DAY;
656             break;
657           case 'k':
658             calendar_field = Calendar.HOUR_OF_DAY;
659             break;
660           case 'm':
661             calendar_field = Calendar.MINUTE;
662             break;
663           case 's':
664             calendar_field = Calendar.SECOND;
665             break;
666           case 'S':
667             calendar_field = Calendar.MILLISECOND;
668             break;
669           case 'a':
670             is_numeric = false;
671             calendar_field = Calendar.AM_PM;
672             match = formatData.getAmPmStrings();
673             break;
674           case 'z':
675             // We need a special case for the timezone, because it
676             // uses a different data structure than the other cases.
677             is_numeric = false;
678             calendar_field = Calendar.DST_OFFSET;
679             String[][] zoneStrings = formatData.getZoneStrings();
680             int zoneCount = zoneStrings.length;
681             int index = pos.getIndex();
682             boolean found_zone = false;
683             for (int j = 0;  j < zoneCount;  j++)
684               {
685                 String[] strings = zoneStrings[j];
686                 int k;
687                 for (k = 1; k < strings.length; ++k)
688                   {
689                     if (dateStr.startsWith(strings[k], index))
690                       break;
691                   }
692                 if (k != strings.length)
693                   {
694                     found_zone = true;
695                     saw_timezone = true;
696                     TimeZone tz = TimeZone.getTimeZone (strings[0]);
697                     calendar.set (Calendar.ZONE_OFFSET, tz.getRawOffset ());
698                     offset = 0;
699                     if (k > 2 && tz instanceof SimpleTimeZone)
700                       {
701                         SimpleTimeZone stz = (SimpleTimeZone) tz;
702                         offset = stz.getDSTSavings ();
703                       }
704                     pos.setIndex(index + strings[k].length());
705                     break;
706                   }
707               }
708             if (! found_zone)
709               {
710                 pos.setErrorIndex(pos.getIndex());
711                 return null;
712               }
713             break;
714           default:
715             pos.setErrorIndex(pos.getIndex());
716             return null;
717           }
718
719         // Compute the value we should assign to the field.
720         int value;
721         int index = -1;
722         if (is_numeric)
723           {
724             numberFormat.setMinimumIntegerDigits(fmt_count);
725             if (limit_digits)
726               numberFormat.setMaximumIntegerDigits(fmt_count);
727             if (maybe2DigitYear)
728               index = pos.getIndex();
729             Number n = numberFormat.parse(dateStr, pos);
730             if (pos == null || ! (n instanceof Long))
731               return null;
732             value = n.intValue() + offset;
733           }
734         else if (match != null)
735           {
736             index = pos.getIndex();
737             int i;
738             for (i = offset; i < match.length; ++i)
739               {
740                 if (dateStr.startsWith(match[i], index))
741                   break;
742               }
743             if (i == match.length)
744               {
745                 pos.setErrorIndex(index);
746                 return null;
747               }
748             pos.setIndex(index + match[i].length());
749             value = i;
750           }
751         else
752           value = offset;
753           
754         if (maybe2DigitYear)
755           {
756             // Parse into default century if the numeric year string has 
757             // exactly 2 digits.
758             int digit_count = pos.getIndex() - index;
759             if (digit_count == 2)
760               is2DigitYear = true;
761           }
762
763         // Assign the value and move on.
764         calendar.set(calendar_field, value);
765       }
766     
767     if (is2DigitYear)
768       {
769         // Apply the 80-20 heuristic to dermine the full year based on 
770         // defaultCenturyStart. 
771         int year = defaultCentury + calendar.get(Calendar.YEAR);
772         calendar.set(Calendar.YEAR, year);
773         if (calendar.getTime().compareTo(defaultCenturyStart) < 0)
774           calendar.set(Calendar.YEAR, year + 100);      
775       }
776
777     try
778       {
779         if (! saw_timezone)
780           {
781             // Use the real rules to determine whether or not this
782             // particular time is in daylight savings.
783             calendar.clear (Calendar.DST_OFFSET);
784             calendar.clear (Calendar.ZONE_OFFSET);
785           }
786         return calendar.getTime();
787       }
788     catch (IllegalArgumentException x)
789       {
790         pos.setErrorIndex(pos.getIndex());
791         return null;
792       }
793   }
794
795   // Compute the start of the current century as defined by
796   // get2DigitYearStart.
797   private void computeCenturyStart()
798   {
799     int year = calendar.get(Calendar.YEAR);
800     calendar.set(Calendar.YEAR, year - 80);
801     set2DigitYearStart(calendar.getTime());
802   }
803 }