KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > google > gwt > i18n > client > DateTimeFormat


1 /*
2  * Copyright 2007 Google Inc.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5  * use this file except in compliance with the License. You may obtain a copy of
6  * the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13  * License for the specific language governing permissions and limitations under
14  * the License.
15  */

16
17 package com.google.gwt.i18n.client;
18
19 import com.google.gwt.core.client.GWT;
20 import com.google.gwt.i18n.client.constants.DateTimeConstants;
21
22 import java.util.ArrayList JavaDoc;
23 import java.util.Date JavaDoc;
24
25 /**
26  * Formats and parses dates and times using locale-sensitive patterns.
27  *
28  * <h3>Patterns</h3>
29  *
30  * <table>
31  * <tr>
32  * <th>Symbol</th>
33  * <th>Meaning</th>
34  * <th>Presentation</th>
35  * <th>Example</th>
36  * </tr>
37  *
38  * <tr>
39  * <td><code>G</code></td>
40  * <td>era designator</td>
41  * <td>Text</td>
42  * <td><code>AD</code></td>
43  * </tr>
44  *
45  * <tr>
46  * <td><code>y</code></td>
47  * <td>year</td>
48  * <td>Number</td>
49  * <td><code>1996</code></td>
50  * </tr>
51  *
52  * <tr>
53  * <td><code>M</code></td>
54  * <td>month in year</td>
55  * <td>Text or Number</td>
56  * <td><code>July (or) 07</code></td>
57  * </tr>
58  *
59  * <tr>
60  * <td><code>d</code></td>
61  * <td>day in month</td>
62  * <td>Number</td>
63  * <td><code>10</code></td>
64  * </tr>
65  *
66  * <tr>
67  * <td><code>h</code></td>
68  * <td>hour in am/pm (1-12)</td>
69  * <td>Number</td>
70  * <td><code>12</code></td>
71  * </tr>
72  *
73  * <tr>
74  * <td><code>H</code></td>
75  * <td>hour in day (0-23)</td>
76  * <td>Number</td>
77  * <td><code>0</code></td>
78  * </tr>
79  *
80  * <tr>
81  * <td><code>m</code></td>
82  * <td>minute in hour</td>
83  * <td>Number</td>
84  * <td><code>30</code></td>
85  * </tr>
86  *
87  * <tr>
88  * <td><code>s</code></td>
89  * <td>second in minute</td>
90  * <td>Number</td>
91  * <td><code>55</code></td>
92  * </tr>
93  *
94  * <tr>
95  * <td><code>S</code></td>
96  * <td>fractional second</td>
97  * <td>Number</td>
98  * <td><code>978</code></td>
99  * </tr>
100  *
101  * <tr>
102  * <td><code>E</code></td>
103  * <td>day of week</td>
104  * <td>Text</td>
105  * <td><code>Tuesday</code></td>
106  * </tr>
107  *
108  * <tr>
109  * <td><code>a</code></td>
110  * <td>am/pm marker</td>
111  * <td>Text</td>
112  * <td><code>PM</code></td>
113  * </tr>
114  *
115  * <tr>
116  * <td><code>k</code></td>
117  * <td>hour in day (1-24)</td>
118  * <td>Number</td>
119  * <td><code>24</code></td>
120  * </tr>
121  *
122  * <tr>
123  * <td><code>K</code></td>
124  * <td>hour in am/pm (0-11)</td>
125  * <td>Number</td>
126  * <td><code>0</code></td>
127  * </tr>
128  *
129  * <tr>
130  * <td><code>z</code></td>
131  * <td>time zone</td>
132  * <td>Text</td>
133  * <td><code>Pacific Standard Time</code></td>
134  * </tr>
135  *
136  * <tr>
137  * <td><code>Z</code></td>
138  * <td>time zone (RFC 822)</td>
139  * <td>Number</td>
140  * <td><code>-0800</code></td>
141  * </tr>
142  *
143  * <tr>
144  * <td><code>v</code></td>
145  * <td>time zone (generic)</td>
146  * <td>Text</td>
147  * <td><code>Pacific Time</code></td>
148  * </tr>
149  *
150  * <tr>
151  * <td><code>'</code></td>
152  * <td>escape for text</td>
153  * <td>Delimiter</td>
154  * <td><code>'Date='</code></td>
155  * </tr>
156  *
157  * <tr>
158  * <td><code>''</code></td>
159  * <td>single quote</td>
160  * <td>Literal</td>
161  * <td><code>'o''clock'</code></td>
162  * </tr>
163  * </table>
164  *
165  * <p>
166  * The number of pattern letters influences the format, as follows:
167  * </p>
168  *
169  * <dl>
170  * <dt>Text</dt>
171  * <dd>if 4 or more, then use the full form; if less than 4, use short or
172  * abbreviated form if it exists (e.g., <code>"EEEE"</code> produces
173  * <code>"Monday"</code>, <code>"EEE"</code> produces <code>"Mon"</code>)</dd>
174  *
175  * <dt>Number</dt>
176  * <dd>the minimum number of digits. Shorter numbers are zero-padded to this
177  * amount (e.g. if <code>"m"</code> produces <code>"6"</code>,
178  * <code>"mm"</code> produces <code>"06"</code>). Year is handled
179  * specially; that is, if the count of 'y' is 2, the Year will be truncated to 2
180  * digits. (e.g., if <code>"yyyy"</code> produces <code>"1997"</code>,
181  * <code>"yy"</code> produces <code>"97"</code>.) Unlike other fields,
182  * fractional seconds are padded on the right with zero.</dd>
183  *
184  * <dt>Text or Number</dt>
185  * <dd>3 or more, use text, otherwise use number. (e.g. <code>"M"</code>
186  * produces <code>"1"</code>, <code>"MM"</code> produces <code>"01"</code>,
187  * <code>"MMM"</code> produces <code>"Jan"</code>, and <code>"MMMM"</code>
188  * produces <code>"January"</code>.</dd>
189  * </dl>
190  *
191  * <p>
192  * Any characters in the pattern that are not in the ranges of ['<code>a</code>'..'<code>z</code>']
193  * and ['<code>A</code>'..'<code>Z</code>'] will be treated as quoted
194  * text. For instance, characters like '<code>:</code>', '<code>.</code>', '<code> </code>'
195  * (space), '<code>#</code>' and '<code>@</code>' will appear in the
196  * resulting time text even they are not embraced within single quotes.
197  * </p>
198  *
199  * <h3>Parsing Dates and Times</h3>
200  * <p>
201  * This implementation could parse partial date/time. Current date/time will be
202  * used to fill in the unavailable part.
203  * </p>
204  *
205  * <p>
206  * As with formatting (described above), the count of pattern letters determine
207  * the parsing behavior.
208  * </p>
209  *
210  * <dl>
211  * <dt>Text</dt>
212  * <dd>4 or more pattern letters--use full form, less than 4--use short or
213  * abbreviated form if one exists. In parsing, we will always try long format,
214  * then short.</dd>
215  *
216  * <dt>Number</dt>
217  * <dd>the minimum number of digits.</dd>
218  *
219  * <dt>Text or Number</dt>
220  * <dd>3 or more characters means use text, otherwise use number</dd>
221  * </dl>
222  *
223  * <p>
224  * Although the current pattern specification doesn't not specify behavior for
225  * all letters, it may in the future. It is strongly discouraged to used
226  * unspecified letters as literal text without being surrounded by quotes.
227  * </p>
228  *
229  * <h3>Examples</h3>
230  * <table>
231  * <tr>
232  * <th>Pattern</th>
233  * <th>Formatted Text</th>
234  * </tr>
235  *
236  * <tr>
237  * <td><code>"yyyy.MM.dd G 'at' HH:mm:ss vvvv"</code></td>
238  * <td><code>1996.07.10 AD at 15:08:56 Pacific Time</code></td>
239  * </tr>
240  *
241  * <tr>
242  * <td><code>"EEE, MMM d, ''yy"</code></td>
243  * <td><code>Wed, July 10, '96</code></td>
244  * </tr>
245  *
246  * <tr>
247  * <td><code>"h:mm a"</code></td>
248  * <td><code>12:08 PM</code></td>
249  * </tr>
250  *
251  * <tr>
252  * <td><code>"hh 'o''clock' a, zzzz"</code></td>
253  * <td><code> 12 o'clock PM, Pacific Daylight Time</code></td>
254  * </tr>
255  *
256  * <tr>
257  * <td><code>"K:mm a, vvv"</code></td>
258  * <td><code> 0:00 PM, PT</code></td>
259  * </tr>
260  *
261  * <tr>
262  * <td><code>"yyyyy.MMMMM.dd GGG hh:mm aaa"</code></td>
263  * <td><code>01996.July.10 AD 12:08 PM</code></td>
264  * </tr>
265  * </table>
266  *
267  * <h3>Additional Parsing Considerations</h3>
268  * <p>
269  * When parsing a date string using the abbreviated year pattern (<code>"yy"</code>),
270  * the parser must interpret the abbreviated year relative to some century. It
271  * does this by adjusting dates to be within 80 years before and 20 years after
272  * the time the parser instance is created. For example, using a pattern of
273  * <code>"MM/dd/yy"</code> and a <code>DateTimeFormat</code> object created
274  * on Jan 1, 1997, the string <code>"01/11/12"</code> would be interpreted as
275  * Jan 11, 2012 while the string <code>"05/04/64"</code> would be interpreted
276  * as May 4, 1964. During parsing, only strings consisting of exactly two
277  * digits, as defined by {@link java.lang.Character#isDigit(char)}, will be
278  * parsed into the default century. If the year pattern does not have exactly
279  * two 'y' characters, the year is interpreted literally, regardless of the
280  * number of digits. For example, using the pattern <code>"MM/dd/yyyy"</code>,
281  * "01/11/12" parses to Jan 11, 12 A.D.
282  * </p>
283  *
284  * <p>
285  * When numeric fields abut one another directly, with no intervening delimiter
286  * characters, they constitute a run of abutting numeric fields. Such runs are
287  * parsed specially. For example, the format "HHmmss" parses the input text
288  * "123456" to 12:34:56, parses the input text "12345" to 1:23:45, and fails to
289  * parse "1234". In other words, the leftmost field of the run is flexible,
290  * while the others keep a fixed width. If the parse fails anywhere in the run,
291  * then the leftmost field is shortened by one character, and the entire run is
292  * parsed again. This is repeated until either the parse succeeds or the
293  * leftmost field is one character in length. If the parse still fails at that
294  * point, the parse of the run fails.
295  * </p>
296  *
297  * <p>
298  * In the current implementation, timezone parsing only supports
299  * <code>GMT:hhmm</code>, <code>GMT:+hhmm</code>, and
300  * <code>GMT:-hhmm</code>.
301  * </p>
302  */

303 public class DateTimeFormat {
304   /**
305    * Class PatternPart holds a "compiled" pattern part.
306    */

307   private class PatternPart {
308     public String JavaDoc text;
309     public int count; // 0 has a special meaning, it stands for literal
310
public boolean abutStart;
311
312     public PatternPart(String JavaDoc txt, int cnt) {
313       text = txt;
314       count = cnt;
315       abutStart = false;
316     }
317   }
318
319   private static final int FULL_DATE_FORMAT = 0;
320   private static final int LONG_DATE_FORMAT = 1;
321   private static final int MEDIUM_DATE_FORMAT = 2;
322   private static final int SHORT_DATE_FORMAT = 3;
323   private static final int FULL_TIME_FORMAT = 0;
324   private static final int LONG_TIME_FORMAT = 1;
325   private static final int MEDIUM_TIME_FORMAT = 2;
326
327   private static final int SHORT_TIME_FORMAT = 3;
328   private static final int NUMBER_BASE = 10;
329   private static final int JS_START_YEAR = 1900;
330
331   private static DateTimeFormat cachedFullDateFormat;
332   private static DateTimeFormat cachedLongDateFormat;
333   private static DateTimeFormat cachedMediumDateFormat;
334
335   private static DateTimeFormat cachedShortDateFormat;
336   private static DateTimeFormat cachedFullTimeFormat;
337   private static DateTimeFormat cachedLongTimeFormat;
338   private static DateTimeFormat cachedMediumTimeFormat;
339
340   private static DateTimeFormat cachedShortTimeFormat;
341   private static DateTimeFormat cachedFullDateTimeFormat;
342   private static DateTimeFormat cachedLongDateTimeFormat;
343   private static DateTimeFormat cachedMediumDateTimeFormat;
344   private static DateTimeFormat cachedShortDateTimeFormat;
345
346   private static final DateTimeConstants defaultDateTimeConstants = (DateTimeConstants) GWT.create(DateTimeConstants.class);
347
348   private static final String JavaDoc PATTERN_CHARS = "GyMdkHmsSEDahKzZv";
349
350   private static final String JavaDoc NUMERIC_FORMAT_CHARS = "MydhHmsSDkK";
351
352   private static final String JavaDoc WHITE_SPACE = " \t\r\n";
353
354   private static final String JavaDoc GMT = "GMT";
355
356   private static final int MINUTES_PER_HOUR = 60;
357
358   /**
359    * Returns a format object using the specified pattern and the date time
360    * constants for the default locale. If you need to format or parse repeatedly
361    * using the same pattern, it is highly recommended that you cache the
362    * returned <code>DateTimeFormat</code> object and reuse it rather than
363    * calling this method repeatedly.
364    *
365    * @param pattern string to specify how the date should be formatted
366    *
367    * @return a <code>DateTimeFormat</code> object that can be used for format
368    * or parse date/time values matching the specified pattern
369    *
370    * @throws IllegalArgumentException if the specified pattern could not be
371    * parsed
372    */

373   public static DateTimeFormat getFormat(String JavaDoc pattern) {
374     return new DateTimeFormat(pattern, defaultDateTimeConstants);
375   }
376
377   public static DateTimeFormat getFullDateFormat() {
378     if (cachedFullDateFormat == null) {
379       String JavaDoc pattern = defaultDateTimeConstants.dateFormats()[FULL_DATE_FORMAT];
380       cachedFullDateFormat = new DateTimeFormat(pattern);
381     }
382     return cachedFullDateFormat;
383   }
384
385   public static DateTimeFormat getFullDateTimeFormat() {
386     if (cachedFullDateTimeFormat == null) {
387       String JavaDoc pattern = defaultDateTimeConstants.dateFormats()[FULL_DATE_FORMAT]
388           + " " + defaultDateTimeConstants.timeFormats()[FULL_TIME_FORMAT];
389       cachedFullDateTimeFormat = new DateTimeFormat(pattern);
390     }
391     return cachedFullDateTimeFormat;
392   }
393
394   public static DateTimeFormat getFullTimeFormat() {
395     if (cachedFullTimeFormat == null) {
396       String JavaDoc pattern = defaultDateTimeConstants.timeFormats()[FULL_TIME_FORMAT];
397       cachedFullTimeFormat = new DateTimeFormat(pattern);
398     }
399     return cachedFullTimeFormat;
400   }
401
402   public static DateTimeFormat getLongDateFormat() {
403     if (cachedLongDateFormat == null) {
404       String JavaDoc pattern = defaultDateTimeConstants.dateFormats()[LONG_DATE_FORMAT];
405       cachedLongDateFormat = new DateTimeFormat(pattern);
406     }
407     return cachedLongDateFormat;
408   }
409
410   public static DateTimeFormat getLongDateTimeFormat() {
411     if (cachedLongDateTimeFormat == null) {
412       String JavaDoc pattern = defaultDateTimeConstants.dateFormats()[LONG_DATE_FORMAT]
413           + " " + defaultDateTimeConstants.timeFormats()[LONG_TIME_FORMAT];
414       cachedLongDateTimeFormat = new DateTimeFormat(pattern);
415     }
416     return cachedLongDateTimeFormat;
417   }
418
419   public static DateTimeFormat getLongTimeFormat() {
420     if (cachedLongTimeFormat == null) {
421       String JavaDoc pattern = defaultDateTimeConstants.timeFormats()[LONG_TIME_FORMAT];
422       cachedLongTimeFormat = new DateTimeFormat(pattern);
423     }
424     return cachedLongTimeFormat;
425   }
426
427   public static DateTimeFormat getMediumDateFormat() {
428     if (cachedMediumDateFormat == null) {
429       String JavaDoc pattern = defaultDateTimeConstants.dateFormats()[MEDIUM_DATE_FORMAT];
430       cachedMediumDateFormat = new DateTimeFormat(pattern);
431     }
432     return cachedMediumDateFormat;
433   };
434
435   public static DateTimeFormat getMediumDateTimeFormat() {
436     if (cachedMediumDateTimeFormat == null) {
437       String JavaDoc pattern = defaultDateTimeConstants.dateFormats()[MEDIUM_DATE_FORMAT]
438           + " " + defaultDateTimeConstants.timeFormats()[MEDIUM_TIME_FORMAT];
439       cachedMediumDateTimeFormat = new DateTimeFormat(pattern);
440     }
441     return cachedMediumDateTimeFormat;
442   }
443
444   public static DateTimeFormat getMediumTimeFormat() {
445     if (cachedMediumTimeFormat == null) {
446       String JavaDoc pattern = defaultDateTimeConstants.timeFormats()[MEDIUM_TIME_FORMAT];
447       cachedMediumTimeFormat = new DateTimeFormat(pattern);
448     }
449     return cachedMediumTimeFormat;
450   }
451
452   public static DateTimeFormat getShortDateFormat() {
453     if (cachedShortDateFormat == null) {
454       String JavaDoc pattern = defaultDateTimeConstants.dateFormats()[SHORT_DATE_FORMAT];
455       cachedShortDateFormat = new DateTimeFormat(pattern);
456     }
457     return cachedShortDateFormat;
458   }
459
460   public static DateTimeFormat getShortDateTimeFormat() {
461     if (cachedShortDateTimeFormat == null) {
462       String JavaDoc pattern = defaultDateTimeConstants.dateFormats()[SHORT_DATE_FORMAT]
463           + " " + defaultDateTimeConstants.timeFormats()[SHORT_TIME_FORMAT];
464       cachedShortDateTimeFormat = new DateTimeFormat(pattern);
465     }
466     return cachedShortDateTimeFormat;
467   }
468
469   public static DateTimeFormat getShortTimeFormat() {
470     if (cachedShortTimeFormat == null) {
471       String JavaDoc pattern = defaultDateTimeConstants.timeFormats()[SHORT_TIME_FORMAT];
472       cachedShortTimeFormat = new DateTimeFormat(pattern);
473     }
474     return cachedShortTimeFormat;
475   }
476
477   private final ArrayList JavaDoc patternParts = new ArrayList JavaDoc();
478
479   private final DateTimeConstants dateTimeConstants;
480
481   private final String JavaDoc pattern;;
482
483   /**
484    * Constructs a format object using the specified pattern and the date time
485    * constants for the default locale.
486    *
487    * @param pattern string pattern specification
488    */

489   protected DateTimeFormat(String JavaDoc pattern) {
490     this(pattern, defaultDateTimeConstants);
491   };
492
493   /**
494    * Constructs a format object using the specified pattern and user-supplied
495    * date time constants.
496    *
497    * @param pattern string pattern specification
498    * @param dateTimeConstants locale specific symbol collection
499    */

500   protected DateTimeFormat(String JavaDoc pattern, DateTimeConstants dateTimeConstants) {
501     this.pattern = pattern;
502     this.dateTimeConstants = dateTimeConstants;
503
504     /*
505      * Even though the pattern is only compiled for use in parsing and parsing
506      * is far less common than formatting, the pattern is still parsed eagerly
507      * here to fail fast in case the pattern itself is malformed.
508      */

509     parsePattern(pattern);
510   };
511
512   /**
513    * Format a date object.
514    *
515    * @param date the date object being formatted
516    *
517    * @return formatted date representation
518    */

519   public String JavaDoc format(Date JavaDoc date) {
520     StringBuffer JavaDoc toAppendTo = new StringBuffer JavaDoc(64);
521     int j, n = pattern.length();
522     for (int i = 0; i < n;) {
523       char ch = pattern.charAt(i);
524       if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')) {
525         // ch is a date-time pattern character to be interpreted by subFormat().
526
// Count the number of times it is repeated.
527
for (j = i + 1; j < n && pattern.charAt(j) == ch; ++j) {
528         }
529         subFormat(toAppendTo, ch, j - i, date);
530         i = j;
531       } else if (ch == '\'') {
532         // Handle an entire quoted string, included embedded
533
// doubled apostrophes (as in 'o''clock').
534

535         // i points after '.
536
++i;
537
538         // If start with '', just add ' and continue.
539
if (i < n && pattern.charAt(i) == '\'') {
540           toAppendTo.append('\'');
541           ++i;
542           continue;
543         }
544
545         // Otherwise add the quoted string.
546
boolean trailQuote = false;
547         while (!trailQuote) {
548           // j points to next ' or EOS.
549
j = i;
550           while (j < n && pattern.charAt(j) != '\'') {
551             ++j;
552           }
553
554           if (j >= n) {
555             // Trailing ' (pathological).
556
throw new IllegalArgumentException JavaDoc("Missing trailing \'");
557           }
558
559           // Look ahead to detect '' within quotes.
560
if (j + 1 < n && pattern.charAt(j + 1) == '\'') {
561             ++j;
562           } else {
563             trailQuote = true;
564           }
565           toAppendTo.append(pattern.substring(i, j));
566           i = j + 1;
567         }
568       } else {
569         // Append unquoted literal characters.
570
toAppendTo.append(ch);
571         ++i;
572       }
573     }
574
575     return toAppendTo.toString();
576   };
577
578   public String JavaDoc getPattern() {
579     return pattern;
580   };
581
582   /**
583    * Parses text to produce a {@link Date} value. An
584    * {@link IllegalArgumentException} is thrown if either the text is empty or
585    * if the parse does not consume all characters of the text.
586    *
587    * @param text the string being parsed
588    * @return a parsed date/time value
589    * @throws IllegalArgumentException if the entire text could not be converted
590    * into a number
591    */

592   public Date JavaDoc parse(String JavaDoc text) {
593     Date JavaDoc date = new Date JavaDoc();
594     int charsConsumed = parse(text, 0, date);
595     if (charsConsumed == 0 || charsConsumed < text.length()) {
596       throw new IllegalArgumentException JavaDoc(text);
597     }
598     return date;
599   }
600
601   /**
602    * This method parses the input string, fill its value into a {@link Date}.
603    *
604    * @param text the string that need to be parsed
605    * @param start the character position in "text" where parsing should start
606    * @param date the date object that will hold parsed value
607    *
608    * @return 0 if parsing failed, otherwise the number of characters advanced
609    */

610   public int parse(String JavaDoc text, int start, Date JavaDoc date) {
611     DateRecord cal = new DateRecord();
612     int[] parsePos = {start};
613
614     // For parsing abutting numeric fields. 'abutPat' is the
615
// offset into 'pattern' of the first of 2 or more abutting
616
// numeric fields. 'abutStart' is the offset into 'text'
617
// where parsing the fields begins. 'abutPass' starts off as 0
618
// and increments each time we try to parse the fields.
619
int abutPat = -1; // If >=0, we are in a run of abutting numeric fields.
620
int abutStart = 0;
621     int abutPass = 0;
622
623     for (int i = 0; i < patternParts.size(); ++i) {
624       PatternPart part = (PatternPart) patternParts.get(i);
625
626       if (part.count > 0) {
627         if (abutPat < 0 && part.abutStart) {
628           abutPat = i;
629           abutStart = start;
630           abutPass = 0;
631         }
632
633         // Handle fields within a run of abutting numeric fields. Take
634
// the pattern "HHmmss" as an example. We will try to parse
635
// 2/2/2 characters of the input text, then if that fails,
636
// 1/2/2. We only adjust the width of the leftmost field; the
637
// others remain fixed. This allows "123456" => 12:34:56, but
638
// "12345" => 1:23:45. Likewise, for the pattern "yyyyMMdd" we
639
// try 4/2/2, 3/2/2, 2/2/2, and finally 1/2/2.
640
if (abutPat >= 0) {
641           // If we are at the start of a run of abutting fields, then
642
// shorten this field in each pass. If we can't shorten
643
// this field any more, then the parse of this set of
644
// abutting numeric fields has failed.
645
int count = part.count;
646           if (i == abutPat) {
647             count -= abutPass++;
648             if (count == 0) {
649               return 0;
650             }
651           }
652
653           if (!subParse(text, parsePos, part, count, cal)) {
654             // If the parse fails anywhere in the run, back up to the
655
// start of the run and retry.
656
i = abutPat - 1;
657             parsePos[0] = abutStart;
658             continue;
659           }
660         } else {
661           // Handle non-numeric fields and non-abutting numeric fields.
662
abutPat = -1;
663           if (!subParse(text, parsePos, part, 0, cal)) {
664             return 0;
665           }
666         }
667       } else {
668         // Handle literal pattern characters. These are any
669
// quoted characters and non-alphabetic unquoted characters.
670
abutPat = -1;
671         // A run of white space in the pattern matches a run
672
// of white space in the input text.
673
if (part.text.charAt(0) == ' ') {
674           // Advance over run in input text.
675
int s = parsePos[0];
676           skipSpace(text, parsePos);
677
678           // Must see at least one white space char in input.
679
if (parsePos[0] > s) {
680             continue;
681           }
682         } else if (text.startsWith(part.text, parsePos[0])) {
683           parsePos[0] += part.text.length();
684           continue;
685         }
686
687         // We fall through to this point if the match fails.
688
return 0;
689       }
690     }
691
692     if (!cal.calcDate(date)) {
693       return 0;
694     }
695
696     // Return progress.
697
return parsePos[0] - start;
698   };
699
700   /**
701    * Method append current content in buf as pattern part if there is any, and
702    * clear buf for next part.
703    *
704    * @param buf pattern part text specification
705    * @param count pattern part repeat count
706    */

707   private void addPart(StringBuffer JavaDoc buf, int count) {
708     if (buf.length() > 0) {
709       patternParts.add((new PatternPart(buf.toString(), count)));
710       buf.setLength(0);
711     }
712   };
713
714   /**
715    * Generate GMT timezone string for given date.
716    *
717    * @param buf where timezone string will be appended to
718    * @param date whose value being evaluated
719    */

720   private void appendGMT(StringBuffer JavaDoc buf, Date JavaDoc date) {
721     int value = -date.getTimezoneOffset();
722
723     if (value < 0) {
724       buf.append("GMT-");
725       value = -value; // suppress the '-' sign for text display.
726
} else {
727       buf.append("GMT+");
728     }
729
730     zeroPaddingNumber(buf, value / MINUTES_PER_HOUR, 2);
731     buf.append(':');
732     zeroPaddingNumber(buf, value % MINUTES_PER_HOUR, 2);
733   };
734
735   /**
736    * Formats (0..11) Hours field according to pattern specified.
737    *
738    * @param buf where formatted string will be appended to
739    * @param count number of time pattern char repeats; this controls how a field
740    * should be formatted
741    * @param date hold the date object to be formatted
742    */

743   private void format0To11Hours(StringBuffer JavaDoc buf, int count, Date JavaDoc date) {
744     int value = date.getHours() % 12;
745     zeroPaddingNumber(buf, value, count);
746   };
747
748   /**
749    * Formats (0..23) Hours field according to pattern specified.
750    *
751    * @param buf where formatted string will be appended to
752    * @param count number of time pattern char repeats; this controls how a field
753    * should be formatted
754    * @param date hold the date object to be formatted
755    */

756   private void format0To23Hours(StringBuffer JavaDoc buf, int count, Date JavaDoc date) {
757     int value = date.getHours();
758     zeroPaddingNumber(buf, value, count);
759   };
760
761   /**
762    * Formats (1..12) Hours field according to pattern specified.
763    *
764    * @param buf where formatted string will be appended to
765    * @param count number of time pattern char repeats; this controls how a field
766    * should be formatted
767    * @param date hold the date object to be formatted
768    */

769   private void format1To12Hours(StringBuffer JavaDoc buf, int count, Date JavaDoc date) {
770     int value = date.getHours() % 12;
771     if (value == 0) {
772       zeroPaddingNumber(buf, 12, count);
773     } else {
774       zeroPaddingNumber(buf, value, count);
775     }
776   };
777
778   /**
779    * Formats (1..24) Hours field according to pattern specified.
780    *
781    * @param buf where formatted string will be appended to
782    * @param count number of time pattern char repeats; this controls how a field
783    * should be formatted
784    * @param date hold the date object to be formatted
785    */

786   private void format24Hours(StringBuffer JavaDoc buf, int count, Date JavaDoc date) {
787     int value = date.getHours();
788     if (value == 0) {
789       zeroPaddingNumber(buf, 24, count);
790     } else {
791       zeroPaddingNumber(buf, value, count);
792     }
793   }
794
795   /**
796    * Formats AM/PM field according to pattern specified.
797    *
798    * @param buf where formatted string will be appended to
799    * @param count number of time pattern char repeats; this controls how a field
800    * should be formatted
801    * @param date hold the date object to be formatted
802    */

803   private void formatAmPm(StringBuffer JavaDoc buf, int count, Date JavaDoc date) {
804     if (date.getHours() >= 12 && date.getHours() < 24) {
805       buf.append(dateTimeConstants.ampms()[1]);
806     } else {
807       buf.append(dateTimeConstants.ampms()[0]);
808     }
809   };
810
811   /**
812    * Formats Date field according to pattern specified.
813    *
814    * @param buf where formatted string will be appended to
815    * @param count number of time pattern char repeats; this controls how a field
816    * should be formatted
817    * @param date hold the date object to be formatted
818    */

819   private void formatDate(StringBuffer JavaDoc buf, int count, Date JavaDoc date) {
820     int value = date.getDate();
821     zeroPaddingNumber(buf, value, count);
822   };
823
824   /**
825    * Formats Day of week field according to pattern specified.
826    *
827    * @param buf where formatted string will be appended to
828    * @param count number of time pattern char repeats; this controls how a field
829    * should be formatted
830    * @param date hold the date object to be formatted
831    */

832   private void formatDayOfWeek(StringBuffer JavaDoc buf, int count, Date JavaDoc date) {
833     int value = date.getDay();
834     if (count >= 4) {
835       buf.append(dateTimeConstants.weekdays()[value]);
836     } else {
837       buf.append(dateTimeConstants.shortWeekdays()[value]);
838     }
839   }
840
841   /**
842    * Formats Era field according to pattern specified.
843    *
844    * @param buf where formatted string will be appended to
845    * @param count number of time pattern char repeats; this controls how a field
846    * should be formatted
847    * @param date hold the date object to be formatted
848    */

849   private void formatEra(StringBuffer JavaDoc buf, int count, Date JavaDoc date) {
850     int value = date.getYear() >= -JS_START_YEAR ? 1 : 0;
851     if (count >= 4) {
852       buf.append(dateTimeConstants.eraNames()[value]);
853     } else {
854       buf.append(dateTimeConstants.eras()[value]);
855     }
856   }
857
858   /**
859    * Formats Fractional seconds field according to pattern specified.
860    *
861    * @param buf where formatted string will be appended to
862    * @param count number of time pattern char repeats; this controls how a field
863    * should be formatted
864    * @param date hold the date object to be formatted
865    */

866   private void formatFractionalSeconds(StringBuffer JavaDoc buf, int count, Date JavaDoc date) {
867     // Fractional seconds should be left-justified, ie. zero must be padded
868
// from left. For example, if value in milliseconds is 5, and count is 3,
869
// the output need to be "005".
870
int value = (int) (date.getTime() % 1000);
871     if (count == 1) {
872       value = (value + 50) / 100; // Round to 100ms.
873
buf.append(Integer.toString(value));
874     } else if (count == 2) {
875       value = (value + 5) / 10; // Round to 10ms.
876
zeroPaddingNumber(buf, value, 2);
877     } else {
878       zeroPaddingNumber(buf, value, 3);
879
880       if (count > 3) {
881         zeroPaddingNumber(buf, 0, count - 3);
882       }
883     }
884   }
885
886   /**
887    * Formats Minutes field according to pattern specified.
888    *
889    * @param buf where formatted string will be appended to
890    * @param count number of time pattern char repeats; this controls how a field
891    * should be formatted
892    * @param date hold the date object to be formatted
893    */

894   private void formatMinutes(StringBuffer JavaDoc buf, int count, Date JavaDoc date) {
895     int value = date.getMinutes();
896     zeroPaddingNumber(buf, value, count);
897   }
898
899   /**
900    * Formats Month field according to pattern specified.
901    *
902    * @param buf where formatted string will be appended to
903    * @param count number of time pattern char repeats; this controls how a field
904    * should be formatted
905    * @param date hold the date object to be formatted
906    */

907   private void formatMonth(StringBuffer JavaDoc buf, int count, Date JavaDoc date) {
908     int value = date.getMonth();
909     switch (count) {
910       case 5:
911         buf.append(dateTimeConstants.narrowMonths()[value]);
912         break;
913       case 4:
914         buf.append(dateTimeConstants.standaloneMonths()[value]);
915         break;
916       case 3:
917         buf.append(dateTimeConstants.shortMonths()[value]);
918         break;
919       default:
920         zeroPaddingNumber(buf, value + 1, count);
921     }
922   }
923
924   /**
925    * Formats Quarter field according to pattern specified.
926    *
927    * @param buf where formatted string will be appended to
928    * @param count number of time pattern char repeats; this controls how a field
929    * should be formatted
930    * @param date hold the date object to be formatted
931    */

932   private void formatQuarter(StringBuffer JavaDoc buf, int count, Date JavaDoc date) {
933     int value = date.getMonth() / 3;
934     if (count < 4) {
935       buf.append(dateTimeConstants.shortQuarters()[value]);
936     } else {
937       buf.append(dateTimeConstants.quarters()[value]);
938     }
939   }
940
941   /**
942    * Formats Seconds field according to pattern specified.
943    *
944    * @param buf where formatted string will be appended to
945    * @param count number of time pattern char repeats; this controls how a field
946    * should be formatted
947    * @param date hold the date object to be formatted
948    */

949   private void formatSeconds(StringBuffer JavaDoc buf, int count, Date JavaDoc date) {
950     int value = date.getSeconds();
951     zeroPaddingNumber(buf, value, count);
952   }
953
954   /**
955    * Formats Standalone weekday field according to pattern specified.
956    *
957    * @param buf where formatted string will be appended to
958    * @param count number of time pattern char repeats; this controls how a field
959    * should be formatted
960    * @param date hold the date object to be formatted
961    */

962   private void formatStandaloneDay(StringBuffer JavaDoc buf, int count, Date JavaDoc date) {
963     int value = date.getDay();
964     if (count == 5) {
965       buf.append(dateTimeConstants.standaloneNarrowWeekdays()[value]);
966     } else if (count == 4) {
967       buf.append(dateTimeConstants.standaloneWeekdays()[value]);
968     } else if (count == 3) {
969       buf.append(dateTimeConstants.standaloneShortWeekdays()[value]);
970     } else {
971       zeroPaddingNumber(buf, value, 1);
972     }
973   }
974
975   /**
976    * Formats Standalone Month field according to pattern specified.
977    *
978    * @param buf where formatted string will be appended to
979    * @param count number of time pattern char repeats; this controls how a field
980    * should be formatted
981    * @param date hold the date object to be formatted
982    */

983   private void formatStandaloneMonth(StringBuffer JavaDoc buf, int count, Date JavaDoc date) {
984     int value = date.getMonth();
985     if (count == 5) {
986       buf.append(dateTimeConstants.standaloneNarrowMonths()[value]);
987     } else if (count == 4) {
988       buf.append(dateTimeConstants.standaloneMonths()[value]);
989     } else if (count == 3) {
990       buf.append(dateTimeConstants.standaloneShortMonths()[value]);
991     } else {
992       zeroPaddingNumber(buf, value + 1, count);
993     }
994   }
995
996   /**
997    * Formats Timezone field following RFC.
998    *
999    * @param buf where formatted string will be appended to
1000   * @param count number of time pattern char repeats; this controls how a field
1001   * should be formatted
1002   * @param date hold the date object to be formatted
1003   */

1004  private void formatTimeZoneRFC(StringBuffer JavaDoc buf, int count, Date JavaDoc date) {
1005    if (count < 4) {
1006      // 'short' (standard Java) form, must use ASCII digits
1007
int val = date.getTimezoneOffset();
1008      char sign = '-';
1009      if (val < 0) {
1010        val = -val;
1011        sign = '+';
1012      }
1013
1014      val = (val / 3) * 5 + (val % MINUTES_PER_HOUR); // minutes => KKmm
1015
buf.append(sign);
1016      zeroPaddingNumber(buf, val, 4);
1017    } else {
1018      appendGMT(buf, date);
1019    }
1020  }
1021
1022  /**
1023   * Formats Year field according to pattern specified. Javascript Date object
1024   * seems incapable handling 1BC and year before. It can show you year 0 which
1025   * does not exists. following we just keep consistent with javascript's
1026   * toString method. But keep in mind those things should be unsupported.
1027   *
1028   * @param buf where formatted string will be appended to
1029   * @param count number of time pattern char repeats; this controls how a field
1030   * should be formatted
1031   * @param date hold the date object to be formatted
1032   */

1033  private void formatYear(StringBuffer JavaDoc buf, int count, Date JavaDoc date) {
1034    int value = date.getYear() + JS_START_YEAR;
1035    if (value < 0) {
1036      value = -value;
1037    }
1038    if (count == 2) {
1039      zeroPaddingNumber(buf, value % 100, 2);
1040    } else {
1041      // count != 2
1042
buf.append(Integer.toString(value));
1043    }
1044  }
1045
1046  /**
1047   * Method getNextCharCountInPattern calculate character repeat count in
1048   * pattern.
1049   *
1050   * @param pattern describe the format of date string that need to be parsed
1051   * @param start the position of pattern character
1052   * @return repeat count
1053   */

1054  private int getNextCharCountInPattern(String JavaDoc pattern, int start) {
1055    char ch = pattern.charAt(start);
1056    int next = start + 1;
1057    while (next < pattern.length() && pattern.charAt(next) == ch) {
1058      ++next;
1059    }
1060    return next - start;
1061  }
1062
1063  /**
1064   * Method identifies the start of a run of abutting numeric fields. Take the
1065   * pattern "HHmmss" as an example. We will try to parse 2/2/2 characters of
1066   * the input text, then if that fails, 1/2/2. We only adjust the width of the
1067   * leftmost field; the others remain fixed. This allows "123456" => 12:34:56,
1068   * but "12345" => 1:23:45. Likewise, for the pattern "yyyyMMdd" we try 4/2/2,
1069   * 3/2/2, 2/2/2, and finally 1/2/2. The first field of connected numeric
1070   * fields will be marked as abutStart, its width can be reduced to accomodate
1071   * others.
1072   */

1073  private void identifyAbutStart() {
1074    // 'abut' parts are continuous numeric parts. abutStart is the switch
1075
// point from non-abut to abut.
1076
boolean abut = false;
1077
1078    int len = patternParts.size();
1079    for (int i = 0; i < len; i++) {
1080      if (isNumeric((PatternPart) patternParts.get(i))) {
1081        // If next part is not following abut sequence, and isNumeric.
1082
if (!abut && i + 1 < len
1083            && isNumeric((PatternPart) patternParts.get(i + 1))) {
1084          abut = true;
1085          ((PatternPart) patternParts.get(i)).abutStart = true;
1086        }
1087      } else {
1088        abut = false;
1089      }
1090    }
1091  }
1092
1093  /**
1094   * Method checks if the pattern part is a numeric field.
1095   *
1096   * @param part pattern part to be examined
1097   * @return <code>true</code> if the pattern part is numberic field
1098   */

1099  private final boolean isNumeric(PatternPart part) {
1100    if (part.count <= 0) {
1101      return false;
1102    }
1103    int i = NUMERIC_FORMAT_CHARS.indexOf(part.text.charAt(0));
1104    return (i > 0 || (i == 0 && part.count < 3));
1105  }
1106
1107  /**
1108   * Method attempts to match the text at a given position against an array of
1109   * strings. Since multiple strings in the array may match (for example, if the
1110   * array contains "a", "ab", and "abc", all will match the input string
1111   * "abcd") the longest match is returned.
1112   *
1113   * @param text the time text being parsed
1114   * @param start where to start parsing
1115   * @param data the string array to parsed
1116   * @param pos to receive where the match stopped
1117   * @return the new start position if matching succeeded; a negative number
1118   * indicating matching failure
1119   */

1120  private int matchString(String JavaDoc text, int start, String JavaDoc[] data, int[] pos) {
1121    int count = data.length;
1122
1123    // There may be multiple strings in the data[] array which begin with
1124
// the same prefix (e.g., Cerven and Cervenec (June and July) in Czech).
1125
// We keep track of the longest match, and return that. Note that this
1126
// unfortunately requires us to test all array elements.
1127
int bestMatchLength = 0, bestMatch = -1;
1128    String JavaDoc textInLowerCase = text.substring(start).toLowerCase();
1129    for (int i = 0; i < count; ++i) {
1130      int length = data[i].length();
1131      // Always compare if we have no match yet; otherwise only compare
1132
// against potentially better matches (longer strings).
1133
if (length > bestMatchLength
1134          && textInLowerCase.startsWith(data[i].toLowerCase())) {
1135        bestMatch = i;
1136        bestMatchLength = length;
1137      }
1138    }
1139    if (bestMatch >= 0) {
1140      pos[0] = start + bestMatchLength;
1141    }
1142    return bestMatch;
1143  }
1144
1145  /**
1146   * Method parses a integer string and return integer value.
1147   *
1148   * @param text string being parsed
1149   * @param pos parse position
1150   *
1151   * @return integer value
1152   */

1153  private int parseInt(String JavaDoc text, int[] pos) {
1154    int ret = 0;
1155    int ind = pos[0];
1156    char ch = text.charAt(ind);
1157    while (ch >= '0' && ch <= '9') {
1158      ret = ret * 10 + (ch - '0');
1159      ind++;
1160      if (ind >= text.length()) {
1161        break;
1162      }
1163      ch = text.charAt(ind);
1164    }
1165    if (ind > pos[0]) {
1166      pos[0] = ind;
1167    } else {
1168      ret = -1;
1169    }
1170    return ret;
1171  }
1172
1173  /**
1174   * Method parses the input pattern string a generate a vector of pattern
1175   * parts.
1176   *
1177   * @param pattern describe the format of date string that need to be parsed
1178   */

1179  private void parsePattern(String JavaDoc pattern) {
1180    StringBuffer JavaDoc buf = new StringBuffer JavaDoc(32);
1181    boolean inQuote = false;
1182
1183    for (int i = 0; i < pattern.length(); i++) {
1184      char ch = pattern.charAt(i);
1185
1186      // Handle space, add literal part (if exist), and add space part.
1187
if (ch == ' ') {
1188        addPart(buf, 0);
1189        buf.append(' ');
1190        addPart(buf, 0);
1191        while (i + 1 < pattern.length() && pattern.charAt(i + 1) == ' ') {
1192          i++;
1193        }
1194        continue;
1195      }
1196
1197      // If inside quote, except two quote connected, just copy or exit.
1198
if (inQuote) {
1199        if (ch == '\'') {
1200          if (i + 1 < pattern.length() && pattern.charAt(i + 1) == '\'') {
1201            // Quote appeared twice continuously, interpret as one quote.
1202
buf.append(ch);
1203            ++i;
1204          } else {
1205            inQuote = false;
1206          }
1207        } else {
1208          // Literal.
1209
buf.append(ch);
1210        }
1211        continue;
1212      }
1213
1214      // Outside quote now.
1215
if (PATTERN_CHARS.indexOf(ch) > 0) {
1216        addPart(buf, 0);
1217        buf.append(ch);
1218        int count = getNextCharCountInPattern(pattern, i);
1219        addPart(buf, count);
1220        i += count - 1;
1221        continue;
1222      }
1223
1224      // Two consecutive quotes is a quote literal, inside or outside of quotes.
1225
if (ch == '\'') {
1226        if (i + 1 < pattern.length() && pattern.charAt(i + 1) == '\'') {
1227          buf.append('\'');
1228          i++;
1229        } else {
1230          inQuote = true;
1231        }
1232      } else {
1233        buf.append(ch);
1234      }
1235    }
1236
1237    addPart(buf, 0);
1238
1239    identifyAbutStart();
1240  }
1241
1242  /**
1243   * Method parses time zone offset.
1244   *
1245   * @param text the time text to be parsed
1246   * @param pos Parse position
1247   * @param cal DateRecord object that holds parsed value
1248   *
1249   * @return <code>true</code> if parsing successful, otherwise
1250   * <code>false</code>
1251   */

1252  private boolean parseTimeZoneOffset(String JavaDoc text, int[] pos, DateRecord cal) {
1253    if (pos[0] >= text.length()) {
1254      cal.setTzOffset(0);
1255      return true;
1256    }
1257
1258    int sign;
1259    switch (text.charAt(pos[0])) {
1260      case '+':
1261        sign = 1;
1262        break;
1263      case '-':
1264        sign = -1;
1265        break;
1266      default:
1267        cal.setTzOffset(0);
1268        return true;
1269    }
1270    ++(pos[0]);
1271
1272    // Look for hours:minutes or hhmm.
1273
int st = pos[0];
1274    int value = parseInt(text, pos);
1275    if (value == 0 && pos[0] == st) {
1276      return false;
1277    }
1278
1279    int offset;
1280    if (pos[0] < text.length() && text.charAt(pos[0]) == ':') {
1281      // This is the hours:minutes case.
1282
offset = value * MINUTES_PER_HOUR;
1283      ++(pos[0]);
1284      st = pos[0];
1285      value = parseInt(text, pos);
1286      if (value == 0 && pos[0] == st) {
1287        return false;
1288      }
1289      offset += value;
1290    } else {
1291      // This is the hhmm case.
1292
offset = value;
1293      // Assume "-23".."+23" refers to hours.
1294
if (offset < 24 && (pos[0] - st) <= 2) {
1295        offset *= MINUTES_PER_HOUR;
1296      } else {
1297        offset = offset % 100 + offset / 100 * MINUTES_PER_HOUR;
1298      }
1299    }
1300
1301    offset *= sign;
1302    cal.setTzOffset(-offset);
1303    return true;
1304  }
1305
1306  /**
1307   * Method skips space in the string as pointed by pos.
1308   *
1309   * @param text input string
1310   * @param pos where skip start, and return back where skip stop
1311   */

1312  private void skipSpace(String JavaDoc text, int[] pos) {
1313    while (pos[0] < text.length()
1314        && WHITE_SPACE.indexOf(text.charAt(pos[0])) >= 0) {
1315      ++(pos[0]);
1316    }
1317  }
1318
1319  /**
1320   * Formats a single field according to pattern specified.
1321   *
1322   * @param buf where formatted string will be appended to
1323   * @param ch pattern for this field
1324   * @param count number of time pattern char repeats; this controls how a field
1325   * should be formatted
1326   * @param date hold the date object to be formatted
1327   *
1328   * @return <code>true</code> if pattern valid, otherwise <code>false</code>
1329   */

1330  private boolean subFormat(StringBuffer JavaDoc buf, char ch, int count, Date JavaDoc date) {
1331    switch (ch) {
1332      case 'G':
1333        formatEra(buf, count, date);
1334        break;
1335      case 'y':
1336        formatYear(buf, count, date);
1337        break;
1338      case 'M':
1339        formatMonth(buf, count, date);
1340        break;
1341      case 'k':
1342        format24Hours(buf, count, date);
1343        break;
1344      case 'S':
1345        formatFractionalSeconds(buf, count, date);
1346        break;
1347      case 'E':
1348        formatDayOfWeek(buf, count, date);
1349        break;
1350      case 'a':
1351        formatAmPm(buf, count, date);
1352        break;
1353      case 'h':
1354        format1To12Hours(buf, count, date);
1355        break;
1356      case 'K':
1357        format0To11Hours(buf, count, date);
1358        break;
1359      case 'H':
1360        format0To23Hours(buf, count, date);
1361        break;
1362      case 'c':
1363        formatStandaloneDay(buf, count, date);
1364        break;
1365      case 'L':
1366        formatStandaloneMonth(buf, count, date);
1367        break;
1368      case 'Q':
1369        formatQuarter(buf, count, date);
1370        break;
1371      case 'd':
1372        formatDate(buf, count, date);
1373        break;
1374      case 'm':
1375        formatMinutes(buf, count, date);
1376        break;
1377      case 's':
1378        formatSeconds(buf, count, date);
1379        break;
1380      case 'z':
1381      case 'v':
1382        appendGMT(buf, date);
1383        break;
1384      case 'Z':
1385        formatTimeZoneRFC(buf, count, date);
1386        break;
1387      default:
1388        return false;
1389    }
1390    return true;
1391  }
1392
1393  /**
1394   * Converts one field of the input string into a numeric field value. Returns
1395   * <code>false</code> if failed.
1396   *
1397   * @param text the time text to be parsed
1398   * @param pos Parse position
1399   * @param part the pattern part for this field
1400   * @param digitCount when greater than 0, numeric parsing must obey the count
1401   * @param cal DateRecord object that will hold parsed value
1402   *
1403   * @return <code>true</code> if parsing successful
1404   */

1405  private boolean subParse(String JavaDoc text, int[] pos, PatternPart part,
1406      int digitCount, DateRecord cal) {
1407
1408    skipSpace(text, pos);
1409
1410    int start = pos[0];
1411    char ch = part.text.charAt(0);
1412
1413    // Parse integer value if it is a numeric field.
1414
int value = -1; // initialize value to be -1,
1415
if (isNumeric(part)) {
1416      if (digitCount > 0) {
1417        if ((start + digitCount) > text.length()) {
1418          return false;
1419        }
1420        value = parseInt(text.substring(0, start + digitCount), pos);
1421      } else {
1422        value = parseInt(text, pos);
1423      }
1424    }
1425
1426    switch (ch) {
1427      case 'G': // 'G' - ERA
1428
value = matchString(text, start, dateTimeConstants.eras(), pos);
1429        cal.setEra(value);
1430        return true;
1431      case 'M': // 'M' - MONTH
1432
return subParseMonth(text, pos, cal, value, start);
1433      case 'E':
1434        return subParseDayOfWeek(text, pos, start, cal);
1435      case 'a': // 'a' - AM_PM
1436
value = matchString(text, start, dateTimeConstants.ampms(), pos);
1437        cal.setAmpm(value);
1438        return true;
1439      case 'y': // 'y' - YEAR
1440
return subParseYear(text, pos, start, value, part, cal);
1441      case 'd': // 'd' - DATE
1442
cal.setDayOfMonth(value);
1443        return true;
1444      case 'S': // 'S' - FRACTIONAL_SECOND
1445
return subParseFractionalSeconds(value, start, pos[0], cal);
1446      case 'h': // 'h' - HOUR (1..12)
1447
if (value == 12) {
1448          value = 0;
1449        }
1450        // fall through
1451
case 'K': // 'K' - HOUR (0..11)
1452
case 'H': // 'H' - HOUR_OF_DAY (0..23)
1453
cal.setHours(value);
1454        return true;
1455      case 'k': // 'k' - HOUR_OF_DAY (1..24)
1456
cal.setHours(value);
1457        return true;
1458      case 'm': // 'm' - MINUTE
1459
cal.setMinutes(value);
1460        return true;
1461      case 's': // 's' - SECOND
1462
cal.setSeconds(value);
1463        return true;
1464
1465      case 'z': // 'z' - ZONE_OFFSET
1466
case 'Z': // 'Z' - TIMEZONE_RFC
1467
case 'v': // 'v' - TIMEZONE_GENERIC
1468
return subParseTimeZoneInGMT(text, start, pos, cal);
1469      default:
1470        return false;
1471    }
1472  }
1473
1474  /**
1475   * Method subParseDayOfWeek parses day of the week field.
1476   *
1477   * @param text the time text to be parsed
1478   * @param pos Parse position
1479   * @param start from where parse start
1480   * @param cal DateRecord object that holds parsed value
1481   *
1482   * @return <code>true</code> if parsing successful, otherwise
1483   * <code>false</code>
1484   */

1485  private boolean subParseDayOfWeek(String JavaDoc text, int[] pos, int start,
1486      DateRecord cal) {
1487    int value;
1488    // 'E' - DAY_OF_WEEK
1489
// Want to be able to parse both short and long forms.
1490
// Try count == 4 (DDDD) first:
1491
value = matchString(text, start, dateTimeConstants.weekdays(), pos);
1492    if (value < 0) {
1493      value = matchString(text, start, dateTimeConstants.shortWeekdays(), pos);
1494    }
1495    if (value < 0) {
1496      return false;
1497    }
1498    cal.setDayOfWeek(value);
1499    return true;
1500  }
1501
1502  /**
1503   * Method subParseFractionalSeconds parses fractional seconds field.
1504   *
1505   * @param value parsed numberic value
1506   * @param start
1507   * @param end parse position
1508   * @param cal DateRecord object that holds parsed value
1509   * @return <code>true</code> if parsing successful, otherwise
1510   * <code>false</code>
1511   */

1512  private boolean subParseFractionalSeconds(int value, int start, int end,
1513      DateRecord cal) {
1514    // Fractional seconds left-justify.
1515
int i = end - start;
1516    if (i < 3) {
1517      while (i < 3) {
1518        value *= 10;
1519        i++;
1520      }
1521    } else {
1522      int a = 1;
1523      while (i > 3) {
1524        a *= 10;
1525        i--;
1526      }
1527      value = (value + (a >> 1)) / a;
1528    }
1529    cal.setMilliseconds(value);
1530    return true;
1531  }
1532
1533  /**
1534   * Method subParseMonth parses Month field.
1535   *
1536   * @param text the time text to be parsed
1537   * @param pos Parse position
1538   * @param cal DateRecord object that will hold parsed value
1539   * @param value numeric value if this field is expressed using numberic
1540   * pattern
1541   * @param start from where parse start
1542   *
1543   * @return <code>true</code> if parsing successful
1544   */

1545  private boolean subParseMonth(String JavaDoc text, int[] pos, DateRecord cal,
1546      int value, int start) {
1547    // When month is symbols, i.e., MMM or MMMM, value will be -1.
1548
if (value < 0) {
1549      // Want to be able to parse both short and long forms.
1550
// Try count == 4 first:
1551
value = matchString(text, start, dateTimeConstants.months(), pos);
1552      if (value < 0) { // count == 4 failed, now try count == 3.
1553
value = matchString(text, start, dateTimeConstants.shortMonths(), pos);
1554      }
1555      if (value < 0) {
1556        return false;
1557      }
1558      cal.setMonth(value);
1559      return true;
1560    } else {
1561      cal.setMonth(value - 1);
1562      return true;
1563    }
1564  }
1565
1566  /**
1567   * Method parses GMT type timezone.
1568   *
1569   * @param text the time text to be parsed
1570   * @param start from where parse start
1571   * @param pos Parse position
1572   * @param cal DateRecord object that holds parsed value
1573   *
1574   * @return <code>true</code> if parsing successful, otherwise
1575   * <code>false</code>
1576   */

1577  private boolean subParseTimeZoneInGMT(String JavaDoc text, int start, int[] pos,
1578      DateRecord cal) {
1579    // First try to parse generic forms such as GMT-07:00. Do this first
1580
// in case localized DateFormatZoneData contains the string "GMT"
1581
// for a zone; in that case, we don't want to match the first three
1582
// characters of GMT+/-HH:MM etc.
1583

1584    // For time zones that have no known names, look for strings
1585
// of the form:
1586
// GMT[+-]hours:minutes or
1587
// GMT[+-]hhmm or
1588
// GMT.
1589
if (text.startsWith(GMT, start)) {
1590      pos[0] = start + GMT.length();
1591      return parseTimeZoneOffset(text, pos, cal);
1592    }
1593
1594    // At this point, check for named time zones by looking through
1595
// the locale data from the DateFormatZoneData strings.
1596
// Want to be able to parse both short and long forms.
1597
/*
1598     * i = subParseZoneString(text, start, cal); if (i != 0) return i;
1599     */

1600
1601    // As a last resort, look for numeric timezones of the form
1602
// [+-]hhmm as specified by RFC 822. This code is actually
1603
// a little more permissive than RFC 822. It will try to do
1604
// its best with numbers that aren't strictly 4 digits long.
1605
return parseTimeZoneOffset(text, pos, cal);
1606  }
1607
1608  /**
1609   * Method subParseYear parse year field. Year field is special because 1, two
1610   * digit year need to be resolved. 2, we allow year to take a sign. 3, year
1611   * field participate in abut processing. In my testing, negative year does not
1612   * seem working due to JDK (or redpill implementation) limitation. It is not a
1613   * big deal so we don't worry about it. But keep the logic here so that we
1614   * might want to replace DateRecord with our a calendar class.
1615   *
1616   * @param text the time text to be parsed
1617   * @param pos parse position
1618   * @param start where this field star
1619   * @param value integer value of yea
1620   * @param part the pattern part for this field
1621   * @param cal DateRecord object that will hold parsed value
1622   *
1623   * @return <code>true</code> if successful
1624   */

1625  private boolean subParseYear(String JavaDoc text, int[] pos, int start, int value,
1626      PatternPart part, DateRecord cal) {
1627    char ch = ' ';
1628    if (value < 0) {
1629      ch = text.charAt(pos[0]);
1630      // Check if it is a sign.
1631
if (ch != '+' && ch != '-') {
1632        return false;
1633      }
1634      ++(pos[0]);
1635      value = parseInt(text, pos);
1636      if (value < 0) {
1637        return false;
1638      }
1639      if (ch == '-') {
1640        value = -value;
1641      }
1642    }
1643
1644    // no sign, only 2 digit was actually parsed, pattern say it has 2 digit.
1645
if (ch == ' ' && (pos[0] - start) == 2 && part.count == 2) {
1646      // Assume for example that the defaultCenturyStart is 6/18/1903.
1647
// This means that two-digit years will be forced into the range
1648
// 6/18/1903 to 6/17/2003. As a result, years 00, 01, and 02
1649
// correspond to 2000, 2001, and 2002. Years 04, 05, etc. correspond
1650
// to 1904, 1905, etc. If the year is 03, then it is 2003 if the
1651
// other fields specify a date before 6/18, or 1903 if they specify a
1652
// date afterwards. As a result, 03 is an ambiguous year. All other
1653
// two-digit years are unambiguous.
1654
Date JavaDoc date = new Date JavaDoc();
1655      int defaultCenturyStartYear = date.getYear() + 1900 - 80;
1656      int ambiguousTwoDigitYear = defaultCenturyStartYear % 100;
1657      cal.setAmbiguousYear(value == ambiguousTwoDigitYear);
1658      value += (defaultCenturyStartYear / 100) * 100
1659          + (value < ambiguousTwoDigitYear ? 100 : 0);
1660    }
1661    cal.setYear(value);
1662    return true;
1663  };
1664
1665  /**
1666   * Formats a number with the specified minimum number of digits, using zero to
1667   * fill the gap.
1668   *
1669   * @param buf where zero padded string will be written to
1670   * @param value the number value being formatted
1671   * @param minWidth minimum width of the formatted string; zero will be padded
1672   * to reach this width
1673   */

1674  private void zeroPaddingNumber(StringBuffer JavaDoc buf, int value, int minWidth) {
1675    int b = NUMBER_BASE;
1676    for (int i = 0; i < minWidth - 1; i++) {
1677      if (value < b) {
1678        buf.append('0');
1679      }
1680      b *= NUMBER_BASE;
1681    }
1682    buf.append(Integer.toString(value));
1683  }
1684}
1685
Popular Tags