KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > joda > time > format > DateTimeFormatterBuilder


1 /*
2  * Copyright 2001-2005 Stephen Colebourne
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of 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,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */

16 package org.joda.time.format;
17
18 import java.io.IOException JavaDoc;
19 import java.io.Writer JavaDoc;
20 import java.util.ArrayList JavaDoc;
21 import java.util.List JavaDoc;
22 import java.util.Locale JavaDoc;
23
24 import org.joda.time.Chronology;
25 import org.joda.time.DateTimeConstants;
26 import org.joda.time.DateTimeField;
27 import org.joda.time.DateTimeFieldType;
28 import org.joda.time.DateTimeZone;
29 import org.joda.time.ReadablePartial;
30 import org.joda.time.field.MillisDurationField;
31 import org.joda.time.field.PreciseDateTimeField;
32
33 /**
34  * Factory that creates complex instances of DateTimeFormatter via method calls.
35  * <p>
36  * Datetime formatting is performed by the {@link DateTimeFormatter} class.
37  * Three classes provide factory methods to create formatters, and this is one.
38  * The others are {@link DateTimeFormat} and {@link ISODateTimeFormat}.
39  * <p>
40  * DateTimeFormatterBuilder is used for constructing formatters which are then
41  * used to print or parse. The formatters are built by appending specific fields
42  * or other formatters to an instance of this builder.
43  * <p>
44  * For example, a formatter that prints month and year, like "January 1970",
45  * can be constructed as follows:
46  * <p>
47  * <pre>
48  * DateTimeFormatter monthAndYear = new DateTimeFormatterBuilder()
49  * .appendMonthOfYearText()
50  * .appendLiteral(' ')
51  * .appendYear(4, 4)
52  * .toFormatter();
53  * </pre>
54  * <p>
55  * DateTimeFormatterBuilder itself is mutable and not thread-safe, but the
56  * formatters that it builds are thread-safe and immutable.
57  *
58  * @author Brian S O'Neill
59  * @author Stephen Colebourne
60  * @author Fredrik Borgh
61  * @since 1.0
62  * @see DateTimeFormat
63  * @see ISODateTimeFormat
64  */

65 public class DateTimeFormatterBuilder {
66
67     /** Array of printers and parsers (alternating). */
68     private ArrayList JavaDoc iElementPairs;
69     /** Cache of the last returned formatter. */
70     private Object JavaDoc iFormatter;
71
72     //-----------------------------------------------------------------------
73
/**
74      * Creates a DateTimeFormatterBuilder.
75      */

76     public DateTimeFormatterBuilder() {
77         super();
78         iElementPairs = new ArrayList JavaDoc();
79     }
80
81     //-----------------------------------------------------------------------
82
/**
83      * Constructs a DateTimeFormatter using all the appended elements.
84      * <p>
85      * This is the main method used by applications at the end of the build
86      * process to create a usable formatter.
87      * <p>
88      * Subsequent changes to this builder do not affect the returned formatter.
89      * <p>
90      * The returned formatter may not support both printing and parsing.
91      * The methods {@link DateTimeFormatter#isPrinter()} and
92      * {@link DateTimeFormatter#isParser()} will help you determine the state
93      * of the formatter.
94      *
95      * @throws UnsupportedOperationException if neither printing nor parsing is supported
96      */

97     public DateTimeFormatter toFormatter() {
98         Object JavaDoc f = getFormatter();
99         DateTimePrinter printer = null;
100         if (isPrinter(f)) {
101             printer = (DateTimePrinter) f;
102         }
103         DateTimeParser parser = null;
104         if (isParser(f)) {
105             parser = (DateTimeParser) f;
106         }
107         if (printer != null || parser != null) {
108             return new DateTimeFormatter(printer, parser);
109         }
110         throw new UnsupportedOperationException JavaDoc("Both printing and parsing not supported");
111     }
112
113     /**
114      * Internal method to create a DateTimePrinter instance using all the
115      * appended elements.
116      * <p>
117      * Most applications will not use this method.
118      * If you want a printer in an application, call {@link #toFormatter()}
119      * and just use the printing API.
120      * <p>
121      * Subsequent changes to this builder do not affect the returned printer.
122      *
123      * @throws UnsupportedOperationException if printing is not supported
124      */

125     public DateTimePrinter toPrinter() {
126         Object JavaDoc f = getFormatter();
127         if (isPrinter(f)) {
128             return (DateTimePrinter) f;
129         }
130         throw new UnsupportedOperationException JavaDoc("Printing is not supported");
131     }
132
133     /**
134      * Internal method to create a DateTimeParser instance using all the
135      * appended elements.
136      * <p>
137      * Most applications will not use this method.
138      * If you want a parser in an application, call {@link #toFormatter()}
139      * and just use the parsing API.
140      * <p>
141      * Subsequent changes to this builder do not affect the returned parser.
142      *
143      * @throws UnsupportedOperationException if parsing is not supported
144      */

145     public DateTimeParser toParser() {
146         Object JavaDoc f = getFormatter();
147         if (isParser(f)) {
148             return (DateTimeParser) f;
149         }
150         throw new UnsupportedOperationException JavaDoc("Parsing is not supported");
151     }
152
153     //-----------------------------------------------------------------------
154
/**
155      * Returns true if toFormatter can be called without throwing an
156      * UnsupportedOperationException.
157      *
158      * @return true if a formatter can be built
159      */

160     public boolean canBuildFormatter() {
161         return isFormatter(getFormatter());
162     }
163
164     /**
165      * Returns true if toPrinter can be called without throwing an
166      * UnsupportedOperationException.
167      *
168      * @return true if a printer can be built
169      */

170     public boolean canBuildPrinter() {
171         return isPrinter(getFormatter());
172     }
173
174     /**
175      * Returns true if toParser can be called without throwing an
176      * UnsupportedOperationException.
177      *
178      * @return true if a parser can be built
179      */

180     public boolean canBuildParser() {
181         return isParser(getFormatter());
182     }
183
184     //-----------------------------------------------------------------------
185
/**
186      * Clears out all the appended elements, allowing this builder to be
187      * reused.
188      */

189     public void clear() {
190         iFormatter = null;
191         iElementPairs.clear();
192     }
193
194     //-----------------------------------------------------------------------
195
/**
196      * Appends another formatter.
197      *
198      * @param formatter the formatter to add
199      * @return this DateTimeFormatterBuilder
200      * @throws IllegalArgumentException if formatter is null or of an invalid type
201      */

202     public DateTimeFormatterBuilder append(DateTimeFormatter formatter) {
203         if (formatter == null) {
204             throw new IllegalArgumentException JavaDoc("No formatter supplied");
205         }
206         return append0(formatter.getPrinter(), formatter.getParser());
207     }
208
209     /**
210      * Appends just a printer. With no matching parser, a parser cannot be
211      * built from this DateTimeFormatterBuilder.
212      *
213      * @param printer the printer to add
214      * @return this DateTimeFormatterBuilder
215      * @throws IllegalArgumentException if printer is null or of an invalid type
216      */

217     public DateTimeFormatterBuilder append(DateTimePrinter printer) {
218         checkPrinter(printer);
219         return append0(printer, null);
220     }
221
222     /**
223      * Appends just a parser. With no matching printer, a printer cannot be
224      * built from this builder.
225      *
226      * @param parser the parser to add
227      * @return this DateTimeFormatterBuilder
228      * @throws IllegalArgumentException if parser is null or of an invalid type
229      */

230     public DateTimeFormatterBuilder append(DateTimeParser parser) {
231         checkParser(parser);
232         return append0(null, parser);
233     }
234
235     /**
236      * Appends a printer/parser pair.
237      *
238      * @param printer the printer to add
239      * @param parser the parser to add
240      * @return this DateTimeFormatterBuilder
241      * @throws IllegalArgumentException if printer or parser is null or of an invalid type
242      */

243     public DateTimeFormatterBuilder append(DateTimePrinter printer, DateTimeParser parser) {
244         checkPrinter(printer);
245         checkParser(parser);
246         return append0(printer, parser);
247     }
248
249     /**
250      * Appends a printer and a set of matching parsers. When parsing, the first
251      * parser in the list is selected for parsing. If it fails, the next is
252      * chosen, and so on. If none of these parsers succeeds, then the failed
253      * position of the parser that made the greatest progress is returned.
254      * <p>
255      * Only the printer is optional. In addtion, it is illegal for any but the
256      * last of the parser array elements to be null. If the last element is
257      * null, this represents the empty parser. The presence of an empty parser
258      * indicates that the entire array of parse formats is optional.
259      *
260      * @param printer the printer to add
261      * @param parsers the parsers to add
262      * @return this DateTimeFormatterBuilder
263      * @throws IllegalArgumentException if any printer or parser is of an invalid type
264      * @throws IllegalArgumentException if any parser element but the last is null
265      */

266     public DateTimeFormatterBuilder append(DateTimePrinter printer, DateTimeParser[] parsers) {
267         if (printer != null) {
268             checkPrinter(printer);
269         }
270         if (parsers == null) {
271             throw new IllegalArgumentException JavaDoc("No parsers supplied");
272         }
273         int length = parsers.length;
274         if (length == 1) {
275             if (parsers[0] == null) {
276                 throw new IllegalArgumentException JavaDoc("No parser supplied");
277             }
278             return append0(printer, parsers[0]);
279         }
280
281         DateTimeParser[] copyOfParsers = new DateTimeParser[length];
282         int i;
283         for (i = 0; i < length - 1; i++) {
284             if ((copyOfParsers[i] = parsers[i]) == null) {
285                 throw new IllegalArgumentException JavaDoc("Incomplete parser array");
286             }
287         }
288         copyOfParsers[i] = parsers[i];
289
290         return append0(printer, new MatchingParser(copyOfParsers));
291     }
292
293     /**
294      * Appends just a parser element which is optional. With no matching
295      * printer, a printer cannot be built from this DateTimeFormatterBuilder.
296      *
297      * @return this DateTimeFormatterBuilder
298      * @throws IllegalArgumentException if parser is null or of an invalid type
299      */

300     public DateTimeFormatterBuilder appendOptional(DateTimeParser parser) {
301         checkParser(parser);
302         DateTimeParser[] parsers = new DateTimeParser[] {parser, null};
303         return append0(null, new MatchingParser(parsers));
304     }
305
306     //-----------------------------------------------------------------------
307
/**
308      * Checks if the parser is non null and a provider.
309      *
310      * @param parser the parser to check
311      */

312     private void checkParser(DateTimeParser parser) {
313         if (parser == null) {
314             throw new IllegalArgumentException JavaDoc("No parser supplied");
315         }
316     }
317
318     /**
319      * Checks if the printer is non null and a provider.
320      *
321      * @param printer the printer to check
322      */

323     private void checkPrinter(DateTimePrinter printer) {
324         if (printer == null) {
325             throw new IllegalArgumentException JavaDoc("No printer supplied");
326         }
327     }
328
329     private DateTimeFormatterBuilder append0(Object JavaDoc element) {
330         iFormatter = null;
331         // Add the element as both a printer and parser.
332
iElementPairs.add(element);
333         iElementPairs.add(element);
334         return this;
335     }
336
337     private DateTimeFormatterBuilder append0(
338             DateTimePrinter printer, DateTimeParser parser) {
339         iFormatter = null;
340         iElementPairs.add(printer);
341         iElementPairs.add(parser);
342         return this;
343     }
344
345     //-----------------------------------------------------------------------
346
/**
347      * Instructs the printer to emit a specific character, and the parser to
348      * expect it. The parser is case-insensitive.
349      *
350      * @return this DateTimeFormatterBuilder
351      */

352     public DateTimeFormatterBuilder appendLiteral(char c) {
353         return append0(new CharacterLiteral(c));
354     }
355
356     /**
357      * Instructs the printer to emit specific text, and the parser to expect
358      * it. The parser is case-insensitive.
359      *
360      * @return this DateTimeFormatterBuilder
361      * @throws IllegalArgumentException if text is null
362      */

363     public DateTimeFormatterBuilder appendLiteral(String JavaDoc text) {
364         if (text == null) {
365             throw new IllegalArgumentException JavaDoc("Literal must not be null");
366         }
367         switch (text.length()) {
368             case 0:
369                 return this;
370             case 1:
371                 return append0(new CharacterLiteral(text.charAt(0)));
372             default:
373                 return append0(new StringLiteral(text));
374         }
375     }
376
377     /**
378      * Instructs the printer to emit a field value as a decimal number, and the
379      * parser to expect an unsigned decimal number.
380      *
381      * @param fieldType type of field to append
382      * @param minDigits minumum number of digits to <i>print</i>
383      * @param maxDigits maximum number of digits to <i>parse</i>, or the estimated
384      * maximum number of digits to print
385      * @return this DateTimeFormatterBuilder
386      * @throws IllegalArgumentException if field type is null
387      */

388     public DateTimeFormatterBuilder appendDecimal(
389             DateTimeFieldType fieldType, int minDigits, int maxDigits) {
390         if (fieldType == null) {
391             throw new IllegalArgumentException JavaDoc("Field type must not be null");
392         }
393         if (maxDigits < minDigits) {
394             maxDigits = minDigits;
395         }
396         if (minDigits < 0 || maxDigits <= 0) {
397             throw new IllegalArgumentException JavaDoc();
398         }
399         if (minDigits <= 1) {
400             return append0(new UnpaddedNumber(fieldType, maxDigits, false));
401         } else {
402             return append0(new PaddedNumber(fieldType, maxDigits, false, minDigits));
403         }
404     }
405
406     /**
407      * Instructs the printer to emit a field value as a decimal number, and the
408      * parser to expect a signed decimal number.
409      *
410      * @param fieldType type of field to append
411      * @param minDigits minumum number of digits to <i>print</i>
412      * @param maxDigits maximum number of digits to <i>parse</i>, or the estimated
413      * maximum number of digits to print
414      * @return this DateTimeFormatterBuilder
415      * @throws IllegalArgumentException if field type is null
416      */

417     public DateTimeFormatterBuilder appendSignedDecimal(
418             DateTimeFieldType fieldType, int minDigits, int maxDigits) {
419         if (fieldType == null) {
420             throw new IllegalArgumentException JavaDoc("Field type must not be null");
421         }
422         if (maxDigits < minDigits) {
423             maxDigits = minDigits;
424         }
425         if (minDigits < 0 || maxDigits <= 0) {
426             throw new IllegalArgumentException JavaDoc();
427         }
428         if (minDigits <= 1) {
429             return append0(new UnpaddedNumber(fieldType, maxDigits, true));
430         } else {
431             return append0(new PaddedNumber(fieldType, maxDigits, true, minDigits));
432         }
433     }
434
435     /**
436      * Instructs the printer to emit a field value as text, and the
437      * parser to expect text.
438      *
439      * @param fieldType type of field to append
440      * @return this DateTimeFormatterBuilder
441      * @throws IllegalArgumentException if field type is null
442      */

443     public DateTimeFormatterBuilder appendText(DateTimeFieldType fieldType) {
444         if (fieldType == null) {
445             throw new IllegalArgumentException JavaDoc("Field type must not be null");
446         }
447         return append0(new TextField(fieldType, false));
448     }
449
450     /**
451      * Instructs the printer to emit a field value as short text, and the
452      * parser to expect text.
453      *
454      * @param fieldType type of field to append
455      * @return this DateTimeFormatterBuilder
456      * @throws IllegalArgumentException if field type is null
457      */

458     public DateTimeFormatterBuilder appendShortText(DateTimeFieldType fieldType) {
459         if (fieldType == null) {
460             throw new IllegalArgumentException JavaDoc("Field type must not be null");
461         }
462         return append0(new TextField(fieldType, true));
463     }
464
465     /**
466      * Instructs the printer to emit a remainder of time as a decimal fraction,
467      * sans decimal point. For example, if the field is specified as
468      * minuteOfHour and the time is 12:30:45, the value printed is 75. A
469      * decimal point is implied, so the fraction is 0.75, or three-quarters of
470      * a minute.
471      *
472      * @param fieldType type of field to append
473      * @param minDigits minumum number of digits to print.
474      * @param maxDigits maximum number of digits to print or parse.
475      * @return this DateTimeFormatterBuilder
476      * @throws IllegalArgumentException if field type is null
477      */

478     public DateTimeFormatterBuilder appendFraction(
479             DateTimeFieldType fieldType, int minDigits, int maxDigits) {
480         if (fieldType == null) {
481             throw new IllegalArgumentException JavaDoc("Field type must not be null");
482         }
483         if (maxDigits < minDigits) {
484             maxDigits = minDigits;
485         }
486         if (minDigits < 0 || maxDigits <= 0) {
487             throw new IllegalArgumentException JavaDoc();
488         }
489         return append0(new Fraction(fieldType, minDigits, maxDigits));
490     }
491
492     /**
493      * @param minDigits minumum number of digits to print
494      * @param maxDigits maximum number of digits to print or parse
495      * @return this DateTimeFormatterBuilder
496      */

497     public DateTimeFormatterBuilder appendFractionOfSecond(int minDigits, int maxDigits) {
498         return appendFraction(DateTimeFieldType.secondOfDay(), minDigits, maxDigits);
499     }
500
501     /**
502      * @param minDigits minumum number of digits to print
503      * @param maxDigits maximum number of digits to print or parse
504      * @return this DateTimeFormatterBuilder
505      */

506     public DateTimeFormatterBuilder appendFractionOfMinute(int minDigits, int maxDigits) {
507         return appendFraction(DateTimeFieldType.minuteOfDay(), minDigits, maxDigits);
508     }
509
510     /**
511      * @param minDigits minumum number of digits to print
512      * @param maxDigits maximum number of digits to print or parse
513      * @return this DateTimeFormatterBuilder
514      */

515     public DateTimeFormatterBuilder appendFractionOfHour(int minDigits, int maxDigits) {
516         return appendFraction(DateTimeFieldType.hourOfDay(), minDigits, maxDigits);
517     }
518
519     /**
520      * @param minDigits minumum number of digits to print
521      * @param maxDigits maximum number of digits to print or parse
522      * @return this DateTimeFormatterBuilder
523      */

524     public DateTimeFormatterBuilder appendFractionOfDay(int minDigits, int maxDigits) {
525         return appendFraction(DateTimeFieldType.dayOfYear(), minDigits, maxDigits);
526     }
527
528     /**
529      * Instructs the printer to emit a numeric millisOfSecond field.
530      *
531      * @param minDigits minumum number of digits to print
532      * @return this DateTimeFormatterBuilder
533      */

534     public DateTimeFormatterBuilder appendMillisOfSecond(int minDigits) {
535         return appendDecimal(DateTimeFieldType.millisOfSecond(), minDigits, 3);
536     }
537
538     /**
539      * Instructs the printer to emit a numeric millisOfDay field.
540      *
541      * @param minDigits minumum number of digits to print
542      * @return this DateTimeFormatterBuilder
543      */

544     public DateTimeFormatterBuilder appendMillisOfDay(int minDigits) {
545         return appendDecimal(DateTimeFieldType.millisOfDay(), minDigits, 8);
546     }
547
548     /**
549      * Instructs the printer to emit a numeric secondOfMinute field.
550      *
551      * @param minDigits minumum number of digits to print
552      * @return this DateTimeFormatterBuilder
553      */

554     public DateTimeFormatterBuilder appendSecondOfMinute(int minDigits) {
555         return appendDecimal(DateTimeFieldType.secondOfMinute(), minDigits, 2);
556     }
557
558     /**
559      * Instructs the printer to emit a numeric secondOfDay field.
560      *
561      * @param minDigits minumum number of digits to print
562      * @return this DateTimeFormatterBuilder
563      */

564     public DateTimeFormatterBuilder appendSecondOfDay(int minDigits) {
565         return appendDecimal(DateTimeFieldType.secondOfDay(), minDigits, 5);
566     }
567
568     /**
569      * Instructs the printer to emit a numeric minuteOfHour field.
570      *
571      * @param minDigits minumum number of digits to print
572      * @return this DateTimeFormatterBuilder
573      */

574     public DateTimeFormatterBuilder appendMinuteOfHour(int minDigits) {
575         return appendDecimal(DateTimeFieldType.minuteOfHour(), minDigits, 2);
576     }
577
578     /**
579      * Instructs the printer to emit a numeric minuteOfDay field.
580      *
581      * @param minDigits minumum number of digits to print
582      * @return this DateTimeFormatterBuilder
583      */

584     public DateTimeFormatterBuilder appendMinuteOfDay(int minDigits) {
585         return appendDecimal(DateTimeFieldType.minuteOfDay(), minDigits, 4);
586     }
587
588     /**
589      * Instructs the printer to emit a numeric hourOfDay field.
590      *
591      * @param minDigits minumum number of digits to print
592      * @return this DateTimeFormatterBuilder
593      */

594     public DateTimeFormatterBuilder appendHourOfDay(int minDigits) {
595         return appendDecimal(DateTimeFieldType.hourOfDay(), minDigits, 2);
596     }
597
598     /**
599      * Instructs the printer to emit a numeric clockhourOfDay field.
600      *
601      * @param minDigits minumum number of digits to print
602      * @return this DateTimeFormatterBuilder
603      */

604     public DateTimeFormatterBuilder appendClockhourOfDay(int minDigits) {
605         return appendDecimal(DateTimeFieldType.clockhourOfDay(), minDigits, 2);
606     }
607
608     /**
609      * Instructs the printer to emit a numeric hourOfHalfday field.
610      *
611      * @param minDigits minumum number of digits to print
612      * @return this DateTimeFormatterBuilder
613      */

614     public DateTimeFormatterBuilder appendHourOfHalfday(int minDigits) {
615         return appendDecimal(DateTimeFieldType.hourOfHalfday(), minDigits, 2);
616     }
617
618     /**
619      * Instructs the printer to emit a numeric clockhourOfHalfday field.
620      *
621      * @param minDigits minumum number of digits to print
622      * @return this DateTimeFormatterBuilder
623      */

624     public DateTimeFormatterBuilder appendClockhourOfHalfday(int minDigits) {
625         return appendDecimal(DateTimeFieldType.clockhourOfHalfday(), minDigits, 2);
626     }
627
628     /**
629      * Instructs the printer to emit a numeric dayOfWeek field.
630      *
631      * @param minDigits minumum number of digits to print
632      * @return this DateTimeFormatterBuilder
633      */

634     public DateTimeFormatterBuilder appendDayOfWeek(int minDigits) {
635         return appendDecimal(DateTimeFieldType.dayOfWeek(), minDigits, 1);
636     }
637
638     /**
639      * Instructs the printer to emit a numeric dayOfMonth field.
640      *
641      * @param minDigits minumum number of digits to print
642      * @return this DateTimeFormatterBuilder
643      */

644     public DateTimeFormatterBuilder appendDayOfMonth(int minDigits) {
645         return appendDecimal(DateTimeFieldType.dayOfMonth(), minDigits, 2);
646     }
647
648     /**
649      * Instructs the printer to emit a numeric dayOfYear field.
650      *
651      * @param minDigits minumum number of digits to print
652      * @return this DateTimeFormatterBuilder
653      */

654     public DateTimeFormatterBuilder appendDayOfYear(int minDigits) {
655         return appendDecimal(DateTimeFieldType.dayOfYear(), minDigits, 3);
656     }
657
658     /**
659      * Instructs the printer to emit a numeric weekOfWeekyear field.
660      *
661      * @param minDigits minumum number of digits to print
662      * @return this DateTimeFormatterBuilder
663      */

664     public DateTimeFormatterBuilder appendWeekOfWeekyear(int minDigits) {
665         return appendDecimal(DateTimeFieldType.weekOfWeekyear(), minDigits, 2);
666     }
667
668     /**
669      * Instructs the printer to emit a numeric weekyear field.
670      *
671      * @param minDigits minumum number of digits to <i>print</i>
672      * @param maxDigits maximum number of digits to <i>parse</i>, or the estimated
673      * maximum number of digits to print
674      * @return this DateTimeFormatterBuilder
675      */

676     public DateTimeFormatterBuilder appendWeekyear(int minDigits, int maxDigits) {
677         return appendSignedDecimal(DateTimeFieldType.weekyear(), minDigits, maxDigits);
678     }
679
680     /**
681      * Instructs the printer to emit a numeric monthOfYear field.
682      *
683      * @param minDigits minumum number of digits to print
684      * @return this DateTimeFormatterBuilder
685      */

686     public DateTimeFormatterBuilder appendMonthOfYear(int minDigits) {
687         return appendDecimal(DateTimeFieldType.monthOfYear(), minDigits, 2);
688     }
689
690     /**
691      * Instructs the printer to emit a numeric year field.
692      *
693      * @param minDigits minumum number of digits to <i>print</i>
694      * @param maxDigits maximum number of digits to <i>parse</i>, or the estimated
695      * maximum number of digits to print
696      * @return this DateTimeFormatterBuilder
697      */

698     public DateTimeFormatterBuilder appendYear(int minDigits, int maxDigits) {
699         return appendSignedDecimal(DateTimeFieldType.year(), minDigits, maxDigits);
700     }
701
702     /**
703      * Instructs the printer to emit a numeric year field which always prints
704      * and parses two digits. A pivot year is used during parsing to determine
705      * the range of supported years as <code>(pivot - 50) .. (pivot + 49)</code>.
706      *
707      * <pre>
708      * pivot supported range 00 is 20 is 40 is 60 is 80 is
709      * ---------------------------------------------------------------
710      * 1950 1900..1999 1900 1920 1940 1960 1980
711      * 1975 1925..2024 2000 2020 1940 1960 1980
712      * 2000 1950..2049 2000 2020 2040 1960 1980
713      * 2025 1975..2074 2000 2020 2040 2060 1980
714      * 2050 2000..2099 2000 2020 2040 2060 2080
715      * </pre>
716      *
717      * @param pivot pivot year to use when parsing
718      * @return this DateTimeFormatterBuilder
719      */

720     public DateTimeFormatterBuilder appendTwoDigitYear(int pivot) {
721         return appendTwoDigitYear(pivot, false);
722     }
723
724     /**
725      * Instructs the printer to emit a numeric year field which always prints
726      * two digits. A pivot year is used during parsing to determine the range
727      * of supported years as <code>(pivot - 50) .. (pivot + 49)</code>. If
728      * parse is instructed to be lenient and the digit count is not two, it is
729      * treated as an absolute year. With lenient parsing, specifying a positive
730      * or negative sign before the year also makes it absolute.
731      *
732      * @param pivot pivot year to use when parsing
733      * @param lenientParse when true, if digit count is not two, it is treated
734      * as an absolute year
735      * @return this DateTimeFormatterBuilder
736      * @since 1.1
737      */

738     public DateTimeFormatterBuilder appendTwoDigitYear(int pivot, boolean lenientParse) {
739         return append0(new TwoDigitYear(DateTimeFieldType.year(), pivot, lenientParse));
740     }
741
742     /**
743      * Instructs the printer to emit a numeric weekyear field which always prints
744      * and parses two digits. A pivot year is used during parsing to determine
745      * the range of supported years as <code>(pivot - 50) .. (pivot + 49)</code>.
746      *
747      * <pre>
748      * pivot supported range 00 is 20 is 40 is 60 is 80 is
749      * ---------------------------------------------------------------
750      * 1950 1900..1999 1900 1920 1940 1960 1980
751      * 1975 1925..2024 2000 2020 1940 1960 1980
752      * 2000 1950..2049 2000 2020 2040 1960 1980
753      * 2025 1975..2074 2000 2020 2040 2060 1980
754      * 2050 2000..2099 2000 2020 2040 2060 2080
755      * </pre>
756      *
757      * @param pivot pivot weekyear to use when parsing
758      * @return this DateTimeFormatterBuilder
759      */

760     public DateTimeFormatterBuilder appendTwoDigitWeekyear(int pivot) {
761         return appendTwoDigitWeekyear(pivot, false);
762     }
763
764     /**
765      * Instructs the printer to emit a numeric weekyear field which always prints
766      * two digits. A pivot year is used during parsing to determine the range
767      * of supported years as <code>(pivot - 50) .. (pivot + 49)</code>. If
768      * parse is instructed to be lenient and the digit count is not two, it is
769      * treated as an absolute weekyear. With lenient parsing, specifying a positive
770      * or negative sign before the weekyear also makes it absolute.
771      *
772      * @param pivot pivot weekyear to use when parsing
773      * @param lenientParse when true, if digit count is not two, it is treated
774      * as an absolute weekyear
775      * @return this DateTimeFormatterBuilder
776      * @since 1.1
777      */

778     public DateTimeFormatterBuilder appendTwoDigitWeekyear(int pivot, boolean lenientParse) {
779         return append0(new TwoDigitYear(DateTimeFieldType.weekyear(), pivot, lenientParse));
780     }
781
782     /**
783      * Instructs the printer to emit a numeric yearOfEra field.
784      *
785      * @param minDigits minumum number of digits to <i>print</i>
786      * @param maxDigits maximum number of digits to <i>parse</i>, or the estimated
787      * maximum number of digits to print
788      * @return this DateTimeFormatterBuilder
789      */

790     public DateTimeFormatterBuilder appendYearOfEra(int minDigits, int maxDigits) {
791         return appendDecimal(DateTimeFieldType.yearOfEra(), minDigits, maxDigits);
792     }
793
794     /**
795      * Instructs the printer to emit a numeric year of century field.
796      *
797      * @param minDigits minumum number of digits to print
798      * @param maxDigits maximum number of digits to <i>parse</i>, or the estimated
799      * maximum number of digits to print
800      * @return this DateTimeFormatterBuilder
801      */

802     public DateTimeFormatterBuilder appendYearOfCentury(int minDigits, int maxDigits) {
803         return appendDecimal(DateTimeFieldType.yearOfCentury(), minDigits, maxDigits);
804     }
805
806     /**
807      * Instructs the printer to emit a numeric century of era field.
808      *
809      * @param minDigits minumum number of digits to print
810      * @param maxDigits maximum number of digits to <i>parse</i>, or the estimated
811      * maximum number of digits to print
812      * @return this DateTimeFormatterBuilder
813      */

814     public DateTimeFormatterBuilder appendCenturyOfEra(int minDigits, int maxDigits) {
815         return appendSignedDecimal(DateTimeFieldType.centuryOfEra(), minDigits, maxDigits);
816     }
817
818     /**
819      * Instructs the printer to emit a locale-specific AM/PM text, and the
820      * parser to expect it. The parser is case-insensitive.
821      *
822      * @return this DateTimeFormatterBuilder
823      */

824     public DateTimeFormatterBuilder appendHalfdayOfDayText() {
825         return appendText(DateTimeFieldType.halfdayOfDay());
826     }
827
828     /**
829      * Instructs the printer to emit a locale-specific dayOfWeek text. The
830      * parser will accept a long or short dayOfWeek text, case-insensitive.
831      *
832      * @return this DateTimeFormatterBuilder
833      */

834     public DateTimeFormatterBuilder appendDayOfWeekText() {
835         return appendText(DateTimeFieldType.dayOfWeek());
836     }
837
838     /**
839      * Instructs the printer to emit a short locale-specific dayOfWeek
840      * text. The parser will accept a long or short dayOfWeek text,
841      * case-insensitive.
842      *
843      * @return this DateTimeFormatterBuilder
844      */

845     public DateTimeFormatterBuilder appendDayOfWeekShortText() {
846         return appendShortText(DateTimeFieldType.dayOfWeek());
847     }
848
849     /**
850      * Instructs the printer to emit a short locale-specific monthOfYear
851      * text. The parser will accept a long or short monthOfYear text,
852      * case-insensitive.
853      *
854      * @return this DateTimeFormatterBuilder
855      */

856     public DateTimeFormatterBuilder appendMonthOfYearText() {
857         return appendText(DateTimeFieldType.monthOfYear());
858     }
859
860     /**
861      * Instructs the printer to emit a locale-specific monthOfYear text. The
862      * parser will accept a long or short monthOfYear text, case-insensitive.
863      *
864      * @return this DateTimeFormatterBuilder
865      */

866     public DateTimeFormatterBuilder appendMonthOfYearShortText() {
867         return appendShortText(DateTimeFieldType.monthOfYear());
868     }
869
870     /**
871      * Instructs the printer to emit a locale-specific era text (BC/AD), and
872      * the parser to expect it. The parser is case-insensitive.
873      *
874      * @return this DateTimeFormatterBuilder
875      */

876     public DateTimeFormatterBuilder appendEraText() {
877         return appendText(DateTimeFieldType.era());
878     }
879
880     /**
881      * Instructs the printer to emit a locale-specific time zone name. A
882      * parser cannot be created from this builder if a time zone name is
883      * appended.
884      *
885      * @return this DateTimeFormatterBuilder
886      */

887     public DateTimeFormatterBuilder appendTimeZoneName() {
888         return append0(new TimeZoneName(TimeZoneName.LONG_NAME), null);
889     }
890
891     /**
892      * Instructs the printer to emit a short locale-specific time zone
893      * name. A parser cannot be created from this builder if time zone
894      * name is appended.
895      *
896      * @return this DateTimeFormatterBuilder
897      */

898     public DateTimeFormatterBuilder appendTimeZoneShortName() {
899         return append0(new TimeZoneName(TimeZoneName.SHORT_NAME), null);
900     }
901
902     /**
903      * Instructs the printer to emit the identifier of the time zone.
904      * This field cannot currently be parsed.
905      *
906      * @return this DateTimeFormatterBuilder
907      */

908     public DateTimeFormatterBuilder appendTimeZoneId() {
909         return append0(new TimeZoneName(TimeZoneName.ID), null);
910     }
911
912     /**
913      * Instructs the printer to emit text and numbers to display time zone
914      * offset from UTC. A parser will use the parsed time zone offset to adjust
915      * the datetime.
916      *
917      * @param zeroOffsetText Text to use if time zone offset is zero. If
918      * null, offset is always shown.
919      * @param showSeparators If true, prints ':' separator before minute and
920      * second field and prints '.' separator before fraction field.
921      * @param minFields minimum number of fields to print, stopping when no
922      * more precision is required. 1=hours, 2=minutes, 3=seconds, 4=fraction
923      * @param maxFields maximum number of fields to print
924      * @return this DateTimeFormatterBuilder
925      */

926     public DateTimeFormatterBuilder appendTimeZoneOffset(
927             String JavaDoc zeroOffsetText, boolean showSeparators,
928             int minFields, int maxFields) {
929         return append0(new TimeZoneOffset
930                        (zeroOffsetText, showSeparators, minFields, maxFields));
931     }
932
933     //-----------------------------------------------------------------------
934
/**
935      * Calls upon {@link DateTimeFormat} to parse the pattern and append the
936      * results into this builder.
937      *
938      * @param pattern pattern specification
939      * @throws IllegalArgumentException if the pattern is invalid
940      * @see DateTimeFormat
941      */

942     public DateTimeFormatterBuilder appendPattern(String JavaDoc pattern) {
943         DateTimeFormat.appendPatternTo(this, pattern);
944         return this;
945     }
946
947     //-----------------------------------------------------------------------
948
private Object JavaDoc getFormatter() {
949         Object JavaDoc f = iFormatter;
950
951         if (f == null) {
952             if (iElementPairs.size() == 2) {
953                 Object JavaDoc printer = iElementPairs.get(0);
954                 Object JavaDoc parser = iElementPairs.get(1);
955
956                 if (printer != null) {
957                     if (printer == parser || parser == null) {
958                         f = printer;
959                     }
960                 } else {
961                     f = parser;
962                 }
963             }
964
965             if (f == null) {
966                 f = new Composite(iElementPairs);
967             }
968
969             iFormatter = f;
970         }
971
972         return f;
973     }
974
975     private boolean isPrinter(Object JavaDoc f) {
976         if (f instanceof DateTimePrinter) {
977             if (f instanceof Composite) {
978                 return ((Composite)f).isPrinter();
979             }
980             return true;
981         }
982         return false;
983     }
984
985     private boolean isParser(Object JavaDoc f) {
986         if (f instanceof DateTimeParser) {
987             if (f instanceof Composite) {
988                 return ((Composite)f).isParser();
989             }
990             return true;
991         }
992         return false;
993     }
994
995     private boolean isFormatter(Object JavaDoc f) {
996         return (isPrinter(f) || isParser(f));
997     }
998
999     static void appendUnknownString(StringBuffer JavaDoc buf, int len) {
1000        for (int i = len; --i >= 0;) {
1001            buf.append('\ufffd');
1002        }
1003    }
1004
1005    static void printUnknownString(Writer JavaDoc out, int len) throws IOException JavaDoc {
1006        for (int i = len; --i >= 0;) {
1007            out.write('\ufffd');
1008        }
1009    }
1010
1011    //-----------------------------------------------------------------------
1012
static class CharacterLiteral
1013            implements DateTimePrinter, DateTimeParser {
1014
1015        private final char iValue;
1016
1017        CharacterLiteral(char value) {
1018            super();
1019            iValue = value;
1020        }
1021
1022        public int estimatePrintedLength() {
1023            return 1;
1024        }
1025
1026        public void printTo(
1027                StringBuffer JavaDoc buf, long instant, Chronology chrono,
1028                int displayOffset, DateTimeZone displayZone, Locale JavaDoc locale) {
1029            buf.append(iValue);
1030        }
1031
1032        public void printTo(
1033                Writer JavaDoc out, long instant, Chronology chrono,
1034                int displayOffset, DateTimeZone displayZone, Locale JavaDoc locale) throws IOException JavaDoc {
1035            out.write(iValue);
1036        }
1037
1038        public void printTo(StringBuffer JavaDoc buf, ReadablePartial partial, Locale JavaDoc locale) {
1039            buf.append(iValue);
1040        }
1041
1042        public void printTo(Writer JavaDoc out, ReadablePartial partial, Locale JavaDoc locale) throws IOException JavaDoc {
1043            out.write(iValue);
1044        }
1045
1046        public int estimateParsedLength() {
1047            return 1;
1048        }
1049
1050        public int parseInto(DateTimeParserBucket bucket, String JavaDoc text, int position) {
1051            if (position >= text.length()) {
1052                return ~position;
1053            }
1054
1055            char a = text.charAt(position);
1056            char b = iValue;
1057
1058            if (a != b) {
1059                a = Character.toUpperCase(a);
1060                b = Character.toUpperCase(b);
1061                if (a != b) {
1062                    a = Character.toLowerCase(a);
1063                    b = Character.toLowerCase(b);
1064                    if (a != b) {
1065                        return ~position;
1066                    }
1067                }
1068            }
1069
1070            return position + 1;
1071        }
1072    }
1073
1074    //-----------------------------------------------------------------------
1075
static class StringLiteral
1076            implements DateTimePrinter, DateTimeParser {
1077
1078        private final String JavaDoc iValue;
1079
1080        StringLiteral(String JavaDoc value) {
1081            super();
1082            iValue = value;
1083        }
1084
1085        public int estimatePrintedLength() {
1086            return iValue.length();
1087        }
1088
1089        public void printTo(
1090                StringBuffer JavaDoc buf, long instant, Chronology chrono,
1091                int displayOffset, DateTimeZone displayZone, Locale JavaDoc locale) {
1092            buf.append(iValue);
1093        }
1094
1095        public void printTo(
1096                Writer JavaDoc out, long instant, Chronology chrono,
1097                int displayOffset, DateTimeZone displayZone, Locale JavaDoc locale) throws IOException JavaDoc {
1098            out.write(iValue);
1099        }
1100
1101        public void printTo(StringBuffer JavaDoc buf, ReadablePartial partial, Locale JavaDoc locale) {
1102            buf.append(iValue);
1103        }
1104
1105        public void printTo(Writer JavaDoc out, ReadablePartial partial, Locale JavaDoc locale) throws IOException JavaDoc {
1106            out.write(iValue);
1107        }
1108
1109        public int estimateParsedLength() {
1110            return iValue.length();
1111        }
1112
1113        public int parseInto(DateTimeParserBucket bucket, String JavaDoc text, int position) {
1114            if (text.regionMatches(true, position, iValue, 0, iValue.length())) {
1115                return position + iValue.length();
1116            }
1117            return ~position;
1118        }
1119    }
1120
1121    //-----------------------------------------------------------------------
1122
static abstract class NumberFormatter
1123            implements DateTimePrinter, DateTimeParser {
1124        protected final DateTimeFieldType iFieldType;
1125        protected final int iMaxParsedDigits;
1126        protected final boolean iSigned;
1127
1128        NumberFormatter(DateTimeFieldType fieldType,
1129                int maxParsedDigits, boolean signed) {
1130            super();
1131            iFieldType = fieldType;
1132            iMaxParsedDigits = maxParsedDigits;
1133            iSigned = signed;
1134        }
1135
1136        public int estimateParsedLength() {
1137            return iMaxParsedDigits;
1138        }
1139
1140        public int parseInto(DateTimeParserBucket bucket, String JavaDoc text, int position) {
1141            int limit = Math.min(iMaxParsedDigits, text.length() - position);
1142
1143            boolean negative = false;
1144            int length = 0;
1145            while (length < limit) {
1146                char c = text.charAt(position + length);
1147                if (length == 0 && (c == '-' || c == '+') && iSigned) {
1148                    negative = c == '-';
1149
1150                    // Next character must be a digit.
1151
if (length + 1 >= limit ||
1152                        (c = text.charAt(position + length + 1)) < '0' || c > '9')
1153                    {
1154                        break;
1155                    }
1156
1157                    if (negative) {
1158                        length++;
1159                    } else {
1160                        // Skip the '+' for parseInt to succeed.
1161
position++;
1162                    }
1163                    // Expand the limit to disregard the sign character.
1164
limit = Math.min(limit + 1, text.length() - position);
1165                    continue;
1166                }
1167                if (c < '0' || c > '9') {
1168                    break;
1169                }
1170                length++;
1171            }
1172
1173            if (length == 0) {
1174                return ~position;
1175            }
1176
1177            int value;
1178            if (length >= 9) {
1179                // Since value may exceed integer limits, use stock parser
1180
// which checks for this.
1181
value = Integer.parseInt(text.substring(position, position += length));
1182            } else {
1183                int i = position;
1184                if (negative) {
1185                    i++;
1186                }
1187                try {
1188                    value = text.charAt(i++) - '0';
1189                } catch (StringIndexOutOfBoundsException JavaDoc e) {
1190                    return ~position;
1191                }
1192                position += length;
1193                while (i < position) {
1194                    value = ((value << 3) + (value << 1)) + text.charAt(i++) - '0';
1195                }
1196                if (negative) {
1197                    value = -value;
1198                }
1199            }
1200
1201            bucket.saveField(iFieldType, value);
1202            return position;
1203        }
1204    }
1205
1206    //-----------------------------------------------------------------------
1207
static class UnpaddedNumber extends NumberFormatter {
1208
1209        protected UnpaddedNumber(DateTimeFieldType fieldType,
1210                       int maxParsedDigits, boolean signed)
1211        {
1212            super(fieldType, maxParsedDigits, signed);
1213        }
1214
1215        public int estimatePrintedLength() {
1216            return iMaxParsedDigits;
1217        }
1218
1219        public void printTo(
1220                StringBuffer JavaDoc buf, long instant, Chronology chrono,
1221                int displayOffset, DateTimeZone displayZone, Locale JavaDoc locale) {
1222            try {
1223                DateTimeField field = iFieldType.getField(chrono);
1224                FormatUtils.appendUnpaddedInteger(buf, field.get(instant));
1225            } catch (RuntimeException JavaDoc e) {
1226                buf.append('\ufffd');
1227            }
1228        }
1229
1230        public void printTo(
1231                Writer JavaDoc out, long instant, Chronology chrono,
1232                int displayOffset, DateTimeZone displayZone, Locale JavaDoc locale) throws IOException JavaDoc {
1233            try {
1234                DateTimeField field = iFieldType.getField(chrono);
1235                FormatUtils.writeUnpaddedInteger(out, field.get(instant));
1236            } catch (RuntimeException JavaDoc e) {
1237                out.write('\ufffd');
1238            }
1239        }
1240
1241        public void printTo(StringBuffer JavaDoc buf, ReadablePartial partial, Locale JavaDoc locale) {
1242            if (partial.isSupported(iFieldType)) {
1243                try {
1244                    FormatUtils.appendUnpaddedInteger(buf, partial.get(iFieldType));
1245                } catch (RuntimeException JavaDoc e) {
1246                    buf.append('\ufffd');
1247                }
1248            } else {
1249                buf.append('\ufffd');
1250            }
1251        }
1252
1253        public void printTo(Writer JavaDoc out, ReadablePartial partial, Locale JavaDoc locale) throws IOException JavaDoc {
1254            if (partial.isSupported(iFieldType)) {
1255                try {
1256                    FormatUtils.writeUnpaddedInteger(out, partial.get(iFieldType));
1257                } catch (RuntimeException JavaDoc e) {
1258                    out.write('\ufffd');
1259                }
1260            } else {
1261                out.write('\ufffd');
1262            }
1263        }
1264    }
1265
1266    //-----------------------------------------------------------------------
1267
static class PaddedNumber extends NumberFormatter {
1268
1269        protected final int iMinPrintedDigits;
1270
1271        protected PaddedNumber(DateTimeFieldType fieldType, int maxParsedDigits,
1272                     boolean signed, int minPrintedDigits)
1273        {
1274            super(fieldType, maxParsedDigits, signed);
1275            iMinPrintedDigits = minPrintedDigits;
1276        }
1277
1278        public int estimatePrintedLength() {
1279            return iMaxParsedDigits;
1280        }
1281
1282        public void printTo(
1283                StringBuffer JavaDoc buf, long instant, Chronology chrono,
1284                int displayOffset, DateTimeZone displayZone, Locale JavaDoc locale) {
1285            try {
1286                DateTimeField field = iFieldType.getField(chrono);
1287                FormatUtils.appendPaddedInteger(buf, field.get(instant), iMinPrintedDigits);
1288            } catch (RuntimeException JavaDoc e) {
1289                appendUnknownString(buf, iMinPrintedDigits);
1290            }
1291        }
1292
1293        public void printTo(
1294                Writer JavaDoc out, long instant, Chronology chrono,
1295                int displayOffset, DateTimeZone displayZone, Locale JavaDoc locale) throws IOException JavaDoc {
1296            try {
1297                DateTimeField field = iFieldType.getField(chrono);
1298                FormatUtils.writePaddedInteger(out, field.get(instant), iMinPrintedDigits);
1299            } catch (RuntimeException JavaDoc e) {
1300                printUnknownString(out, iMinPrintedDigits);
1301            }
1302        }
1303
1304        public void printTo(StringBuffer JavaDoc buf, ReadablePartial partial, Locale JavaDoc locale) {
1305            if (partial.isSupported(iFieldType)) {
1306                try {
1307                    FormatUtils.appendPaddedInteger(buf, partial.get(iFieldType), iMinPrintedDigits);
1308                } catch (RuntimeException JavaDoc e) {
1309                    appendUnknownString(buf, iMinPrintedDigits);
1310                }
1311            } else {
1312                appendUnknownString(buf, iMinPrintedDigits);
1313            }
1314        }
1315
1316        public void printTo(Writer JavaDoc out, ReadablePartial partial, Locale JavaDoc locale) throws IOException JavaDoc {
1317            if (partial.isSupported(iFieldType)) {
1318                try {
1319                    FormatUtils.writePaddedInteger(out, partial.get(iFieldType), iMinPrintedDigits);
1320                } catch (RuntimeException JavaDoc e) {
1321                    printUnknownString(out, iMinPrintedDigits);
1322                }
1323            } else {
1324                printUnknownString(out, iMinPrintedDigits);
1325            }
1326        }
1327    }
1328
1329    //-----------------------------------------------------------------------
1330
static class TwoDigitYear
1331            implements DateTimePrinter, DateTimeParser {
1332
1333        /** The field to print/parse. */
1334        private final DateTimeFieldType iType;
1335        /** The pivot year. */
1336        private final int iPivot;
1337        private final boolean iLenientParse;
1338
1339        TwoDigitYear(DateTimeFieldType type, int pivot, boolean lenientParse) {
1340            super();
1341            iType = type;
1342            iPivot = pivot;
1343            iLenientParse = lenientParse;
1344        }
1345
1346        public int estimateParsedLength() {
1347            return iLenientParse ? 4 : 2;
1348        }
1349
1350        public int parseInto(DateTimeParserBucket bucket, String JavaDoc text, int position) {
1351            int limit = text.length() - position;
1352
1353            if (!iLenientParse) {
1354                limit = Math.min(2, limit);
1355                if (limit < 2) {
1356                    return ~position;
1357                }
1358            } else {
1359                boolean hasSignChar = false;
1360                boolean negative = false;
1361                int length = 0;
1362                while (length < limit) {
1363                    char c = text.charAt(position + length);
1364                    if (length == 0 && (c == '-' || c == '+')) {
1365                        hasSignChar = true;
1366                        negative = c == '-';
1367                        if (negative) {
1368                            length++;
1369                        } else {
1370                            // Skip the '+' for parseInt to succeed.
1371
position++;
1372                            limit--;
1373                        }
1374                        continue;
1375                    }
1376                    if (c < '0' || c > '9') {
1377                        break;
1378                    }
1379                    length++;
1380                }
1381                
1382                if (length == 0) {
1383                    return ~position;
1384                }
1385
1386                if (hasSignChar || length != 2) {
1387                    int value;
1388                    if (length >= 9) {
1389                        // Since value may exceed integer limits, use stock
1390
// parser which checks for this.
1391
value = Integer.parseInt(text.substring(position, position += length));
1392                    } else {
1393                        int i = position;
1394                        if (negative) {
1395                            i++;
1396                        }
1397                        try {
1398                            value = text.charAt(i++) - '0';
1399                        } catch (StringIndexOutOfBoundsException JavaDoc e) {
1400                            return ~position;
1401                        }
1402                        position += length;
1403                        while (i < position) {
1404                            value = ((value << 3) + (value << 1)) + text.charAt(i++) - '0';
1405                        }
1406                        if (negative) {
1407                            value = -value;
1408                        }
1409                    }
1410                    
1411                    bucket.saveField(iType, value);
1412                    return position;
1413                }
1414            }
1415
1416            int year;
1417            char c = text.charAt(position);
1418            if (c < '0' || c > '9') {
1419                return ~position;
1420            }
1421            year = c - '0';
1422            c = text.charAt(position + 1);
1423            if (c < '0' || c > '9') {
1424                return ~position;
1425            }
1426            year = ((year << 3) + (year << 1)) + c - '0';
1427
1428            int pivot = iPivot;
1429            // If the bucket pivot year is non-null, use that when parsing
1430
if (bucket.getPivotYear() != null) {
1431                pivot = bucket.getPivotYear().intValue();
1432            }
1433
1434            int low = pivot - 50;
1435
1436            int t;
1437            if (low >= 0) {
1438                t = low % 100;
1439            } else {
1440                t = 99 + ((low + 1) % 100);
1441            }
1442
1443            year += low + ((year < t) ? 100 : 0) - t;
1444
1445            bucket.saveField(iType, year);
1446            return position + 2;
1447        }
1448        
1449        public int estimatePrintedLength() {
1450            return 2;
1451        }
1452
1453        public void printTo(
1454                StringBuffer JavaDoc buf, long instant, Chronology chrono,
1455                int displayOffset, DateTimeZone displayZone, Locale JavaDoc locale) {
1456            int year = getTwoDigitYear(instant, chrono);
1457            if (year < 0) {
1458                buf.append('\ufffd');
1459                buf.append('\ufffd');
1460            } else {
1461                FormatUtils.appendPaddedInteger(buf, year, 2);
1462            }
1463        }
1464
1465        public void printTo(
1466                Writer JavaDoc out, long instant, Chronology chrono,
1467                int displayOffset, DateTimeZone displayZone, Locale JavaDoc locale) throws IOException JavaDoc {
1468            int year = getTwoDigitYear(instant, chrono);
1469            if (year < 0) {
1470                out.write('\ufffd');
1471                out.write('\ufffd');
1472            } else {
1473                FormatUtils.writePaddedInteger(out, year, 2);
1474            }
1475        }
1476
1477        private int getTwoDigitYear(long instant, Chronology chrono) {
1478            try {
1479                int year = iType.getField(chrono).get(instant);
1480                if (year < 0) {
1481                    year = -year;
1482                }
1483                return year % 100;
1484            } catch (RuntimeException JavaDoc e) {
1485                return -1;
1486            }
1487        }
1488
1489        public void printTo(StringBuffer JavaDoc buf, ReadablePartial partial, Locale JavaDoc locale) {
1490            int year = getTwoDigitYear(partial);
1491            if (year < 0) {
1492                buf.append('\ufffd');
1493                buf.append('\ufffd');
1494            } else {
1495                FormatUtils.appendPaddedInteger(buf, year, 2);
1496            }
1497        }
1498
1499        public void printTo(Writer JavaDoc out, ReadablePartial partial, Locale JavaDoc locale) throws IOException JavaDoc {
1500            int year = getTwoDigitYear(partial);
1501            if (year < 0) {
1502                out.write('\ufffd');
1503                out.write('\ufffd');
1504            } else {
1505                FormatUtils.writePaddedInteger(out, year, 2);
1506            }
1507        }
1508
1509        private int getTwoDigitYear(ReadablePartial partial) {
1510            if (partial.isSupported(iType)) {
1511                try {
1512                    int year = partial.get(iType);
1513                    if (year < 0) {
1514                        year = -year;
1515                    }
1516                    return year % 100;
1517                } catch (RuntimeException JavaDoc e) {}
1518            }
1519            return -1;
1520        }
1521    }
1522
1523    //-----------------------------------------------------------------------
1524
static class TextField
1525            implements DateTimePrinter, DateTimeParser {
1526
1527        private final DateTimeFieldType iFieldType;
1528        private final boolean iShort;
1529
1530        TextField(DateTimeFieldType fieldType, boolean isShort) {
1531            super();
1532            iFieldType = fieldType;
1533            iShort = isShort;
1534        }
1535
1536        public int estimatePrintedLength() {
1537            return iShort ? 6 : 20;
1538        }
1539
1540        public void printTo(
1541                StringBuffer JavaDoc buf, long instant, Chronology chrono,
1542                int displayOffset, DateTimeZone displayZone, Locale JavaDoc locale) {
1543            try {
1544                buf.append(print(instant, chrono, locale));
1545            } catch (RuntimeException JavaDoc e) {
1546                buf.append('\ufffd');
1547            }
1548        }
1549
1550        public void printTo(
1551                Writer JavaDoc out, long instant, Chronology chrono,
1552                int displayOffset, DateTimeZone displayZone, Locale JavaDoc locale) throws IOException JavaDoc {
1553            try {
1554                out.write(print(instant, chrono, locale));
1555            } catch (RuntimeException JavaDoc e) {
1556                out.write('\ufffd');
1557            }
1558        }
1559
1560        public void printTo(StringBuffer JavaDoc buf, ReadablePartial partial, Locale JavaDoc locale) {
1561            try {
1562                buf.append(print(partial, locale));
1563            } catch (RuntimeException JavaDoc e) {
1564                buf.append('\ufffd');
1565            }
1566        }
1567
1568        public void printTo(Writer JavaDoc out, ReadablePartial partial, Locale JavaDoc locale) throws IOException JavaDoc {
1569            try {
1570                out.write(print(partial, locale));
1571            } catch (RuntimeException JavaDoc e) {
1572                out.write('\ufffd');
1573            }
1574        }
1575
1576        private String JavaDoc print(long instant, Chronology chrono, Locale JavaDoc locale) {
1577            DateTimeField field = iFieldType.getField(chrono);
1578            if (iShort) {
1579                return field.getAsShortText(instant, locale);
1580            } else {
1581                return field.getAsText(instant, locale);
1582            }
1583        }
1584
1585        private String JavaDoc print(ReadablePartial partial, Locale JavaDoc locale) {
1586            if (partial.isSupported(iFieldType)) {
1587                DateTimeField field = iFieldType.getField(partial.getChronology());
1588                if (iShort) {
1589                    return field.getAsShortText(partial, locale);
1590                } else {
1591                    return field.getAsText(partial, locale);
1592                }
1593            } else {
1594                return "\ufffd";
1595            }
1596        }
1597
1598        public int estimateParsedLength() {
1599            return estimatePrintedLength();
1600        }
1601
1602        public int parseInto(DateTimeParserBucket bucket, String JavaDoc text, int position) {
1603            int limit = text.length();
1604            int i = position;
1605            for (; i<limit; i++) {
1606                char c = text.charAt(i);
1607                if (c < 'A') {
1608                    break;
1609                }
1610                if (c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z' || Character.isLetter(c)) {
1611                    continue;
1612                }
1613                break;
1614            }
1615
1616            if (i == position) {
1617                return ~position;
1618            }
1619
1620            Locale JavaDoc locale = bucket.getLocale();
1621            bucket.saveField(iFieldType, text.substring(position, i), locale);
1622
1623            return i;
1624        }
1625    }
1626
1627    //-----------------------------------------------------------------------
1628
static class Fraction
1629            implements DateTimePrinter, DateTimeParser {
1630
1631        private final DateTimeFieldType iFieldType;
1632        protected int iMinDigits;
1633        protected int iMaxDigits;
1634
1635        protected Fraction(DateTimeFieldType fieldType, int minDigits, int maxDigits) {
1636            super();
1637            iFieldType = fieldType;
1638            // Limit the precision requirements.
1639
if (maxDigits > 18) {
1640                maxDigits = 18;
1641            }
1642            iMinDigits = minDigits;
1643            iMaxDigits = maxDigits;
1644        }
1645
1646        public int estimatePrintedLength() {
1647            return iMaxDigits;
1648        }
1649
1650        public void printTo(
1651                StringBuffer JavaDoc buf, long instant, Chronology chrono,
1652                int displayOffset, DateTimeZone displayZone, Locale JavaDoc locale) {
1653            try {
1654                printTo(buf, null, instant, chrono);
1655            } catch (IOException JavaDoc e) {
1656                // Not gonna happen.
1657
}
1658        }
1659
1660        public void printTo(
1661                Writer JavaDoc out, long instant, Chronology chrono,
1662                int displayOffset, DateTimeZone displayZone, Locale JavaDoc locale) throws IOException JavaDoc {
1663            printTo(null, out, instant, chrono);
1664        }
1665
1666        public void printTo(StringBuffer JavaDoc buf, ReadablePartial partial, Locale JavaDoc locale) {
1667            if (partial.isSupported(iFieldType)) {
1668                long millis = partial.getChronology().set(partial, 0L);
1669                try {
1670                    printTo(buf, null, millis, partial.getChronology());
1671                } catch (IOException JavaDoc e) {
1672                    // Not gonna happen.
1673
}
1674            } else {
1675                buf.append('\ufffd');
1676            }
1677        }
1678
1679        public void printTo(Writer JavaDoc out, ReadablePartial partial, Locale JavaDoc locale) throws IOException JavaDoc {
1680            if (partial.isSupported(iFieldType)) {
1681                long millis = partial.getChronology().set(partial, 0L);
1682                printTo(null, out, millis, partial.getChronology());
1683            } else {
1684                out.write('\ufffd');
1685            }
1686        }
1687
1688        protected void printTo(StringBuffer JavaDoc buf, Writer JavaDoc out, long instant, Chronology chrono)
1689            throws IOException JavaDoc
1690        {
1691            DateTimeField field = iFieldType.getField(chrono);
1692            int minDigits = iMinDigits;
1693
1694            long fraction;
1695            try {
1696                fraction = field.remainder(instant);
1697            } catch (RuntimeException JavaDoc e) {
1698                if (buf != null) {
1699                    appendUnknownString(buf, minDigits);
1700                } else {
1701                    printUnknownString(out, minDigits);
1702                }
1703                return;
1704            }
1705
1706            if (fraction == 0) {
1707                if (buf != null) {
1708                    while (--minDigits >= 0) {
1709                        buf.append('0');
1710                    }
1711                } else {
1712                    while (--minDigits >= 0) {
1713                        out.write('0');
1714                    }
1715                }
1716                return;
1717            }
1718
1719            String JavaDoc str;
1720            long[] fractionData = getFractionData(fraction, field);
1721            long scaled = fractionData[0];
1722            int maxDigits = (int) fractionData[1];
1723            
1724            if ((scaled & 0x7fffffff) == scaled) {
1725                str = Integer.toString((int) scaled);
1726            } else {
1727                str = Long.toString(scaled);
1728            }
1729
1730            int length = str.length();
1731            int digits = maxDigits;
1732            while (length < digits) {
1733                if (buf != null) {
1734                    buf.append('0');
1735                } else {
1736                    out.write('0');
1737                }
1738                minDigits--;
1739                digits--;
1740            }
1741
1742            if (minDigits < digits) {
1743                // Chop off as many trailing zero digits as necessary.
1744
while (minDigits < digits) {
1745                    if (length <= 1 || str.charAt(length - 1) != '0') {
1746                        break;
1747                    }
1748                    digits--;
1749                    length--;
1750                }
1751                if (length < str.length()) {
1752                    if (buf != null) {
1753                        for (int i=0; i<length; i++) {
1754                            buf.append(str.charAt(i));
1755                        }
1756                    } else {
1757                        for (int i=0; i<length; i++) {
1758                            out.write(str.charAt(i));
1759                        }
1760                    }
1761                    return;
1762                }
1763            }
1764
1765            if (buf != null) {
1766                buf.append(str);
1767            } else {
1768                out.write(str);
1769            }
1770        }
1771        
1772        private long[] getFractionData(long fraction, DateTimeField field) {
1773            long rangeMillis = field.getDurationField().getUnitMillis();
1774            long scalar;
1775            int maxDigits = iMaxDigits;
1776            while (true) {
1777                switch (maxDigits) {
1778                default: scalar = 1L; break;
1779                case 1: scalar = 10L; break;
1780                case 2: scalar = 100L; break;
1781                case 3: scalar = 1000L; break;
1782                case 4: scalar = 10000L; break;
1783                case 5: scalar = 100000L; break;
1784                case 6: scalar = 1000000L; break;
1785                case 7: scalar = 10000000L; break;
1786                case 8: scalar = 100000000L; break;
1787                case 9: scalar = 1000000000L; break;
1788                case 10: scalar = 10000000000L; break;
1789                case 11: scalar = 100000000000L; break;
1790                case 12: scalar = 1000000000000L; break;
1791                case 13: scalar = 10000000000000L; break;
1792                case 14: scalar = 100000000000000L; break;
1793                case 15: scalar = 1000000000000000L; break;
1794                case 16: scalar = 10000000000000000L; break;
1795                case 17: scalar = 100000000000000000L; break;
1796                case 18: scalar = 1000000000000000000L; break;
1797                }
1798                if (((rangeMillis * scalar) / scalar) == rangeMillis) {
1799                    break;
1800                }
1801                // Overflowed: scale down.
1802
maxDigits--;
1803            }
1804            
1805            return new long[] {fraction * scalar / rangeMillis, maxDigits};
1806        }
1807
1808        public int estimateParsedLength() {
1809            return iMaxDigits;
1810        }
1811
1812        public int parseInto(DateTimeParserBucket bucket, String JavaDoc text, int position) {
1813            DateTimeField field = iFieldType.getField(bucket.getChronology());
1814            
1815            int limit = Math.min(iMaxDigits, text.length() - position);
1816
1817            long value = 0;
1818            long n = field.getDurationField().getUnitMillis() * 10;
1819            int length = 0;
1820            while (length < limit) {
1821                char c = text.charAt(position + length);
1822                if (c < '0' || c > '9') {
1823                    break;
1824                }
1825                length++;
1826                long nn = n / 10;
1827                value += (c - '0') * nn;
1828                n = nn;
1829            }
1830
1831            value /= 10;
1832
1833            if (length == 0) {
1834                return ~position;
1835            }
1836
1837            if (value > Integer.MAX_VALUE) {
1838                return ~position;
1839            }
1840
1841            DateTimeField parseField = new PreciseDateTimeField(
1842                DateTimeFieldType.millisOfSecond(),
1843                MillisDurationField.INSTANCE,
1844                field.getDurationField());
1845
1846            bucket.saveField(parseField, (int) value);
1847
1848            return position + length;
1849        }
1850    }
1851
1852    //-----------------------------------------------------------------------
1853
static class TimeZoneOffset
1854            implements DateTimePrinter, DateTimeParser {
1855
1856        private final String JavaDoc iZeroOffsetText;
1857        private final boolean iShowSeparators;
1858        private final int iMinFields;
1859        private final int iMaxFields;
1860
1861        TimeZoneOffset(String JavaDoc zeroOffsetText,
1862                                boolean showSeparators,
1863                                int minFields, int maxFields)
1864        {
1865            super();
1866            iZeroOffsetText = zeroOffsetText;
1867            iShowSeparators = showSeparators;
1868            if (minFields <= 0 || maxFields < minFields) {
1869                throw new IllegalArgumentException JavaDoc();
1870            }
1871            if (minFields > 4) {
1872                minFields = 4;
1873                maxFields = 4;
1874            }
1875            iMinFields = minFields;
1876            iMaxFields = maxFields;
1877        }
1878            
1879        public int estimatePrintedLength() {
1880            int est = 1 + iMinFields << 1;
1881            if (iShowSeparators) {
1882                est += iMinFields - 1;
1883            }
1884            if (iZeroOffsetText != null && iZeroOffsetText.length() > est) {
1885                est = iZeroOffsetText.length();
1886            }
1887            return est;
1888        }
1889        
1890        public void printTo(
1891                StringBuffer JavaDoc buf, long instant, Chronology chrono,
1892                int displayOffset, DateTimeZone displayZone, Locale JavaDoc locale) {
1893            if (displayZone == null) {
1894                return; // no zone
1895
}
1896            if (displayOffset == 0 && iZeroOffsetText != null) {
1897                buf.append(iZeroOffsetText);
1898                return;
1899            }
1900            if (displayOffset >= 0) {
1901                buf.append('+');
1902            } else {
1903                buf.append('-');
1904                displayOffset = -displayOffset;
1905            }
1906
1907            int hours = displayOffset / DateTimeConstants.MILLIS_PER_HOUR;
1908            FormatUtils.appendPaddedInteger(buf, hours, 2);
1909            if (iMaxFields == 1) {
1910                return;
1911            }
1912            displayOffset -= hours * (int)DateTimeConstants.MILLIS_PER_HOUR;
1913            if (displayOffset == 0 && iMinFields <= 1) {
1914                return;
1915            }
1916
1917            int minutes = displayOffset / DateTimeConstants.MILLIS_PER_MINUTE;
1918            if (iShowSeparators) {
1919                buf.append(':');
1920            }
1921            FormatUtils.appendPaddedInteger(buf, minutes, 2);
1922            if (iMaxFields == 2) {
1923                return;
1924            }
1925            displayOffset -= minutes * DateTimeConstants.MILLIS_PER_MINUTE;
1926            if (displayOffset == 0 && iMinFields <= 2) {
1927                return;
1928            }
1929
1930            int seconds = displayOffset / DateTimeConstants.MILLIS_PER_SECOND;
1931            if (iShowSeparators) {
1932                buf.append(':');
1933            }
1934            FormatUtils.appendPaddedInteger(buf, seconds, 2);
1935            if (iMaxFields == 3) {
1936                return;
1937            }
1938            displayOffset -= seconds * DateTimeConstants.MILLIS_PER_SECOND;
1939            if (displayOffset == 0 && iMinFields <= 3) {
1940                return;
1941            }
1942
1943            if (iShowSeparators) {
1944                buf.append('.');
1945            }
1946            FormatUtils.appendPaddedInteger(buf, displayOffset, 3);
1947        }
1948        
1949        public void printTo(
1950                Writer JavaDoc out, long instant, Chronology chrono,
1951                int displayOffset, DateTimeZone displayZone, Locale JavaDoc locale) throws IOException JavaDoc {
1952            if (displayZone == null) {
1953                return; // no zone
1954
}
1955            if (displayOffset == 0 && iZeroOffsetText != null) {
1956                out.write(iZeroOffsetText);
1957                return;
1958            }
1959            if (displayOffset >= 0) {
1960                out.write('+');
1961            } else {
1962                out.write('-');
1963                displayOffset = -displayOffset;
1964            }
1965
1966            int hours = displayOffset / DateTimeConstants.MILLIS_PER_HOUR;
1967            FormatUtils.writePaddedInteger(out, hours, 2);
1968            if (iMaxFields == 1) {
1969                return;
1970            }
1971            displayOffset -= hours * (int)DateTimeConstants.MILLIS_PER_HOUR;
1972            if (displayOffset == 0 && iMinFields == 1) {
1973                return;
1974            }
1975
1976            int minutes = displayOffset / DateTimeConstants.MILLIS_PER_MINUTE;
1977            if (iShowSeparators) {
1978                out.write(':');
1979            }
1980            FormatUtils.writePaddedInteger(out, minutes, 2);
1981            if (iMaxFields == 2) {
1982                return;
1983            }
1984            displayOffset -= minutes * DateTimeConstants.MILLIS_PER_MINUTE;
1985            if (displayOffset == 0 && iMinFields == 2) {
1986                return;
1987            }
1988
1989            int seconds = displayOffset / DateTimeConstants.MILLIS_PER_SECOND;
1990            if (iShowSeparators) {
1991                out.write(':');
1992            }
1993            FormatUtils.writePaddedInteger(out, seconds, 2);
1994            if (iMaxFields == 3) {
1995                return;
1996            }
1997            displayOffset -= seconds * DateTimeConstants.MILLIS_PER_SECOND;
1998            if (displayOffset == 0 && iMinFields == 3) {
1999                return;
2000            }
2001
2002            if (iShowSeparators) {
2003                out.write('.');
2004            }
2005            FormatUtils.writePaddedInteger(out, displayOffset, 3);
2006        }
2007
2008        public void printTo(StringBuffer JavaDoc buf, ReadablePartial partial, Locale JavaDoc locale) {
2009            // no zone info
2010
}
2011
2012        public void printTo(Writer JavaDoc out, ReadablePartial partial, Locale JavaDoc locale) throws IOException JavaDoc {
2013            // no zone info
2014
}
2015
2016        public int estimateParsedLength() {
2017            return estimatePrintedLength();
2018        }
2019
2020        public int parseInto(DateTimeParserBucket bucket, String JavaDoc text, int position) {
2021            int limit = text.length() - position;
2022
2023            zeroOffset:
2024            if (iZeroOffsetText != null) {
2025                if (iZeroOffsetText.length() == 0) {
2026                    // Peek ahead, looking for sign character.
2027
if (limit > 0) {
2028                        char c = text.charAt(position);
2029                        if (c == '-' || c == '+') {
2030                            break zeroOffset;
2031                        }
2032                    }
2033                    bucket.setOffset(0);
2034                    return position;
2035                }
2036                if (text.regionMatches(true, position, iZeroOffsetText, 0,
2037                                       iZeroOffsetText.length())) {
2038                    bucket.setOffset(0);
2039                    return position + iZeroOffsetText.length();
2040                }
2041            }
2042
2043            // Format to expect is sign character followed by at least one digit.
2044

2045            if (limit <= 1) {
2046                return ~position;
2047            }
2048
2049            boolean negative;
2050            char c = text.charAt(position);
2051            if (c == '-') {
2052                negative = true;
2053            } else if (c == '+') {
2054                negative = false;
2055            } else {
2056                return ~position;
2057            }
2058
2059            limit--;
2060            position++;
2061
2062            // Format following sign is one of:
2063
//
2064
// hh
2065
// hhmm
2066
// hhmmss
2067
// hhmmssSSS
2068
// hh:mm
2069
// hh:mm:ss
2070
// hh:mm:ss.SSS
2071

2072            // First parse hours.
2073

2074            if (digitCount(text, position, 2) < 2) {
2075                // Need two digits for hour.
2076
return ~position;
2077            }
2078
2079            int offset;
2080
2081            int hours = FormatUtils.parseTwoDigits(text, position);
2082            if (hours > 23) {
2083                return ~position;
2084            }
2085            offset = hours * DateTimeConstants.MILLIS_PER_HOUR;
2086            limit -= 2;
2087            position += 2;
2088
2089            parse: {
2090                // Need to decide now if separators are expected or parsing
2091
// stops at hour field.
2092

2093                if (limit <= 0) {
2094                    break parse;
2095                }
2096
2097                boolean expectSeparators;
2098                c = text.charAt(position);
2099                if (c == ':') {
2100                    expectSeparators = true;
2101                    limit--;
2102                    position++;
2103                } else if (c >= '0' && c <= '9') {
2104                    expectSeparators = false;
2105                } else {
2106                    break parse;
2107                }
2108
2109                // Proceed to parse minutes.
2110

2111                int count = digitCount(text, position, 2);
2112                if (count == 0 && !expectSeparators) {
2113                    break parse;
2114                } else if (count < 2) {
2115                    // Need two digits for minute.
2116
return ~position;
2117                }
2118
2119                int minutes = FormatUtils.parseTwoDigits(text, position);
2120                if (minutes > 59) {
2121                    return ~position;
2122                }
2123                offset += minutes * DateTimeConstants.MILLIS_PER_MINUTE;
2124                limit -= 2;
2125                position += 2;
2126
2127                // Proceed to parse seconds.
2128

2129                if (limit <= 0) {
2130                    break parse;
2131                }
2132
2133                if (expectSeparators) {
2134                    if (text.charAt(position) != ':') {
2135                        break parse;
2136                    }
2137                    limit--;
2138                    position++;
2139                }
2140
2141                count = digitCount(text, position, 2);
2142                if (count == 0 && !expectSeparators) {
2143                    break parse;
2144                } else if (count < 2) {
2145                    // Need two digits for second.
2146
return ~position;
2147                }
2148
2149                int seconds = FormatUtils.parseTwoDigits(text, position);
2150                if (seconds > 59) {
2151                    return ~position;
2152                }
2153                offset += seconds * DateTimeConstants.MILLIS_PER_SECOND;
2154                limit -= 2;
2155                position += 2;
2156
2157                // Proceed to parse fraction of second.
2158

2159                if (limit <= 0) {
2160                    break parse;
2161                }
2162
2163                if (expectSeparators) {
2164                    if (text.charAt(position) != '.' && text.charAt(position) != ',') {
2165                        break parse;
2166                    }
2167                    limit--;
2168                    position++;
2169                }
2170                
2171                count = digitCount(text, position, 3);
2172                if (count == 0 && !expectSeparators) {
2173                    break parse;
2174                } else if (count < 1) {
2175                    // Need at least one digit for fraction of second.
2176
return ~position;
2177                }
2178
2179                offset += (text.charAt(position++) - '0') * 100;
2180                if (count > 1) {
2181                    offset += (text.charAt(position++) - '0') * 10;
2182                    if (count > 2) {
2183                        offset += text.charAt(position++) - '0';
2184                    }
2185                }
2186            }
2187
2188            bucket.setOffset(negative ? -offset : offset);
2189            return position;
2190        }
2191
2192        /**
2193         * Returns actual amount of digits to parse, but no more than original
2194         * 'amount' parameter.
2195         */

2196        private int digitCount(String JavaDoc text, int position, int amount) {
2197            int limit = Math.min(text.length() - position, amount);
2198            amount = 0;
2199            for (; limit > 0; limit--) {
2200                char c = text.charAt(position + amount);
2201                if (c < '0' || c > '9') {
2202                    break;
2203                }
2204                amount++;
2205            }
2206            return amount;
2207        }
2208    }
2209
2210    //-----------------------------------------------------------------------
2211
static class TimeZoneName
2212            implements DateTimePrinter {
2213
2214        static final int LONG_NAME = 0;
2215        static final int SHORT_NAME = 1;
2216        static final int ID = 2;
2217
2218        private final int iType;
2219
2220        TimeZoneName(int type) {
2221            super();
2222            iType = type;
2223        }
2224
2225        public int estimatePrintedLength() {
2226            return (iType == SHORT_NAME ? 4 : 20);
2227        }
2228
2229        public void printTo(
2230                StringBuffer JavaDoc buf, long instant, Chronology chrono,
2231                int displayOffset, DateTimeZone displayZone, Locale JavaDoc locale) {
2232            buf.append(print(instant - displayOffset, displayZone, locale));
2233        }
2234
2235        public void printTo(
2236                Writer JavaDoc out, long instant, Chronology chrono,
2237                int displayOffset, DateTimeZone displayZone, Locale JavaDoc locale) throws IOException JavaDoc {
2238            out.write(print(instant - displayOffset, displayZone, locale));
2239        }
2240
2241        private String JavaDoc print(long instant, DateTimeZone displayZone, Locale JavaDoc locale) {
2242            if (displayZone == null) {
2243                return ""; // no zone
2244
}
2245            switch (iType) {
2246                case LONG_NAME:
2247                    return displayZone.getName(instant, locale);
2248                case SHORT_NAME:
2249                    return displayZone.getShortName(instant, locale);
2250                case ID:
2251                    return displayZone.getID();
2252            }
2253            return "";
2254        }
2255
2256        public void printTo(StringBuffer JavaDoc buf, ReadablePartial partial, Locale JavaDoc locale) {
2257            // no zone info
2258
}
2259
2260        public void printTo(Writer JavaDoc out, ReadablePartial partial, Locale JavaDoc locale) throws IOException JavaDoc {
2261            // no zone info
2262
}
2263    }
2264
2265    //-----------------------------------------------------------------------
2266
static class Composite
2267            implements DateTimePrinter, DateTimeParser {
2268
2269        private final DateTimePrinter[] iPrinters;
2270        private final DateTimeParser[] iParsers;
2271
2272        private final int iPrintedLengthEstimate;
2273        private final int iParsedLengthEstimate;
2274
2275        Composite(List JavaDoc elementPairs) {
2276            super();
2277
2278            List JavaDoc printerList = new ArrayList JavaDoc();
2279            List JavaDoc parserList = new ArrayList JavaDoc();
2280
2281            decompose(elementPairs, printerList, parserList);
2282
2283            if (printerList.size() <= 0) {
2284                iPrinters = null;
2285                iPrintedLengthEstimate = 0;
2286            } else {
2287                int size = printerList.size();
2288                iPrinters = new DateTimePrinter[size];
2289                int printEst = 0;
2290                for (int i=0; i<size; i++) {
2291                    DateTimePrinter printer = (DateTimePrinter) printerList.get(i);
2292                    printEst += printer.estimatePrintedLength();
2293                    iPrinters[i] = printer;
2294                }
2295                iPrintedLengthEstimate = printEst;
2296            }
2297
2298            if (parserList.size() <= 0) {
2299                iParsers = null;
2300                iParsedLengthEstimate = 0;
2301            } else {
2302                int size = parserList.size();
2303                iParsers = new DateTimeParser[size];
2304                int parseEst = 0;
2305                for (int i=0; i<size; i++) {
2306                    DateTimeParser parser = (DateTimeParser) parserList.get(i);
2307                    parseEst += parser.estimateParsedLength();
2308                    iParsers[i] = parser;
2309                }
2310                iParsedLengthEstimate = parseEst;
2311            }
2312        }
2313
2314        public int estimatePrintedLength() {
2315            return iPrintedLengthEstimate;
2316        }
2317
2318        public void printTo(
2319                StringBuffer JavaDoc buf, long instant, Chronology chrono,
2320                int displayOffset, DateTimeZone displayZone, Locale JavaDoc locale) {
2321            DateTimePrinter[] elements = iPrinters;
2322            if (elements == null) {
2323                throw new UnsupportedOperationException JavaDoc();
2324            }
2325
2326            int len = elements.length;
2327            for (int i = 0; i < len; i++) {
2328                elements[i].printTo(buf, instant, chrono, displayOffset, displayZone, locale);
2329            }
2330        }
2331
2332        public void printTo(
2333                Writer JavaDoc out, long instant, Chronology chrono,
2334                int displayOffset, DateTimeZone displayZone, Locale JavaDoc locale) throws IOException JavaDoc {
2335            DateTimePrinter[] elements = iPrinters;
2336            if (elements == null) {
2337                throw new UnsupportedOperationException JavaDoc();
2338            }
2339
2340            int len = elements.length;
2341            for (int i = 0; i < len; i++) {
2342                elements[i].printTo(out, instant, chrono, displayOffset, displayZone, locale);
2343            }
2344        }
2345
2346        public void printTo(StringBuffer JavaDoc buf, ReadablePartial partial, Locale JavaDoc locale) {
2347            DateTimePrinter[] elements = iPrinters;
2348            if (elements == null) {
2349                throw new UnsupportedOperationException JavaDoc();
2350            }
2351
2352            int len = elements.length;
2353            for (int i=0; i<len; i++) {
2354                elements[i].printTo(buf, partial, locale);
2355            }
2356        }
2357
2358        public void printTo(Writer JavaDoc out, ReadablePartial partial, Locale JavaDoc locale) throws IOException JavaDoc {
2359            DateTimePrinter[] elements = iPrinters;
2360            if (elements == null) {
2361                throw new UnsupportedOperationException JavaDoc();
2362            }
2363
2364            int len = elements.length;
2365            for (int i=0; i<len; i++) {
2366                elements[i].printTo(out, partial, locale);
2367            }
2368        }
2369
2370        public int estimateParsedLength() {
2371            return iParsedLengthEstimate;
2372        }
2373
2374        public int parseInto(DateTimeParserBucket bucket, String JavaDoc text, int position) {
2375            DateTimeParser[] elements = iParsers;
2376            if (elements == null) {
2377                throw new UnsupportedOperationException JavaDoc();
2378            }
2379
2380            int len = elements.length;
2381            for (int i=0; i<len && position >= 0; i++) {
2382                position = elements[i].parseInto(bucket, text, position);
2383            }
2384            return position;
2385        }
2386
2387        boolean isPrinter() {
2388            return iPrinters != null;
2389        }
2390
2391        boolean isParser() {
2392            return iParsers != null;
2393        }
2394
2395        /**
2396         * Processes the element pairs, putting results into the given printer
2397         * and parser lists.
2398         */

2399        private void decompose(List JavaDoc elementPairs, List JavaDoc printerList, List JavaDoc parserList) {
2400            int size = elementPairs.size();
2401            for (int i=0; i<size; i+=2) {
2402                Object JavaDoc element = elementPairs.get(i);
2403                if (element instanceof DateTimePrinter) {
2404                    if (element instanceof Composite) {
2405                        addArrayToList(printerList, ((Composite)element).iPrinters);
2406                    } else {
2407                        printerList.add(element);
2408                    }
2409                }
2410
2411                element = elementPairs.get(i + 1);
2412                if (element instanceof DateTimeParser) {
2413                    if (element instanceof Composite) {
2414                        addArrayToList(parserList, ((Composite)element).iParsers);
2415                    } else {
2416                        parserList.add(element);
2417                    }
2418                }
2419            }
2420        }
2421
2422        private void addArrayToList(List JavaDoc list, Object JavaDoc[] array) {
2423            if (array != null) {
2424                for (int i=0; i<array.length; i++) {
2425                    list.add(array[i]);
2426                }
2427            }
2428        }
2429    }
2430
2431    //-----------------------------------------------------------------------
2432
static class MatchingParser
2433            implements DateTimeParser {
2434
2435        private final DateTimeParser[] iParsers;
2436        private final int iParsedLengthEstimate;
2437
2438        MatchingParser(DateTimeParser[] parsers) {
2439            super();
2440            iParsers = parsers;
2441            int est = 0;
2442            for (int i=parsers.length; --i>=0 ;) {
2443                DateTimeParser parser = parsers[i];
2444                if (parser != null) {
2445                    int len = parser.estimateParsedLength();
2446                    if (len > est) {
2447                        est = len;
2448                    }
2449                }
2450            }
2451            iParsedLengthEstimate = est;
2452        }
2453
2454        public int estimateParsedLength() {
2455            return iParsedLengthEstimate;
2456        }
2457
2458        public int parseInto(DateTimeParserBucket bucket, String JavaDoc text, int position) {
2459            DateTimeParser[] parsers = iParsers;
2460            int length = parsers.length;
2461
2462            final Object JavaDoc originalState = bucket.saveState();
2463            boolean isOptional = false;
2464
2465            int bestValidPos = position;
2466            Object JavaDoc bestValidState = null;
2467
2468            int bestInvalidPos = position;
2469
2470            for (int i=0; i<length; i++) {
2471                DateTimeParser parser = parsers[i];
2472                if (parser == null) {
2473                    // The empty parser wins only if nothing is better.
2474
if (bestValidPos <= position) {
2475                        return position;
2476                    }
2477                    isOptional = true;
2478                    break;
2479                }
2480                int parsePos = parser.parseInto(bucket, text, position);
2481                if (parsePos >= position) {
2482                    if (parsePos > bestValidPos) {
2483                        if (parsePos >= text.length() ||
2484                            (i + 1) >= length || parsers[i + 1] == null) {
2485
2486                            // Completely parsed text or no more parsers to
2487
// check. Skip the rest.
2488
return parsePos;
2489                        }
2490                        bestValidPos = parsePos;
2491                        bestValidState = bucket.saveState();
2492                    }
2493                } else {
2494                    if (parsePos < 0) {
2495                        parsePos = ~parsePos;
2496                        if (parsePos > bestInvalidPos) {
2497                            bestInvalidPos = parsePos;
2498                        }
2499                    }
2500                }
2501                bucket.restoreState(originalState);
2502            }
2503
2504            if (bestValidPos > position || (bestValidPos == position && isOptional)) {
2505                // Restore the state to the best valid parse.
2506
if (bestValidState != null) {
2507                    bucket.restoreState(bestValidState);
2508                }
2509                return bestValidPos;
2510            }
2511
2512            return ~bestInvalidPos;
2513        }
2514    }
2515
2516}
2517
Popular Tags