View Javadoc

1   /*
2    *  Copyright 2001-2011 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;
19  import java.io.Writer;
20  import java.util.ArrayList;
21  import java.util.HashMap;
22  import java.util.HashSet;
23  import java.util.List;
24  import java.util.Locale;
25  import java.util.Map;
26  import java.util.Set;
27  
28  import org.joda.time.Chronology;
29  import org.joda.time.DateTimeConstants;
30  import org.joda.time.DateTimeField;
31  import org.joda.time.DateTimeFieldType;
32  import org.joda.time.DateTimeUtils;
33  import org.joda.time.DateTimeZone;
34  import org.joda.time.MutableDateTime;
35  import org.joda.time.ReadablePartial;
36  import org.joda.time.MutableDateTime.Property;
37  import org.joda.time.field.MillisDurationField;
38  import org.joda.time.field.PreciseDateTimeField;
39  
40  /**
41   * Factory that creates complex instances of DateTimeFormatter via method calls.
42   * <p>
43   * Datetime formatting is performed by the {@link DateTimeFormatter} class.
44   * Three classes provide factory methods to create formatters, and this is one.
45   * The others are {@link DateTimeFormat} and {@link ISODateTimeFormat}.
46   * <p>
47   * DateTimeFormatterBuilder is used for constructing formatters which are then
48   * used to print or parse. The formatters are built by appending specific fields
49   * or other formatters to an instance of this builder.
50   * <p>
51   * For example, a formatter that prints month and year, like "January 1970",
52   * can be constructed as follows:
53   * <p>
54   * <pre>
55   * DateTimeFormatter monthAndYear = new DateTimeFormatterBuilder()
56   *     .appendMonthOfYearText()
57   *     .appendLiteral(' ')
58   *     .appendYear(4, 4)
59   *     .toFormatter();
60   * </pre>
61   * <p>
62   * DateTimeFormatterBuilder itself is mutable and not thread-safe, but the
63   * formatters that it builds are thread-safe and immutable.
64   *
65   * @author Brian S O'Neill
66   * @author Stephen Colebourne
67   * @author Fredrik Borgh
68   * @since 1.0
69   * @see DateTimeFormat
70   * @see ISODateTimeFormat
71   */
72  public class DateTimeFormatterBuilder {
73  
74      /** Array of printers and parsers (alternating). */
75      private ArrayList<Object> iElementPairs;
76      /** Cache of the last returned formatter. */
77      private Object iFormatter;
78  
79      //-----------------------------------------------------------------------
80      /**
81       * Creates a DateTimeFormatterBuilder.
82       */
83      public DateTimeFormatterBuilder() {
84          super();
85          iElementPairs = new ArrayList<Object>();
86      }
87  
88      //-----------------------------------------------------------------------
89      /**
90       * Constructs a DateTimeFormatter using all the appended elements.
91       * <p>
92       * This is the main method used by applications at the end of the build
93       * process to create a usable formatter.
94       * <p>
95       * Subsequent changes to this builder do not affect the returned formatter.
96       * <p>
97       * The returned formatter may not support both printing and parsing.
98       * The methods {@link DateTimeFormatter#isPrinter()} and
99       * {@link DateTimeFormatter#isParser()} will help you determine the state
100      * of the formatter.
101      *
102      * @throws UnsupportedOperationException if neither printing nor parsing is supported
103      */
104     public DateTimeFormatter toFormatter() {
105         Object f = getFormatter();
106         DateTimePrinter printer = null;
107         if (isPrinter(f)) {
108             printer = (DateTimePrinter) f;
109         }
110         DateTimeParser parser = null;
111         if (isParser(f)) {
112             parser = (DateTimeParser) f;
113         }
114         if (printer != null || parser != null) {
115             return new DateTimeFormatter(printer, parser);
116         }
117         throw new UnsupportedOperationException("Both printing and parsing not supported");
118     }
119 
120     /**
121      * Internal method to create a DateTimePrinter instance using all the
122      * appended elements.
123      * <p>
124      * Most applications will not use this method.
125      * If you want a printer in an application, call {@link #toFormatter()}
126      * and just use the printing API.
127      * <p>
128      * Subsequent changes to this builder do not affect the returned printer.
129      *
130      * @throws UnsupportedOperationException if printing is not supported
131      */
132     public DateTimePrinter toPrinter() {
133         Object f = getFormatter();
134         if (isPrinter(f)) {
135             return (DateTimePrinter) f;
136         }
137         throw new UnsupportedOperationException("Printing is not supported");
138     }
139 
140     /**
141      * Internal method to create a DateTimeParser instance using all the
142      * appended elements.
143      * <p>
144      * Most applications will not use this method.
145      * If you want a parser in an application, call {@link #toFormatter()}
146      * and just use the parsing API.
147      * <p>
148      * Subsequent changes to this builder do not affect the returned parser.
149      *
150      * @throws UnsupportedOperationException if parsing is not supported
151      */
152     public DateTimeParser toParser() {
153         Object f = getFormatter();
154         if (isParser(f)) {
155             return (DateTimeParser) f;
156         }
157         throw new UnsupportedOperationException("Parsing is not supported");
158     }
159 
160     //-----------------------------------------------------------------------
161     /**
162      * Returns true if toFormatter can be called without throwing an
163      * UnsupportedOperationException.
164      * 
165      * @return true if a formatter can be built
166      */
167     public boolean canBuildFormatter() {
168         return isFormatter(getFormatter());
169     }
170 
171     /**
172      * Returns true if toPrinter can be called without throwing an
173      * UnsupportedOperationException.
174      * 
175      * @return true if a printer can be built
176      */
177     public boolean canBuildPrinter() {
178         return isPrinter(getFormatter());
179     }
180 
181     /**
182      * Returns true if toParser can be called without throwing an
183      * UnsupportedOperationException.
184      * 
185      * @return true if a parser can be built
186      */
187     public boolean canBuildParser() {
188         return isParser(getFormatter());
189     }
190 
191     //-----------------------------------------------------------------------
192     /**
193      * Clears out all the appended elements, allowing this builder to be
194      * reused.
195      */
196     public void clear() {
197         iFormatter = null;
198         iElementPairs.clear();
199     }
200 
201     //-----------------------------------------------------------------------
202     /**
203      * Appends another formatter.
204      * <p>
205      * This extracts the underlying printer and parser and appends them
206      * The printer and parser interfaces are the low-level part of the formatting API.
207      * Normally, instances are extracted from another formatter.
208      * Note however that any formatter specific information, such as the locale,
209      * time-zone, chronology, offset parsing or pivot/default year, will not be
210      * extracted by this method.
211      *
212      * @param formatter  the formatter to add
213      * @return this DateTimeFormatterBuilder, for chaining
214      * @throws IllegalArgumentException if formatter is null or of an invalid type
215      */
216     public DateTimeFormatterBuilder append(DateTimeFormatter formatter) {
217         if (formatter == null) {
218             throw new IllegalArgumentException("No formatter supplied");
219         }
220         return append0(formatter.getPrinter(), formatter.getParser());
221     }
222 
223     /**
224      * Appends just a printer. With no matching parser, a parser cannot be
225      * built from this DateTimeFormatterBuilder.
226      * <p>
227      * The printer interface is part of the low-level part of the formatting API.
228      * Normally, instances are extracted from another formatter.
229      * Note however that any formatter specific information, such as the locale,
230      * time-zone, chronology, offset parsing or pivot/default year, will not be
231      * extracted by this method.
232      *
233      * @param printer  the printer to add, not null
234      * @return this DateTimeFormatterBuilder, for chaining
235      * @throws IllegalArgumentException if printer is null or of an invalid type
236      */
237     public DateTimeFormatterBuilder append(DateTimePrinter printer) {
238         checkPrinter(printer);
239         return append0(printer, null);
240     }
241 
242     /**
243      * Appends just a parser. With no matching printer, a printer cannot be
244      * built from this builder.
245      * <p>
246      * The parser interface is part of the low-level part of the formatting API.
247      * Normally, instances are extracted from another formatter.
248      * Note however that any formatter specific information, such as the locale,
249      * time-zone, chronology, offset parsing or pivot/default year, will not be
250      * extracted by this method.
251      *
252      * @param parser  the parser to add, not null
253      * @return this DateTimeFormatterBuilder, for chaining
254      * @throws IllegalArgumentException if parser is null or of an invalid type
255      */
256     public DateTimeFormatterBuilder append(DateTimeParser parser) {
257         checkParser(parser);
258         return append0(null, parser);
259     }
260 
261     /**
262      * Appends a printer/parser pair.
263      * <p>
264      * The printer and parser interfaces are the low-level part of the formatting API.
265      * Normally, instances are extracted from another formatter.
266      * Note however that any formatter specific information, such as the locale,
267      * time-zone, chronology, offset parsing or pivot/default year, will not be
268      * extracted by this method.
269      *
270      * @param printer  the printer to add, not null
271      * @param parser  the parser to add, not null
272      * @return this DateTimeFormatterBuilder, for chaining
273      * @throws IllegalArgumentException if printer or parser is null or of an invalid type
274      */
275     public DateTimeFormatterBuilder append(DateTimePrinter printer, DateTimeParser parser) {
276         checkPrinter(printer);
277         checkParser(parser);
278         return append0(printer, parser);
279     }
280 
281     /**
282      * Appends a printer and a set of matching parsers. When parsing, the first
283      * parser in the list is selected for parsing. If it fails, the next is
284      * chosen, and so on. If none of these parsers succeeds, then the failed
285      * position of the parser that made the greatest progress is returned.
286      * <p>
287      * Only the printer is optional. In addition, it is illegal for any but the
288      * last of the parser array elements to be null. If the last element is
289      * null, this represents the empty parser. The presence of an empty parser
290      * indicates that the entire array of parse formats is optional.
291      * <p>
292      * The printer and parser interfaces are the low-level part of the formatting API.
293      * Normally, instances are extracted from another formatter.
294      * Note however that any formatter specific information, such as the locale,
295      * time-zone, chronology, offset parsing or pivot/default year, will not be
296      * extracted by this method.
297      *
298      * @param printer  the printer to add
299      * @param parsers  the parsers to add
300      * @return this DateTimeFormatterBuilder, for chaining
301      * @throws IllegalArgumentException if any printer or parser is of an invalid type
302      * @throws IllegalArgumentException if any parser element but the last is null
303      */
304     public DateTimeFormatterBuilder append(DateTimePrinter printer, DateTimeParser[] parsers) {
305         if (printer != null) {
306             checkPrinter(printer);
307         }
308         if (parsers == null) {
309             throw new IllegalArgumentException("No parsers supplied");
310         }
311         int length = parsers.length;
312         if (length == 1) {
313             if (parsers[0] == null) {
314                 throw new IllegalArgumentException("No parser supplied");
315             }
316             return append0(printer, parsers[0]);
317         }
318 
319         DateTimeParser[] copyOfParsers = new DateTimeParser[length];
320         int i;
321         for (i = 0; i < length - 1; i++) {
322             if ((copyOfParsers[i] = parsers[i]) == null) {
323                 throw new IllegalArgumentException("Incomplete parser array");
324             }
325         }
326         copyOfParsers[i] = parsers[i];
327 
328         return append0(printer, new MatchingParser(copyOfParsers));
329     }
330 
331     /**
332      * Appends just a parser element which is optional. With no matching
333      * printer, a printer cannot be built from this DateTimeFormatterBuilder.
334      * <p>
335      * The parser interface is part of the low-level part of the formatting API.
336      * Normally, instances are extracted from another formatter.
337      * Note however that any formatter specific information, such as the locale,
338      * time-zone, chronology, offset parsing or pivot/default year, will not be
339      * extracted by this method.
340      *
341      * @return this DateTimeFormatterBuilder, for chaining
342      * @throws IllegalArgumentException if parser is null or of an invalid type
343      */
344     public DateTimeFormatterBuilder appendOptional(DateTimeParser parser) {
345         checkParser(parser);
346         DateTimeParser[] parsers = new DateTimeParser[] {parser, null};
347         return append0(null, new MatchingParser(parsers));
348     }
349 
350     //-----------------------------------------------------------------------
351     /**
352      * Checks if the parser is non null and a provider.
353      * 
354      * @param parser  the parser to check
355      */
356     private void checkParser(DateTimeParser parser) {
357         if (parser == null) {
358             throw new IllegalArgumentException("No parser supplied");
359         }
360     }
361 
362     /**
363      * Checks if the printer is non null and a provider.
364      * 
365      * @param printer  the printer to check
366      */
367     private void checkPrinter(DateTimePrinter printer) {
368         if (printer == null) {
369             throw new IllegalArgumentException("No printer supplied");
370         }
371     }
372 
373     private DateTimeFormatterBuilder append0(Object element) {
374         iFormatter = null;
375         // Add the element as both a printer and parser.
376         iElementPairs.add(element);
377         iElementPairs.add(element);
378         return this;
379     }
380 
381     private DateTimeFormatterBuilder append0(
382             DateTimePrinter printer, DateTimeParser parser) {
383         iFormatter = null;
384         iElementPairs.add(printer);
385         iElementPairs.add(parser);
386         return this;
387     }
388 
389     //-----------------------------------------------------------------------
390     /**
391      * Instructs the printer to emit a specific character, and the parser to
392      * expect it. The parser is case-insensitive.
393      *
394      * @return this DateTimeFormatterBuilder, for chaining
395      */
396     public DateTimeFormatterBuilder appendLiteral(char c) {
397         return append0(new CharacterLiteral(c));
398     }
399 
400     /**
401      * Instructs the printer to emit specific text, and the parser to expect
402      * it. The parser is case-insensitive.
403      *
404      * @return this DateTimeFormatterBuilder, for chaining
405      * @throws IllegalArgumentException if text is null
406      */
407     public DateTimeFormatterBuilder appendLiteral(String text) {
408         if (text == null) {
409             throw new IllegalArgumentException("Literal must not be null");
410         }
411         switch (text.length()) {
412             case 0:
413                 return this;
414             case 1:
415                 return append0(new CharacterLiteral(text.charAt(0)));
416             default:
417                 return append0(new StringLiteral(text));
418         }
419     }
420 
421     /**
422      * Instructs the printer to emit a field value as a decimal number, and the
423      * parser to expect an unsigned decimal number.
424      *
425      * @param fieldType  type of field to append
426      * @param minDigits  minimum number of digits to <i>print</i>
427      * @param maxDigits  maximum number of digits to <i>parse</i>, or the estimated
428      * maximum number of digits to print
429      * @return this DateTimeFormatterBuilder, for chaining
430      * @throws IllegalArgumentException if field type is null
431      */
432     public DateTimeFormatterBuilder appendDecimal(
433             DateTimeFieldType fieldType, int minDigits, int maxDigits) {
434         if (fieldType == null) {
435             throw new IllegalArgumentException("Field type must not be null");
436         }
437         if (maxDigits < minDigits) {
438             maxDigits = minDigits;
439         }
440         if (minDigits < 0 || maxDigits <= 0) {
441             throw new IllegalArgumentException();
442         }
443         if (minDigits <= 1) {
444             return append0(new UnpaddedNumber(fieldType, maxDigits, false));
445         } else {
446             return append0(new PaddedNumber(fieldType, maxDigits, false, minDigits));
447         }
448     }
449 
450     /**
451      * Instructs the printer to emit a field value as a fixed-width decimal
452      * number (smaller numbers will be left-padded with zeros), and the parser
453      * to expect an unsigned decimal number with the same fixed width.
454      * 
455      * @param fieldType  type of field to append
456      * @param numDigits  the exact number of digits to parse or print, except if
457      * printed value requires more digits
458      * @return this DateTimeFormatterBuilder, for chaining
459      * @throws IllegalArgumentException if field type is null or if <code>numDigits <= 0</code>
460      * @since 1.5
461      */
462     public DateTimeFormatterBuilder appendFixedDecimal(
463             DateTimeFieldType fieldType, int numDigits) {
464         if (fieldType == null) {
465             throw new IllegalArgumentException("Field type must not be null");
466         }
467         if (numDigits <= 0) {
468             throw new IllegalArgumentException("Illegal number of digits: " + numDigits);
469         }
470         return append0(new FixedNumber(fieldType, numDigits, false));
471     }
472 
473     /**
474      * Instructs the printer to emit a field value as a decimal number, and the
475      * parser to expect a signed decimal number.
476      *
477      * @param fieldType  type of field to append
478      * @param minDigits  minimum number of digits to <i>print</i>
479      * @param maxDigits  maximum number of digits to <i>parse</i>, or the estimated
480      * maximum number of digits to print
481      * @return this DateTimeFormatterBuilder, for chaining
482      * @throws IllegalArgumentException if field type is null
483      */
484     public DateTimeFormatterBuilder appendSignedDecimal(
485             DateTimeFieldType fieldType, int minDigits, int maxDigits) {
486         if (fieldType == null) {
487             throw new IllegalArgumentException("Field type must not be null");
488         }
489         if (maxDigits < minDigits) {
490             maxDigits = minDigits;
491         }
492         if (minDigits < 0 || maxDigits <= 0) {
493             throw new IllegalArgumentException();
494         }
495         if (minDigits <= 1) {
496             return append0(new UnpaddedNumber(fieldType, maxDigits, true));
497         } else {
498             return append0(new PaddedNumber(fieldType, maxDigits, true, minDigits));
499         }
500     }
501 
502     /**
503      * Instructs the printer to emit a field value as a fixed-width decimal
504      * number (smaller numbers will be left-padded with zeros), and the parser
505      * to expect an signed decimal number with the same fixed width.
506      * 
507      * @param fieldType  type of field to append
508      * @param numDigits  the exact number of digits to parse or print, except if
509      * printed value requires more digits
510      * @return this DateTimeFormatterBuilder, for chaining
511      * @throws IllegalArgumentException if field type is null or if <code>numDigits <= 0</code>
512      * @since 1.5
513      */
514     public DateTimeFormatterBuilder appendFixedSignedDecimal(
515             DateTimeFieldType fieldType, int numDigits) {
516         if (fieldType == null) {
517             throw new IllegalArgumentException("Field type must not be null");
518         }
519         if (numDigits <= 0) {
520             throw new IllegalArgumentException("Illegal number of digits: " + numDigits);
521         }
522         return append0(new FixedNumber(fieldType, numDigits, true));
523     }
524 
525     /**
526      * Instructs the printer to emit a field value as text, and the
527      * parser to expect text.
528      *
529      * @param fieldType  type of field to append
530      * @return this DateTimeFormatterBuilder, for chaining
531      * @throws IllegalArgumentException if field type is null
532      */
533     public DateTimeFormatterBuilder appendText(DateTimeFieldType fieldType) {
534         if (fieldType == null) {
535             throw new IllegalArgumentException("Field type must not be null");
536         }
537         return append0(new TextField(fieldType, false));
538     }
539 
540     /**
541      * Instructs the printer to emit a field value as short text, and the
542      * parser to expect text.
543      *
544      * @param fieldType  type of field to append
545      * @return this DateTimeFormatterBuilder, for chaining
546      * @throws IllegalArgumentException if field type is null
547      */
548     public DateTimeFormatterBuilder appendShortText(DateTimeFieldType fieldType) {
549         if (fieldType == null) {
550             throw new IllegalArgumentException("Field type must not be null");
551         }
552         return append0(new TextField(fieldType, true));
553     }
554 
555     /**
556      * Instructs the printer to emit a remainder of time as a decimal fraction,
557      * without decimal point. For example, if the field is specified as
558      * minuteOfHour and the time is 12:30:45, the value printed is 75. A
559      * decimal point is implied, so the fraction is 0.75, or three-quarters of
560      * a minute.
561      *
562      * @param fieldType  type of field to append
563      * @param minDigits  minimum number of digits to print.
564      * @param maxDigits  maximum number of digits to print or parse.
565      * @return this DateTimeFormatterBuilder, for chaining
566      * @throws IllegalArgumentException if field type is null
567      */
568     public DateTimeFormatterBuilder appendFraction(
569             DateTimeFieldType fieldType, int minDigits, int maxDigits) {
570         if (fieldType == null) {
571             throw new IllegalArgumentException("Field type must not be null");
572         }
573         if (maxDigits < minDigits) {
574             maxDigits = minDigits;
575         }
576         if (minDigits < 0 || maxDigits <= 0) {
577             throw new IllegalArgumentException();
578         }
579         return append0(new Fraction(fieldType, minDigits, maxDigits));
580     }
581 
582     /**
583      * Appends the print/parse of a fractional second.
584      * <p>
585      * This reliably handles the case where fractional digits are being handled
586      * beyond a visible decimal point. The digits parsed will always be treated
587      * as the most significant (numerically largest) digits.
588      * Thus '23' will be parsed as 230 milliseconds.
589      * Contrast this behaviour to {@link #appendMillisOfSecond}.
590      * This method does not print or parse the decimal point itself.
591      * 
592      * @param minDigits  minimum number of digits to print
593      * @param maxDigits  maximum number of digits to print or parse
594      * @return this DateTimeFormatterBuilder, for chaining
595      */
596     public DateTimeFormatterBuilder appendFractionOfSecond(int minDigits, int maxDigits) {
597         return appendFraction(DateTimeFieldType.secondOfDay(), minDigits, maxDigits);
598     }
599 
600     /**
601      * Appends the print/parse of a fractional minute.
602      * <p>
603      * This reliably handles the case where fractional digits are being handled
604      * beyond a visible decimal point. The digits parsed will always be treated
605      * as the most significant (numerically largest) digits.
606      * Thus '23' will be parsed as 0.23 minutes (converted to milliseconds).
607      * This method does not print or parse the decimal point itself.
608      * 
609      * @param minDigits  minimum number of digits to print
610      * @param maxDigits  maximum number of digits to print or parse
611      * @return this DateTimeFormatterBuilder, for chaining
612      */
613     public DateTimeFormatterBuilder appendFractionOfMinute(int minDigits, int maxDigits) {
614         return appendFraction(DateTimeFieldType.minuteOfDay(), minDigits, maxDigits);
615     }
616 
617     /**
618      * Appends the print/parse of a fractional hour.
619      * <p>
620      * This reliably handles the case where fractional digits are being handled
621      * beyond a visible decimal point. The digits parsed will always be treated
622      * as the most significant (numerically largest) digits.
623      * Thus '23' will be parsed as 0.23 hours (converted to milliseconds).
624      * This method does not print or parse the decimal point itself.
625      * 
626      * @param minDigits  minimum number of digits to print
627      * @param maxDigits  maximum number of digits to print or parse
628      * @return this DateTimeFormatterBuilder, for chaining
629      */
630     public DateTimeFormatterBuilder appendFractionOfHour(int minDigits, int maxDigits) {
631         return appendFraction(DateTimeFieldType.hourOfDay(), minDigits, maxDigits);
632     }
633 
634     /**
635      * Appends the print/parse of a fractional day.
636      * <p>
637      * This reliably handles the case where fractional digits are being handled
638      * beyond a visible decimal point. The digits parsed will always be treated
639      * as the most significant (numerically largest) digits.
640      * Thus '23' will be parsed as 0.23 days (converted to milliseconds).
641      * This method does not print or parse the decimal point itself.
642      * 
643      * @param minDigits  minimum number of digits to print
644      * @param maxDigits  maximum number of digits to print or parse
645      * @return this DateTimeFormatterBuilder, for chaining
646      */
647     public DateTimeFormatterBuilder appendFractionOfDay(int minDigits, int maxDigits) {
648         return appendFraction(DateTimeFieldType.dayOfYear(), minDigits, maxDigits);
649     }
650 
651     /**
652      * Instructs the printer to emit a numeric millisOfSecond field.
653      * <p>
654      * This method will append a field that prints a three digit value.
655      * During parsing the value that is parsed is assumed to be three digits.
656      * If less than three digits are present then they will be counted as the
657      * smallest parts of the millisecond. This is probably not what you want
658      * if you are using the field as a fraction. Instead, a fractional
659      * millisecond should be produced using {@link #appendFractionOfSecond}.
660      *
661      * @param minDigits  minimum number of digits to print
662      * @return this DateTimeFormatterBuilder, for chaining
663      */
664     public DateTimeFormatterBuilder appendMillisOfSecond(int minDigits) {
665         return appendDecimal(DateTimeFieldType.millisOfSecond(), minDigits, 3);
666     }
667 
668     /**
669      * Instructs the printer to emit a numeric millisOfDay field.
670      *
671      * @param minDigits  minimum number of digits to print
672      * @return this DateTimeFormatterBuilder, for chaining
673      */
674     public DateTimeFormatterBuilder appendMillisOfDay(int minDigits) {
675         return appendDecimal(DateTimeFieldType.millisOfDay(), minDigits, 8);
676     }
677 
678     /**
679      * Instructs the printer to emit a numeric secondOfMinute field.
680      *
681      * @param minDigits  minimum number of digits to print
682      * @return this DateTimeFormatterBuilder, for chaining
683      */
684     public DateTimeFormatterBuilder appendSecondOfMinute(int minDigits) {
685         return appendDecimal(DateTimeFieldType.secondOfMinute(), minDigits, 2);
686     }
687 
688     /**
689      * Instructs the printer to emit a numeric secondOfDay field.
690      *
691      * @param minDigits  minimum number of digits to print
692      * @return this DateTimeFormatterBuilder, for chaining
693      */
694     public DateTimeFormatterBuilder appendSecondOfDay(int minDigits) {
695         return appendDecimal(DateTimeFieldType.secondOfDay(), minDigits, 5);
696     }
697 
698     /**
699      * Instructs the printer to emit a numeric minuteOfHour field.
700      *
701      * @param minDigits  minimum number of digits to print
702      * @return this DateTimeFormatterBuilder, for chaining
703      */
704     public DateTimeFormatterBuilder appendMinuteOfHour(int minDigits) {
705         return appendDecimal(DateTimeFieldType.minuteOfHour(), minDigits, 2);
706     }
707 
708     /**
709      * Instructs the printer to emit a numeric minuteOfDay field.
710      *
711      * @param minDigits  minimum number of digits to print
712      * @return this DateTimeFormatterBuilder, for chaining
713      */
714     public DateTimeFormatterBuilder appendMinuteOfDay(int minDigits) {
715         return appendDecimal(DateTimeFieldType.minuteOfDay(), minDigits, 4);
716     }
717 
718     /**
719      * Instructs the printer to emit a numeric hourOfDay field.
720      *
721      * @param minDigits  minimum number of digits to print
722      * @return this DateTimeFormatterBuilder, for chaining
723      */
724     public DateTimeFormatterBuilder appendHourOfDay(int minDigits) {
725         return appendDecimal(DateTimeFieldType.hourOfDay(), minDigits, 2);
726     }
727 
728     /**
729      * Instructs the printer to emit a numeric clockhourOfDay field.
730      *
731      * @param minDigits minimum number of digits to print
732      * @return this DateTimeFormatterBuilder, for chaining
733      */
734     public DateTimeFormatterBuilder appendClockhourOfDay(int minDigits) {
735         return appendDecimal(DateTimeFieldType.clockhourOfDay(), minDigits, 2);
736     }
737 
738     /**
739      * Instructs the printer to emit a numeric hourOfHalfday field.
740      *
741      * @param minDigits  minimum number of digits to print
742      * @return this DateTimeFormatterBuilder, for chaining
743      */
744     public DateTimeFormatterBuilder appendHourOfHalfday(int minDigits) {
745         return appendDecimal(DateTimeFieldType.hourOfHalfday(), minDigits, 2);
746     }
747 
748     /**
749      * Instructs the printer to emit a numeric clockhourOfHalfday field.
750      *
751      * @param minDigits  minimum number of digits to print
752      * @return this DateTimeFormatterBuilder, for chaining
753      */
754     public DateTimeFormatterBuilder appendClockhourOfHalfday(int minDigits) {
755         return appendDecimal(DateTimeFieldType.clockhourOfHalfday(), minDigits, 2);
756     }
757 
758     /**
759      * Instructs the printer to emit a numeric dayOfWeek field.
760      *
761      * @param minDigits  minimum number of digits to print
762      * @return this DateTimeFormatterBuilder, for chaining
763      */
764     public DateTimeFormatterBuilder appendDayOfWeek(int minDigits) {
765         return appendDecimal(DateTimeFieldType.dayOfWeek(), minDigits, 1);
766     }
767 
768     /**
769      * Instructs the printer to emit a numeric dayOfMonth field.
770      *
771      * @param minDigits  minimum number of digits to print
772      * @return this DateTimeFormatterBuilder, for chaining
773      */
774     public DateTimeFormatterBuilder appendDayOfMonth(int minDigits) {
775         return appendDecimal(DateTimeFieldType.dayOfMonth(), minDigits, 2);
776     }
777 
778     /**
779      * Instructs the printer to emit a numeric dayOfYear field.
780      *
781      * @param minDigits  minimum number of digits to print
782      * @return this DateTimeFormatterBuilder, for chaining
783      */
784     public DateTimeFormatterBuilder appendDayOfYear(int minDigits) {
785         return appendDecimal(DateTimeFieldType.dayOfYear(), minDigits, 3);
786     }
787 
788     /**
789      * Instructs the printer to emit a numeric weekOfWeekyear field.
790      *
791      * @param minDigits  minimum number of digits to print
792      * @return this DateTimeFormatterBuilder, for chaining
793      */
794     public DateTimeFormatterBuilder appendWeekOfWeekyear(int minDigits) {
795         return appendDecimal(DateTimeFieldType.weekOfWeekyear(), minDigits, 2);
796     }
797 
798     /**
799      * Instructs the printer to emit a numeric weekyear field.
800      *
801      * @param minDigits  minimum number of digits to <i>print</i>
802      * @param maxDigits  maximum number of digits to <i>parse</i>, or the estimated
803      * maximum number of digits to print
804      * @return this DateTimeFormatterBuilder, for chaining
805      */
806     public DateTimeFormatterBuilder appendWeekyear(int minDigits, int maxDigits) {
807         return appendSignedDecimal(DateTimeFieldType.weekyear(), minDigits, maxDigits);
808     }
809 
810     /**
811      * Instructs the printer to emit a numeric monthOfYear field.
812      *
813      * @param minDigits  minimum number of digits to print
814      * @return this DateTimeFormatterBuilder, for chaining
815      */
816     public DateTimeFormatterBuilder appendMonthOfYear(int minDigits) {
817         return appendDecimal(DateTimeFieldType.monthOfYear(), minDigits, 2);
818     }
819 
820     /**
821      * Instructs the printer to emit a numeric year field.
822      *
823      * @param minDigits  minimum number of digits to <i>print</i>
824      * @param maxDigits  maximum number of digits to <i>parse</i>, or the estimated
825      * maximum number of digits to print
826      * @return this DateTimeFormatterBuilder, for chaining
827      */
828     public DateTimeFormatterBuilder appendYear(int minDigits, int maxDigits) {
829         return appendSignedDecimal(DateTimeFieldType.year(), minDigits, maxDigits);
830     }
831 
832     /**
833      * Instructs the printer to emit a numeric year field which always prints
834      * and parses two digits. A pivot year is used during parsing to determine
835      * the range of supported years as <code>(pivot - 50) .. (pivot + 49)</code>.
836      *
837      * <pre>
838      * pivot   supported range   00 is   20 is   40 is   60 is   80 is
839      * ---------------------------------------------------------------
840      * 1950      1900..1999      1900    1920    1940    1960    1980
841      * 1975      1925..2024      2000    2020    1940    1960    1980
842      * 2000      1950..2049      2000    2020    2040    1960    1980
843      * 2025      1975..2074      2000    2020    2040    2060    1980
844      * 2050      2000..2099      2000    2020    2040    2060    2080
845      * </pre>
846      *
847      * @param pivot  pivot year to use when parsing
848      * @return this DateTimeFormatterBuilder, for chaining
849      */
850     public DateTimeFormatterBuilder appendTwoDigitYear(int pivot) {
851         return appendTwoDigitYear(pivot, false);
852     }
853 
854     /**
855      * Instructs the printer to emit a numeric year field which always prints
856      * two digits. A pivot year is used during parsing to determine the range
857      * of supported years as <code>(pivot - 50) .. (pivot + 49)</code>. If
858      * parse is instructed to be lenient and the digit count is not two, it is
859      * treated as an absolute year. With lenient parsing, specifying a positive
860      * or negative sign before the year also makes it absolute.
861      *
862      * @param pivot  pivot year to use when parsing
863      * @param lenientParse  when true, if digit count is not two, it is treated
864      * as an absolute year
865      * @return this DateTimeFormatterBuilder, for chaining
866      * @since 1.1
867      */
868     public DateTimeFormatterBuilder appendTwoDigitYear(int pivot, boolean lenientParse) {
869         return append0(new TwoDigitYear(DateTimeFieldType.year(), pivot, lenientParse));
870     }
871 
872     /**
873      * Instructs the printer to emit a numeric weekyear field which always prints
874      * and parses two digits. A pivot year is used during parsing to determine
875      * the range of supported years as <code>(pivot - 50) .. (pivot + 49)</code>.
876      *
877      * <pre>
878      * pivot   supported range   00 is   20 is   40 is   60 is   80 is
879      * ---------------------------------------------------------------
880      * 1950      1900..1999      1900    1920    1940    1960    1980
881      * 1975      1925..2024      2000    2020    1940    1960    1980
882      * 2000      1950..2049      2000    2020    2040    1960    1980
883      * 2025      1975..2074      2000    2020    2040    2060    1980
884      * 2050      2000..2099      2000    2020    2040    2060    2080
885      * </pre>
886      *
887      * @param pivot  pivot weekyear to use when parsing
888      * @return this DateTimeFormatterBuilder, for chaining
889      */
890     public DateTimeFormatterBuilder appendTwoDigitWeekyear(int pivot) {
891         return appendTwoDigitWeekyear(pivot, false);
892     }
893 
894     /**
895      * Instructs the printer to emit a numeric weekyear field which always prints
896      * two digits. A pivot year is used during parsing to determine the range
897      * of supported years as <code>(pivot - 50) .. (pivot + 49)</code>. If
898      * parse is instructed to be lenient and the digit count is not two, it is
899      * treated as an absolute weekyear. With lenient parsing, specifying a positive
900      * or negative sign before the weekyear also makes it absolute.
901      *
902      * @param pivot  pivot weekyear to use when parsing
903      * @param lenientParse  when true, if digit count is not two, it is treated
904      * as an absolute weekyear
905      * @return this DateTimeFormatterBuilder, for chaining
906      * @since 1.1
907      */
908     public DateTimeFormatterBuilder appendTwoDigitWeekyear(int pivot, boolean lenientParse) {
909         return append0(new TwoDigitYear(DateTimeFieldType.weekyear(), pivot, lenientParse));
910     }
911 
912     /**
913      * Instructs the printer to emit a numeric yearOfEra field.
914      *
915      * @param minDigits  minimum number of digits to <i>print</i>
916      * @param maxDigits  maximum number of digits to <i>parse</i>, or the estimated
917      * maximum number of digits to print
918      * @return this DateTimeFormatterBuilder, for chaining
919      */
920     public DateTimeFormatterBuilder appendYearOfEra(int minDigits, int maxDigits) {
921         return appendDecimal(DateTimeFieldType.yearOfEra(), minDigits, maxDigits);
922     }
923 
924     /**
925      * Instructs the printer to emit a numeric year of century field.
926      *
927      * @param minDigits  minimum number of digits to print
928      * @param maxDigits  maximum number of digits to <i>parse</i>, or the estimated
929      * maximum number of digits to print
930      * @return this DateTimeFormatterBuilder, for chaining
931      */
932     public DateTimeFormatterBuilder appendYearOfCentury(int minDigits, int maxDigits) {
933         return appendDecimal(DateTimeFieldType.yearOfCentury(), minDigits, maxDigits);
934     }
935 
936     /**
937      * Instructs the printer to emit a numeric century of era field.
938      *
939      * @param minDigits  minimum number of digits to print
940      * @param maxDigits  maximum number of digits to <i>parse</i>, or the estimated
941      * maximum number of digits to print
942      * @return this DateTimeFormatterBuilder, for chaining
943      */
944     public DateTimeFormatterBuilder appendCenturyOfEra(int minDigits, int maxDigits) {
945         return appendSignedDecimal(DateTimeFieldType.centuryOfEra(), minDigits, maxDigits);
946     }
947 
948     /**
949      * Instructs the printer to emit a locale-specific AM/PM text, and the
950      * parser to expect it. The parser is case-insensitive.
951      *
952      * @return this DateTimeFormatterBuilder, for chaining
953      */
954     public DateTimeFormatterBuilder appendHalfdayOfDayText() {
955         return appendText(DateTimeFieldType.halfdayOfDay());
956     }
957 
958     /**
959      * Instructs the printer to emit a locale-specific dayOfWeek text. The
960      * parser will accept a long or short dayOfWeek text, case-insensitive.
961      *
962      * @return this DateTimeFormatterBuilder, for chaining
963      */
964     public DateTimeFormatterBuilder appendDayOfWeekText() {
965         return appendText(DateTimeFieldType.dayOfWeek());
966     }
967 
968     /**
969      * Instructs the printer to emit a short locale-specific dayOfWeek
970      * text. The parser will accept a long or short dayOfWeek text,
971      * case-insensitive.
972      *
973      * @return this DateTimeFormatterBuilder, for chaining
974      */
975     public DateTimeFormatterBuilder appendDayOfWeekShortText() {
976         return appendShortText(DateTimeFieldType.dayOfWeek());
977     }
978 
979     /**
980      * Instructs the printer to emit a short locale-specific monthOfYear
981      * text. The parser will accept a long or short monthOfYear text,
982      * case-insensitive.
983      *
984      * @return this DateTimeFormatterBuilder, for chaining
985      */
986     public DateTimeFormatterBuilder appendMonthOfYearText() { 
987         return appendText(DateTimeFieldType.monthOfYear());
988     }
989 
990     /**
991      * Instructs the printer to emit a locale-specific monthOfYear text. The
992      * parser will accept a long or short monthOfYear text, case-insensitive.
993      *
994      * @return this DateTimeFormatterBuilder, for chaining
995      */
996     public DateTimeFormatterBuilder appendMonthOfYearShortText() {
997         return appendShortText(DateTimeFieldType.monthOfYear());
998     }
999 
1000     /**
1001      * Instructs the printer to emit a locale-specific era text (BC/AD), and
1002      * the parser to expect it. The parser is case-insensitive.
1003      *
1004      * @return this DateTimeFormatterBuilder, for chaining
1005      */
1006     public DateTimeFormatterBuilder appendEraText() {
1007         return appendText(DateTimeFieldType.era());
1008     }
1009 
1010     /**
1011      * Instructs the printer to emit a locale-specific time zone name.
1012      * Using this method prevents parsing, because time zone names are not unique.
1013      * See {@link #appendTimeZoneName(Map)}.
1014      *
1015      * @return this DateTimeFormatterBuilder, for chaining
1016      */
1017     public DateTimeFormatterBuilder appendTimeZoneName() {
1018         return append0(new TimeZoneName(TimeZoneName.LONG_NAME, null), null);
1019     }
1020 
1021     /**
1022      * Instructs the printer to emit a locale-specific time zone name, providing a lookup for parsing.
1023      * Time zone names are not unique, thus the API forces you to supply the lookup.
1024      * The names are searched in the order of the map, thus it is strongly recommended
1025      * to use a {@code LinkedHashMap} or similar.
1026      *
1027      * @param parseLookup  the table of names, not null
1028      * @return this DateTimeFormatterBuilder, for chaining
1029      */
1030     public DateTimeFormatterBuilder appendTimeZoneName(Map<String, DateTimeZone> parseLookup) {
1031         TimeZoneName pp = new TimeZoneName(TimeZoneName.LONG_NAME, parseLookup);
1032         return append0(pp, pp);
1033     }
1034 
1035     /**
1036      * Instructs the printer to emit a short locale-specific time zone name.
1037      * Using this method prevents parsing, because time zone names are not unique.
1038      * See {@link #appendTimeZoneShortName(Map)}.
1039      *
1040      * @return this DateTimeFormatterBuilder, for chaining
1041      */
1042     public DateTimeFormatterBuilder appendTimeZoneShortName() {
1043         return append0(new TimeZoneName(TimeZoneName.SHORT_NAME, null), null);
1044     }
1045 
1046     /**
1047      * Instructs the printer to emit a short locale-specific time zone
1048      * name, providing a lookup for parsing.
1049      * Time zone names are not unique, thus the API forces you to supply the lookup.
1050      * The names are searched in the order of the map, thus it is strongly recommended
1051      * to use a {@code LinkedHashMap} or similar.
1052      *
1053      * @param parseLookup  the table of names, null to use the {@link DateTimeUtils#getDefaultTimeZoneNames() default names}
1054      * @return this DateTimeFormatterBuilder, for chaining
1055      */
1056     public DateTimeFormatterBuilder appendTimeZoneShortName(Map<String, DateTimeZone> parseLookup) {
1057         TimeZoneName pp = new TimeZoneName(TimeZoneName.SHORT_NAME, parseLookup);
1058         return append0(pp, pp);
1059     }
1060 
1061     /**
1062      * Instructs the printer to emit the identifier of the time zone.
1063      * From version 2.0, this field can be parsed.
1064      *
1065      * @return this DateTimeFormatterBuilder, for chaining
1066      */
1067     public DateTimeFormatterBuilder appendTimeZoneId() {
1068         return append0(TimeZoneId.INSTANCE, TimeZoneId.INSTANCE);
1069     }
1070 
1071     /**
1072      * Instructs the printer to emit text and numbers to display time zone
1073      * offset from UTC. A parser will use the parsed time zone offset to adjust
1074      * the datetime.
1075      * <p>
1076      * If zero offset text is supplied, then it will be printed when the zone is zero.
1077      * During parsing, either the zero offset text, or the offset will be parsed.
1078      *
1079      * @param zeroOffsetText  the text to use if time zone offset is zero. If
1080      * null, offset is always shown.
1081      * @param showSeparators  if true, prints ':' separator before minute and
1082      * second field and prints '.' separator before fraction field.
1083      * @param minFields  minimum number of fields to print, stopping when no
1084      * more precision is required. 1=hours, 2=minutes, 3=seconds, 4=fraction
1085      * @param maxFields  maximum number of fields to print
1086      * @return this DateTimeFormatterBuilder, for chaining
1087      */
1088     public DateTimeFormatterBuilder appendTimeZoneOffset(
1089             String zeroOffsetText, boolean showSeparators,
1090             int minFields, int maxFields) {
1091         return append0(new TimeZoneOffset
1092                        (zeroOffsetText, zeroOffsetText, showSeparators, minFields, maxFields));
1093     }
1094 
1095     /**
1096      * Instructs the printer to emit text and numbers to display time zone
1097      * offset from UTC. A parser will use the parsed time zone offset to adjust
1098      * the datetime.
1099      * <p>
1100      * If zero offset print text is supplied, then it will be printed when the zone is zero.
1101      * If zero offset parse text is supplied, then either it or the offset will be parsed.
1102      *
1103      * @param zeroOffsetPrintText  the text to print if time zone offset is zero. If
1104      * null, offset is always shown.
1105      * @param zeroOffsetParseText  the text to optionally parse to indicate that the time
1106      * zone offset is zero. If null, then always use the offset.
1107      * @param showSeparators  if true, prints ':' separator before minute and
1108      * second field and prints '.' separator before fraction field.
1109      * @param minFields  minimum number of fields to print, stopping when no
1110      * more precision is required. 1=hours, 2=minutes, 3=seconds, 4=fraction
1111      * @param maxFields  maximum number of fields to print
1112      * @return this DateTimeFormatterBuilder, for chaining
1113      * @since 2.0
1114      */
1115     public DateTimeFormatterBuilder appendTimeZoneOffset(
1116             String zeroOffsetPrintText, String zeroOffsetParseText, boolean showSeparators,
1117             int minFields, int maxFields) {
1118         return append0(new TimeZoneOffset
1119                        (zeroOffsetPrintText, zeroOffsetParseText, showSeparators, minFields, maxFields));
1120     }
1121 
1122     //-----------------------------------------------------------------------
1123     /**
1124      * Calls upon {@link DateTimeFormat} to parse the pattern and append the
1125      * results into this builder.
1126      *
1127      * @param pattern  pattern specification
1128      * @throws IllegalArgumentException if the pattern is invalid
1129      * @see DateTimeFormat
1130      */
1131     public DateTimeFormatterBuilder appendPattern(String pattern) {
1132         DateTimeFormat.appendPatternTo(this, pattern);
1133         return this;
1134     }
1135 
1136     //-----------------------------------------------------------------------
1137     private Object getFormatter() {
1138         Object f = iFormatter;
1139 
1140         if (f == null) {
1141             if (iElementPairs.size() == 2) {
1142                 Object printer = iElementPairs.get(0);
1143                 Object parser = iElementPairs.get(1);
1144 
1145                 if (printer != null) {
1146                     if (printer == parser || parser == null) {
1147                         f = printer;
1148                     }
1149                 } else {
1150                     f = parser;
1151                 }
1152             }
1153 
1154             if (f == null) {
1155                 f = new Composite(iElementPairs);
1156             }
1157 
1158             iFormatter = f;
1159         }
1160 
1161         return f;
1162     }
1163 
1164     private boolean isPrinter(Object f) {
1165         if (f instanceof DateTimePrinter) {
1166             if (f instanceof Composite) {
1167                 return ((Composite)f).isPrinter();
1168             }
1169             return true;
1170         }
1171         return false;
1172     }
1173 
1174     private boolean isParser(Object f) {
1175         if (f instanceof DateTimeParser) {
1176             if (f instanceof Composite) {
1177                 return ((Composite)f).isParser();
1178             }
1179             return true;
1180         }
1181         return false;
1182     }
1183 
1184     private boolean isFormatter(Object f) {
1185         return (isPrinter(f) || isParser(f));
1186     }
1187 
1188     static void appendUnknownString(StringBuffer buf, int len) {
1189         for (int i = len; --i >= 0;) {
1190             buf.append('\ufffd');
1191         }
1192     }
1193 
1194     static void printUnknownString(Writer out, int len) throws IOException {
1195         for (int i = len; --i >= 0;) {
1196             out.write('\ufffd');
1197         }
1198     }
1199 
1200     //-----------------------------------------------------------------------
1201     static class CharacterLiteral
1202             implements DateTimePrinter, DateTimeParser {
1203 
1204         private final char iValue;
1205 
1206         CharacterLiteral(char value) {
1207             super();
1208             iValue = value;
1209         }
1210 
1211         public int estimatePrintedLength() {
1212             return 1;
1213         }
1214 
1215         public void printTo(
1216                 StringBuffer buf, long instant, Chronology chrono,
1217                 int displayOffset, DateTimeZone displayZone, Locale locale) {
1218             buf.append(iValue);
1219         }
1220 
1221         public void printTo(
1222                 Writer out, long instant, Chronology chrono,
1223                 int displayOffset, DateTimeZone displayZone, Locale locale) throws IOException {
1224             out.write(iValue);
1225         }
1226 
1227         public void printTo(StringBuffer buf, ReadablePartial partial, Locale locale) {
1228             buf.append(iValue);
1229         }
1230 
1231         public void printTo(Writer out, ReadablePartial partial, Locale locale) throws IOException {
1232             out.write(iValue);
1233         }
1234 
1235         public int estimateParsedLength() {
1236             return 1;
1237         }
1238 
1239         public int parseInto(DateTimeParserBucket bucket, String text, int position) {
1240             if (position >= text.length()) {
1241                 return ~position;
1242             }
1243 
1244             char a = text.charAt(position);
1245             char b = iValue;
1246 
1247             if (a != b) {
1248                 a = Character.toUpperCase(a);
1249                 b = Character.toUpperCase(b);
1250                 if (a != b) {
1251                     a = Character.toLowerCase(a);
1252                     b = Character.toLowerCase(b);
1253                     if (a != b) {
1254                         return ~position;
1255                     }
1256                 }
1257             }
1258 
1259             return position + 1;
1260         }
1261     }
1262 
1263     //-----------------------------------------------------------------------
1264     static class StringLiteral
1265             implements DateTimePrinter, DateTimeParser {
1266 
1267         private final String iValue;
1268 
1269         StringLiteral(String value) {
1270             super();
1271             iValue = value;
1272         }
1273 
1274         public int estimatePrintedLength() {
1275             return iValue.length();
1276         }
1277 
1278         public void printTo(
1279                 StringBuffer buf, long instant, Chronology chrono,
1280                 int displayOffset, DateTimeZone displayZone, Locale locale) {
1281             buf.append(iValue);
1282         }
1283 
1284         public void printTo(
1285                 Writer out, long instant, Chronology chrono,
1286                 int displayOffset, DateTimeZone displayZone, Locale locale) throws IOException {
1287             out.write(iValue);
1288         }
1289 
1290         public void printTo(StringBuffer buf, ReadablePartial partial, Locale locale) {
1291             buf.append(iValue);
1292         }
1293 
1294         public void printTo(Writer out, ReadablePartial partial, Locale locale) throws IOException {
1295             out.write(iValue);
1296         }
1297 
1298         public int estimateParsedLength() {
1299             return iValue.length();
1300         }
1301 
1302         public int parseInto(DateTimeParserBucket bucket, String text, int position) {
1303             if (text.regionMatches(true, position, iValue, 0, iValue.length())) {
1304                 return position + iValue.length();
1305             }
1306             return ~position;
1307         }
1308     }
1309 
1310     //-----------------------------------------------------------------------
1311     static abstract class NumberFormatter
1312             implements DateTimePrinter, DateTimeParser {
1313         protected final DateTimeFieldType iFieldType;
1314         protected final int iMaxParsedDigits;
1315         protected final boolean iSigned;
1316 
1317         NumberFormatter(DateTimeFieldType fieldType,
1318                 int maxParsedDigits, boolean signed) {
1319             super();
1320             iFieldType = fieldType;
1321             iMaxParsedDigits = maxParsedDigits;
1322             iSigned = signed;
1323         }
1324 
1325         public int estimateParsedLength() {
1326             return iMaxParsedDigits;
1327         }
1328 
1329         public int parseInto(DateTimeParserBucket bucket, String text, int position) {
1330             int limit = Math.min(iMaxParsedDigits, text.length() - position);
1331 
1332             boolean negative = false;
1333             int length = 0;
1334             while (length < limit) {
1335                 char c = text.charAt(position + length);
1336                 if (length == 0 && (c == '-' || c == '+') && iSigned) {
1337                     negative = c == '-';
1338 
1339                     // Next character must be a digit.
1340                     if (length + 1 >= limit || 
1341                         (c = text.charAt(position + length + 1)) < '0' || c > '9')
1342                     {
1343                         break;
1344                     }
1345 
1346                     if (negative) {
1347                         length++;
1348                     } else {
1349                         // Skip the '+' for parseInt to succeed.
1350                         position++;
1351                     }
1352                     // Expand the limit to disregard the sign character.
1353                     limit = Math.min(limit + 1, text.length() - position);
1354                     continue;
1355                 }
1356                 if (c < '0' || c > '9') {
1357                     break;
1358                 }
1359                 length++;
1360             }
1361 
1362             if (length == 0) {
1363                 return ~position;
1364             }
1365 
1366             int value;
1367             if (length >= 9) {
1368                 // Since value may exceed integer limits, use stock parser
1369                 // which checks for this.
1370                 value = Integer.parseInt(text.substring(position, position += length));
1371             } else {
1372                 int i = position;
1373                 if (negative) {
1374                     i++;
1375                 }
1376                 try {
1377                     value = text.charAt(i++) - '0';
1378                 } catch (StringIndexOutOfBoundsException e) {
1379                     return ~position;
1380                 }
1381                 position += length;
1382                 while (i < position) {
1383                     value = ((value << 3) + (value << 1)) + text.charAt(i++) - '0';
1384                 }
1385                 if (negative) {
1386                     value = -value;
1387                 }
1388             }
1389 
1390             bucket.saveField(iFieldType, value);
1391             return position;
1392         }
1393     }
1394 
1395     //-----------------------------------------------------------------------
1396     static class UnpaddedNumber extends NumberFormatter {
1397 
1398         protected UnpaddedNumber(DateTimeFieldType fieldType,
1399                        int maxParsedDigits, boolean signed)
1400         {
1401             super(fieldType, maxParsedDigits, signed);
1402         }
1403 
1404         public int estimatePrintedLength() {
1405             return iMaxParsedDigits;
1406         }
1407 
1408         public void printTo(
1409                 StringBuffer buf, long instant, Chronology chrono,
1410                 int displayOffset, DateTimeZone displayZone, Locale locale) {
1411             try {
1412                 DateTimeField field = iFieldType.getField(chrono);
1413                 FormatUtils.appendUnpaddedInteger(buf, field.get(instant));
1414             } catch (RuntimeException e) {
1415                 buf.append('\ufffd');
1416             }
1417         }
1418 
1419         public void printTo(
1420                 Writer out, long instant, Chronology chrono,
1421                 int displayOffset, DateTimeZone displayZone, Locale locale) throws IOException {
1422             try {
1423                 DateTimeField field = iFieldType.getField(chrono);
1424                 FormatUtils.writeUnpaddedInteger(out, field.get(instant));
1425             } catch (RuntimeException e) {
1426                 out.write('\ufffd');
1427             }
1428         }
1429 
1430         public void printTo(StringBuffer buf, ReadablePartial partial, Locale locale) {
1431             if (partial.isSupported(iFieldType)) {
1432                 try {
1433                     FormatUtils.appendUnpaddedInteger(buf, partial.get(iFieldType));
1434                 } catch (RuntimeException e) {
1435                     buf.append('\ufffd');
1436                 }
1437             } else {
1438                 buf.append('\ufffd');
1439             }
1440         }
1441 
1442         public void printTo(Writer out, ReadablePartial partial, Locale locale) throws IOException {
1443             if (partial.isSupported(iFieldType)) {
1444                 try {
1445                     FormatUtils.writeUnpaddedInteger(out, partial.get(iFieldType));
1446                 } catch (RuntimeException e) {
1447                     out.write('\ufffd');
1448                 }
1449             } else {
1450                 out.write('\ufffd');
1451             }
1452         }
1453     }
1454 
1455     //-----------------------------------------------------------------------
1456     static class PaddedNumber extends NumberFormatter {
1457 
1458         protected final int iMinPrintedDigits;
1459 
1460         protected PaddedNumber(DateTimeFieldType fieldType, int maxParsedDigits,
1461                      boolean signed, int minPrintedDigits)
1462         {
1463             super(fieldType, maxParsedDigits, signed);
1464             iMinPrintedDigits = minPrintedDigits;
1465         }
1466 
1467         public int estimatePrintedLength() {
1468             return iMaxParsedDigits;
1469         }
1470 
1471         public void printTo(
1472                 StringBuffer buf, long instant, Chronology chrono,
1473                 int displayOffset, DateTimeZone displayZone, Locale locale) {
1474             try {
1475                 DateTimeField field = iFieldType.getField(chrono);
1476                 FormatUtils.appendPaddedInteger(buf, field.get(instant), iMinPrintedDigits);
1477             } catch (RuntimeException e) {
1478                 appendUnknownString(buf, iMinPrintedDigits);
1479             }
1480         }
1481 
1482         public void printTo(
1483                 Writer out, long instant, Chronology chrono,
1484                 int displayOffset, DateTimeZone displayZone, Locale locale) throws IOException {
1485             try {
1486                 DateTimeField field = iFieldType.getField(chrono);
1487                 FormatUtils.writePaddedInteger(out, field.get(instant), iMinPrintedDigits);
1488             } catch (RuntimeException e) {
1489                 printUnknownString(out, iMinPrintedDigits);
1490             }
1491         }
1492 
1493         public void printTo(StringBuffer buf, ReadablePartial partial, Locale locale) {
1494             if (partial.isSupported(iFieldType)) {
1495                 try {
1496                     FormatUtils.appendPaddedInteger(buf, partial.get(iFieldType), iMinPrintedDigits);
1497                 } catch (RuntimeException e) {
1498                     appendUnknownString(buf, iMinPrintedDigits);
1499                 }
1500             } else {
1501                 appendUnknownString(buf, iMinPrintedDigits);
1502             }
1503         }
1504 
1505         public void printTo(Writer out, ReadablePartial partial, Locale locale) throws IOException {
1506             if (partial.isSupported(iFieldType)) {
1507                 try {
1508                     FormatUtils.writePaddedInteger(out, partial.get(iFieldType), iMinPrintedDigits);
1509                 } catch (RuntimeException e) {
1510                     printUnknownString(out, iMinPrintedDigits);
1511                 }
1512             } else {
1513                 printUnknownString(out, iMinPrintedDigits);
1514             }
1515         }
1516     }
1517 
1518     //-----------------------------------------------------------------------
1519     static class FixedNumber extends PaddedNumber {
1520 
1521         protected FixedNumber(DateTimeFieldType fieldType, int numDigits, boolean signed) {
1522             super(fieldType, numDigits, signed, numDigits);
1523         }
1524 
1525         public int parseInto(DateTimeParserBucket bucket, String text, int position) {
1526             int newPos = super.parseInto(bucket, text, position);
1527             if (newPos < 0) {
1528                 return newPos;
1529             }
1530             int expectedPos = position + iMaxParsedDigits;
1531             if (newPos != expectedPos) {
1532                 if (iSigned) {
1533                     char c = text.charAt(position);
1534                     if (c == '-' || c == '+') {
1535                         expectedPos++;
1536                     }
1537                 }
1538                 if (newPos > expectedPos) {
1539                     // The failure is at the position of the first extra digit.
1540                     return ~(expectedPos + 1);
1541                 } else if (newPos < expectedPos) {
1542                     // The failure is at the position where the next digit should be.
1543                     return ~newPos;
1544                 }
1545             }
1546             return newPos;
1547         }
1548     }
1549 
1550     //-----------------------------------------------------------------------
1551     static class TwoDigitYear
1552             implements DateTimePrinter, DateTimeParser {
1553 
1554         /** The field to print/parse. */
1555         private final DateTimeFieldType iType;
1556         /** The pivot year. */
1557         private final int iPivot;
1558         private final boolean iLenientParse;
1559 
1560         TwoDigitYear(DateTimeFieldType type, int pivot, boolean lenientParse) {
1561             super();
1562             iType = type;
1563             iPivot = pivot;
1564             iLenientParse = lenientParse;
1565         }
1566 
1567         public int estimateParsedLength() {
1568             return iLenientParse ? 4 : 2;
1569         }
1570 
1571         public int parseInto(DateTimeParserBucket bucket, String text, int position) {
1572             int limit = text.length() - position;
1573 
1574             if (!iLenientParse) {
1575                 limit = Math.min(2, limit);
1576                 if (limit < 2) {
1577                     return ~position;
1578                 }
1579             } else {
1580                 boolean hasSignChar = false;
1581                 boolean negative = false;
1582                 int length = 0;
1583                 while (length < limit) {
1584                     char c = text.charAt(position + length);
1585                     if (length == 0 && (c == '-' || c == '+')) {
1586                         hasSignChar = true;
1587                         negative = c == '-';
1588                         if (negative) {
1589                             length++;
1590                         } else {
1591                             // Skip the '+' for parseInt to succeed.
1592                             position++;
1593                             limit--;
1594                         }
1595                         continue;
1596                     }
1597                     if (c < '0' || c > '9') {
1598                         break;
1599                     }
1600                     length++;
1601                 }
1602                 
1603                 if (length == 0) {
1604                     return ~position;
1605                 }
1606 
1607                 if (hasSignChar || length != 2) {
1608                     int value;
1609                     if (length >= 9) {
1610                         // Since value may exceed integer limits, use stock
1611                         // parser which checks for this.
1612                         value = Integer.parseInt(text.substring(position, position += length));
1613                     } else {
1614                         int i = position;
1615                         if (negative) {
1616                             i++;
1617                         }
1618                         try {
1619                             value = text.charAt(i++) - '0';
1620                         } catch (StringIndexOutOfBoundsException e) {
1621                             return ~position;
1622                         }
1623                         position += length;
1624                         while (i < position) {
1625                             value = ((value << 3) + (value << 1)) + text.charAt(i++) - '0';
1626                         }
1627                         if (negative) {
1628                             value = -value;
1629                         }
1630                     }
1631                     
1632                     bucket.saveField(iType, value);
1633                     return position;
1634                 }
1635             }
1636 
1637             int year;
1638             char c = text.charAt(position);
1639             if (c < '0' || c > '9') {
1640                 return ~position;
1641             }
1642             year = c - '0';
1643             c = text.charAt(position + 1);
1644             if (c < '0' || c > '9') {
1645                 return ~position;
1646             }
1647             year = ((year << 3) + (year << 1)) + c - '0';
1648 
1649             int pivot = iPivot;
1650             // If the bucket pivot year is non-null, use that when parsing
1651             if (bucket.getPivotYear() != null) {
1652                 pivot = bucket.getPivotYear().intValue();
1653             }
1654 
1655             int low = pivot - 50;
1656 
1657             int t;
1658             if (low >= 0) {
1659                 t = low % 100;
1660             } else {
1661                 t = 99 + ((low + 1) % 100);
1662             }
1663 
1664             year += low + ((year < t) ? 100 : 0) - t;
1665 
1666             bucket.saveField(iType, year);
1667             return position + 2;
1668         }
1669         
1670         public int estimatePrintedLength() {
1671             return 2;
1672         }
1673 
1674         public void printTo(
1675                 StringBuffer buf, long instant, Chronology chrono,
1676                 int displayOffset, DateTimeZone displayZone, Locale locale) {
1677             int year = getTwoDigitYear(instant, chrono);
1678             if (year < 0) {
1679                 buf.append('\ufffd');
1680                 buf.append('\ufffd');
1681             } else {
1682                 FormatUtils.appendPaddedInteger(buf, year, 2);
1683             }
1684         }
1685 
1686         public void printTo(
1687                 Writer out, long instant, Chronology chrono,
1688                 int displayOffset, DateTimeZone displayZone, Locale locale) throws IOException {
1689             int year = getTwoDigitYear(instant, chrono);
1690             if (year < 0) {
1691                 out.write('\ufffd');
1692                 out.write('\ufffd');
1693             } else {
1694                 FormatUtils.writePaddedInteger(out, year, 2);
1695             }
1696         }
1697 
1698         private int getTwoDigitYear(long instant, Chronology chrono) {
1699             try {
1700                 int year = iType.getField(chrono).get(instant);
1701                 if (year < 0) {
1702                     year = -year;
1703                 }
1704                 return year % 100;
1705             } catch (RuntimeException e) {
1706                 return -1;
1707             }
1708         }
1709 
1710         public void printTo(StringBuffer buf, ReadablePartial partial, Locale locale) {
1711             int year = getTwoDigitYear(partial);
1712             if (year < 0) {
1713                 buf.append('\ufffd');
1714                 buf.append('\ufffd');
1715             } else {
1716                 FormatUtils.appendPaddedInteger(buf, year, 2);
1717             }
1718         }
1719 
1720         public void printTo(Writer out, ReadablePartial partial, Locale locale) throws IOException {
1721             int year = getTwoDigitYear(partial);
1722             if (year < 0) {
1723                 out.write('\ufffd');
1724                 out.write('\ufffd');
1725             } else {
1726                 FormatUtils.writePaddedInteger(out, year, 2);
1727             }
1728         }
1729 
1730         private int getTwoDigitYear(ReadablePartial partial) {
1731             if (partial.isSupported(iType)) {
1732                 try {
1733                     int year = partial.get(iType);
1734                     if (year < 0) {
1735                         year = -year;
1736                     }
1737                     return year % 100;
1738                 } catch (RuntimeException e) {}
1739             } 
1740             return -1;
1741         }
1742     }
1743 
1744     //-----------------------------------------------------------------------
1745     static class TextField
1746             implements DateTimePrinter, DateTimeParser {
1747 
1748         private static Map<Locale, Map<DateTimeFieldType, Object[]>> cParseCache =
1749                     new HashMap<Locale, Map<DateTimeFieldType, Object[]>>();
1750         private final DateTimeFieldType iFieldType;
1751         private final boolean iShort;
1752 
1753         TextField(DateTimeFieldType fieldType, boolean isShort) {
1754             super();
1755             iFieldType = fieldType;
1756             iShort = isShort;
1757         }
1758 
1759         public int estimatePrintedLength() {
1760             return iShort ? 6 : 20;
1761         }
1762 
1763         public void printTo(
1764                 StringBuffer buf, long instant, Chronology chrono,
1765                 int displayOffset, DateTimeZone displayZone, Locale locale) {
1766             try {
1767                 buf.append(print(instant, chrono, locale));
1768             } catch (RuntimeException e) {
1769                 buf.append('\ufffd');
1770             }
1771         }
1772 
1773         public void printTo(
1774                 Writer out, long instant, Chronology chrono,
1775                 int displayOffset, DateTimeZone displayZone, Locale locale) throws IOException {
1776             try {
1777                 out.write(print(instant, chrono, locale));
1778             } catch (RuntimeException e) {
1779                 out.write('\ufffd');
1780             }
1781         }
1782 
1783         public void printTo(StringBuffer buf, ReadablePartial partial, Locale locale) {
1784             try {
1785                 buf.append(print(partial, locale));
1786             } catch (RuntimeException e) {
1787                 buf.append('\ufffd');
1788             }
1789         }
1790 
1791         public void printTo(Writer out, ReadablePartial partial, Locale locale) throws IOException {
1792             try {
1793                 out.write(print(partial, locale));
1794             } catch (RuntimeException e) {
1795                 out.write('\ufffd');
1796             }
1797         }
1798 
1799         private String print(long instant, Chronology chrono, Locale locale) {
1800             DateTimeField field = iFieldType.getField(chrono);
1801             if (iShort) {
1802                 return field.getAsShortText(instant, locale);
1803             } else {
1804                 return field.getAsText(instant, locale);
1805             }
1806         }
1807 
1808         private String print(ReadablePartial partial, Locale locale) {
1809             if (partial.isSupported(iFieldType)) {
1810                 DateTimeField field = iFieldType.getField(partial.getChronology());
1811                 if (iShort) {
1812                     return field.getAsShortText(partial, locale);
1813                 } else {
1814                     return field.getAsText(partial, locale);
1815                 }
1816             } else {
1817                 return "\ufffd";
1818             }
1819         }
1820 
1821         public int estimateParsedLength() {
1822             return estimatePrintedLength();
1823         }
1824 
1825         @SuppressWarnings("unchecked")
1826         public int parseInto(DateTimeParserBucket bucket, String text, int position) {
1827             Locale locale = bucket.getLocale();
1828             // handle languages which might have non ASCII A-Z or punctuation
1829             // bug 1788282
1830             Set<String> validValues = null;
1831             int maxLength = 0;
1832             synchronized (cParseCache) {
1833                 Map<DateTimeFieldType, Object[]> innerMap = cParseCache.get(locale);
1834                 if (innerMap == null) {
1835                     innerMap = new HashMap<DateTimeFieldType, Object[]>();
1836                     cParseCache.put(locale, innerMap);
1837                 }
1838                 Object[] array = innerMap.get(iFieldType);
1839                 if (array == null) {
1840                     validValues = new HashSet<String>(32);
1841                     MutableDateTime dt = new MutableDateTime(0L, DateTimeZone.UTC);
1842                     Property property = dt.property(iFieldType);
1843                     int min = property.getMinimumValueOverall();
1844                     int max = property.getMaximumValueOverall();
1845                     if (max - min > 32) {  // protect against invalid fields
1846                         return ~position;
1847                     }
1848                     maxLength = property.getMaximumTextLength(locale);
1849                     for (int i = min; i <= max; i++) {
1850                         property.set(i);
1851                         validValues.add(property.getAsShortText(locale));
1852                         validValues.add(property.getAsShortText(locale).toLowerCase(locale));
1853                         validValues.add(property.getAsShortText(locale).toUpperCase(locale));
1854                         validValues.add(property.getAsText(locale));
1855                         validValues.add(property.getAsText(locale).toLowerCase(locale));
1856                         validValues.add(property.getAsText(locale).toUpperCase(locale));
1857                     }
1858                     if ("en".equals(locale.getLanguage()) && iFieldType == DateTimeFieldType.era()) {
1859                         // hack to support for parsing "BCE" and "CE" if the language is English
1860                         validValues.add("BCE");
1861                         validValues.add("bce");
1862                         validValues.add("CE");
1863                         validValues.add("ce");
1864                         maxLength = 3;
1865                     }
1866                     array = new Object[] {validValues, Integer.valueOf(maxLength)};
1867                     innerMap.put(iFieldType, array);
1868                 } else {
1869                     validValues = (Set<String>) array[0];
1870                     maxLength = ((Integer) array[1]).intValue();
1871                 }
1872             }
1873             // match the longest string first using our knowledge of the max length
1874             int limit = Math.min(text.length(), position + maxLength);
1875             for (int i = limit; i > position; i--) {
1876                 String match = text.substring(position, i);
1877                 if (validValues.contains(match)) {
1878                     bucket.saveField(iFieldType, match, locale);
1879                     return i;
1880                 }
1881             }
1882             return ~position;
1883         }
1884     }
1885 
1886     //-----------------------------------------------------------------------
1887     static class Fraction
1888             implements DateTimePrinter, DateTimeParser {
1889 
1890         private final DateTimeFieldType iFieldType;
1891         protected int iMinDigits;
1892         protected int iMaxDigits;
1893 
1894         protected Fraction(DateTimeFieldType fieldType, int minDigits, int maxDigits) {
1895             super();
1896             iFieldType = fieldType;
1897             // Limit the precision requirements.
1898             if (maxDigits > 18) {
1899                 maxDigits = 18;
1900             }
1901             iMinDigits = minDigits;
1902             iMaxDigits = maxDigits;
1903         }
1904 
1905         public int estimatePrintedLength() {
1906             return iMaxDigits;
1907         }
1908 
1909         public void printTo(
1910                 StringBuffer buf, long instant, Chronology chrono,
1911                 int displayOffset, DateTimeZone displayZone, Locale locale) {
1912             try {
1913                 printTo(buf, null, instant, chrono);
1914             } catch (IOException e) {
1915                 // Not gonna happen.
1916             }
1917         }
1918 
1919         public void printTo(
1920                 Writer out, long instant, Chronology chrono,
1921                 int displayOffset, DateTimeZone displayZone, Locale locale) throws IOException {
1922             printTo(null, out, instant, chrono);
1923         }
1924 
1925         public void printTo(StringBuffer buf, ReadablePartial partial, Locale locale) {
1926             // removed check whether field is supported, as input field is typically
1927             // secondOfDay which is unsupported by TimeOfDay
1928             long millis = partial.getChronology().set(partial, 0L);
1929             try {
1930                 printTo(buf, null, millis, partial.getChronology());
1931             } catch (IOException e) {
1932                 // Not gonna happen.
1933             }
1934         }
1935 
1936         public void printTo(Writer out, ReadablePartial partial, Locale locale) throws IOException {
1937             // removed check whether field is supported, as input field is typically
1938             // secondOfDay which is unsupported by TimeOfDay
1939             long millis = partial.getChronology().set(partial, 0L);
1940             printTo(null, out, millis, partial.getChronology());
1941         }
1942 
1943         protected void printTo(StringBuffer buf, Writer out, long instant, Chronology chrono)
1944             throws IOException
1945         {
1946             DateTimeField field = iFieldType.getField(chrono);
1947             int minDigits = iMinDigits;
1948 
1949             long fraction;
1950             try {
1951                 fraction = field.remainder(instant);
1952             } catch (RuntimeException e) {
1953                 if (buf != null) {
1954                     appendUnknownString(buf, minDigits);
1955                 } else {
1956                     printUnknownString(out, minDigits);
1957                 }
1958                 return;
1959             }
1960 
1961             if (fraction == 0) {
1962                 if (buf != null) {
1963                     while (--minDigits >= 0) {
1964                         buf.append('0');
1965                     }
1966                 } else {
1967                     while (--minDigits >= 0) {
1968                         out.write('0');
1969                     }
1970                 }
1971                 return;
1972             }
1973 
1974             String str;
1975             long[] fractionData = getFractionData(fraction, field);
1976             long scaled = fractionData[0];
1977             int maxDigits = (int) fractionData[1];
1978             
1979             if ((scaled & 0x7fffffff) == scaled) {
1980                 str = Integer.toString((int) scaled);
1981             } else {
1982                 str = Long.toString(scaled);
1983             }
1984 
1985             int length = str.length();
1986             int digits = maxDigits;
1987             while (length < digits) {
1988                 if (buf != null) {
1989                     buf.append('0');
1990                 } else {
1991                     out.write('0');
1992                 }
1993                 minDigits--;
1994                 digits--;
1995             }
1996 
1997             if (minDigits < digits) {
1998                 // Chop off as many trailing zero digits as necessary.
1999                 while (minDigits < digits) {
2000                     if (length <= 1 || str.charAt(length - 1) != '0') {
2001                         break;
2002                     }
2003                     digits--;
2004                     length--;
2005                 }
2006                 if (length < str.length()) {
2007                     if (buf != null) {
2008                         for (int i=0; i<length; i++) {
2009                             buf.append(str.charAt(i));
2010                         }
2011                     } else {
2012                         for (int i=0; i<length; i++) {
2013                             out.write(str.charAt(i));
2014                         }
2015                     }
2016                     return;
2017                 }
2018             }
2019 
2020             if (buf != null) {
2021                 buf.append(str);
2022             } else {
2023                 out.write(str);
2024             }
2025         }
2026         
2027         private long[] getFractionData(long fraction, DateTimeField field) {
2028             long rangeMillis = field.getDurationField().getUnitMillis();
2029             long scalar;
2030             int maxDigits = iMaxDigits;
2031             while (true) {
2032                 switch (maxDigits) {
2033                 default: scalar = 1L; break;
2034                 case 1:  scalar = 10L; break;
2035                 case 2:  scalar = 100L; break;
2036                 case 3:  scalar = 1000L; break;
2037                 case 4:  scalar = 10000L; break;
2038                 case 5:  scalar = 100000L; break;
2039                 case 6:  scalar = 1000000L; break;
2040                 case 7:  scalar = 10000000L; break;
2041                 case 8:  scalar = 100000000L; break;
2042                 case 9:  scalar = 1000000000L; break;
2043                 case 10: scalar = 10000000000L; break;
2044                 case 11: scalar = 100000000000L; break;
2045                 case 12: scalar = 1000000000000L; break;
2046                 case 13: scalar = 10000000000000L; break;
2047                 case 14: scalar = 100000000000000L; break;
2048                 case 15: scalar = 1000000000000000L; break;
2049                 case 16: scalar = 10000000000000000L; break;
2050                 case 17: scalar = 100000000000000000L; break;
2051                 case 18: scalar = 1000000000000000000L; break;
2052                 }
2053                 if (((rangeMillis * scalar) / scalar) == rangeMillis) {
2054                     break;
2055                 }
2056                 // Overflowed: scale down.
2057                 maxDigits--;
2058             }
2059             
2060             return new long[] {fraction * scalar / rangeMillis, maxDigits};
2061         }
2062 
2063         public int estimateParsedLength() {
2064             return iMaxDigits;
2065         }
2066 
2067         public int parseInto(DateTimeParserBucket bucket, String text, int position) {
2068             DateTimeField field = iFieldType.getField(bucket.getChronology());
2069             
2070             int limit = Math.min(iMaxDigits, text.length() - position);
2071 
2072             long value = 0;
2073             long n = field.getDurationField().getUnitMillis() * 10;
2074             int length = 0;
2075             while (length < limit) {
2076                 char c = text.charAt(position + length);
2077                 if (c < '0' || c > '9') {
2078                     break;
2079                 }
2080                 length++;
2081                 long nn = n / 10;
2082                 value += (c - '0') * nn;
2083                 n = nn;
2084             }
2085 
2086             value /= 10;
2087 
2088             if (length == 0) {
2089                 return ~position;
2090             }
2091 
2092             if (value > Integer.MAX_VALUE) {
2093                 return ~position;
2094             }
2095 
2096             DateTimeField parseField = new PreciseDateTimeField(
2097                 DateTimeFieldType.millisOfSecond(),
2098                 MillisDurationField.INSTANCE,
2099                 field.getDurationField());
2100 
2101             bucket.saveField(parseField, (int) value);
2102 
2103             return position + length;
2104         }
2105     }
2106 
2107     //-----------------------------------------------------------------------
2108     static class TimeZoneOffset
2109             implements DateTimePrinter, DateTimeParser {
2110 
2111         private final String iZeroOffsetPrintText;
2112         private final String iZeroOffsetParseText;
2113         private final boolean iShowSeparators;
2114         private final int iMinFields;
2115         private final int iMaxFields;
2116 
2117         TimeZoneOffset(String zeroOffsetPrintText, String zeroOffsetParseText,
2118                                 boolean showSeparators,
2119                                 int minFields, int maxFields)
2120         {
2121             super();
2122             iZeroOffsetPrintText = zeroOffsetPrintText;
2123             iZeroOffsetParseText = zeroOffsetParseText;
2124             iShowSeparators = showSeparators;
2125             if (minFields <= 0 || maxFields < minFields) {
2126                 throw new IllegalArgumentException();
2127             }
2128             if (minFields > 4) {
2129                 minFields = 4;
2130                 maxFields = 4;
2131             }
2132             iMinFields = minFields;
2133             iMaxFields = maxFields;
2134         }
2135             
2136         public int estimatePrintedLength() {
2137             int est = 1 + iMinFields << 1;
2138             if (iShowSeparators) {
2139                 est += iMinFields - 1;
2140             }
2141             if (iZeroOffsetPrintText != null && iZeroOffsetPrintText.length() > est) {
2142                 est = iZeroOffsetPrintText.length();
2143             }
2144             return est;
2145         }
2146         
2147         public void printTo(
2148                 StringBuffer buf, long instant, Chronology chrono,
2149                 int displayOffset, DateTimeZone displayZone, Locale locale) {
2150             if (displayZone == null) {
2151                 return;  // no zone
2152             }
2153             if (displayOffset == 0 && iZeroOffsetPrintText != null) {
2154                 buf.append(iZeroOffsetPrintText);
2155                 return;
2156             }
2157             if (displayOffset >= 0) {
2158                 buf.append('+');
2159             } else {
2160                 buf.append('-');
2161                 displayOffset = -displayOffset;
2162             }
2163 
2164             int hours = displayOffset / DateTimeConstants.MILLIS_PER_HOUR;
2165             FormatUtils.appendPaddedInteger(buf, hours, 2);
2166             if (iMaxFields == 1) {
2167                 return;
2168             }
2169             displayOffset -= hours * (int)DateTimeConstants.MILLIS_PER_HOUR;
2170             if (displayOffset == 0 && iMinFields <= 1) {
2171                 return;
2172             }
2173 
2174             int minutes = displayOffset / DateTimeConstants.MILLIS_PER_MINUTE;
2175             if (iShowSeparators) {
2176                 buf.append(':');
2177             }
2178             FormatUtils.appendPaddedInteger(buf, minutes, 2);
2179             if (iMaxFields == 2) {
2180                 return;
2181             }
2182             displayOffset -= minutes * DateTimeConstants.MILLIS_PER_MINUTE;
2183             if (displayOffset == 0 && iMinFields <= 2) {
2184                 return;
2185             }
2186 
2187             int seconds = displayOffset / DateTimeConstants.MILLIS_PER_SECOND;
2188             if (iShowSeparators) {
2189                 buf.append(':');
2190             }
2191             FormatUtils.appendPaddedInteger(buf, seconds, 2);
2192             if (iMaxFields == 3) {
2193                 return;
2194             }
2195             displayOffset -= seconds * DateTimeConstants.MILLIS_PER_SECOND;
2196             if (displayOffset == 0 && iMinFields <= 3) {
2197                 return;
2198             }
2199 
2200             if (iShowSeparators) {
2201                 buf.append('.');
2202             }
2203             FormatUtils.appendPaddedInteger(buf, displayOffset, 3);
2204         }
2205         
2206         public void printTo(
2207                 Writer out, long instant, Chronology chrono,
2208                 int displayOffset, DateTimeZone displayZone, Locale locale) throws IOException {
2209             if (displayZone == null) {
2210                 return;  // no zone
2211             }
2212             if (displayOffset == 0 && iZeroOffsetPrintText != null) {
2213                 out.write(iZeroOffsetPrintText);
2214                 return;
2215             }
2216             if (displayOffset >= 0) {
2217                 out.write('+');
2218             } else {
2219                 out.write('-');
2220                 displayOffset = -displayOffset;
2221             }
2222 
2223             int hours = displayOffset / DateTimeConstants.MILLIS_PER_HOUR;
2224             FormatUtils.writePaddedInteger(out, hours, 2);
2225             if (iMaxFields == 1) {
2226                 return;
2227             }
2228             displayOffset -= hours * (int)DateTimeConstants.MILLIS_PER_HOUR;
2229             if (displayOffset == 0 && iMinFields == 1) {
2230                 return;
2231             }
2232 
2233             int minutes = displayOffset / DateTimeConstants.MILLIS_PER_MINUTE;
2234             if (iShowSeparators) {
2235                 out.write(':');
2236             }
2237             FormatUtils.writePaddedInteger(out, minutes, 2);
2238             if (iMaxFields == 2) {
2239                 return;
2240             }
2241             displayOffset -= minutes * DateTimeConstants.MILLIS_PER_MINUTE;
2242             if (displayOffset == 0 && iMinFields == 2) {
2243                 return;
2244             }
2245 
2246             int seconds = displayOffset / DateTimeConstants.MILLIS_PER_SECOND;
2247             if (iShowSeparators) {
2248                 out.write(':');
2249             }
2250             FormatUtils.writePaddedInteger(out, seconds, 2);
2251             if (iMaxFields == 3) {
2252                 return;
2253             }
2254             displayOffset -= seconds * DateTimeConstants.MILLIS_PER_SECOND;
2255             if (displayOffset == 0 && iMinFields == 3) {
2256                 return;
2257             }
2258 
2259             if (iShowSeparators) {
2260                 out.write('.');
2261             }
2262             FormatUtils.writePaddedInteger(out, displayOffset, 3);
2263         }
2264 
2265         public void printTo(StringBuffer buf, ReadablePartial partial, Locale locale) {
2266             // no zone info
2267         }
2268 
2269         public void printTo(Writer out, ReadablePartial partial, Locale locale) throws IOException {
2270             // no zone info
2271         }
2272 
2273         public int estimateParsedLength() {
2274             return estimatePrintedLength();
2275         }
2276 
2277         public int parseInto(DateTimeParserBucket bucket, String text, int position) {
2278             int limit = text.length() - position;
2279 
2280             zeroOffset:
2281             if (iZeroOffsetParseText != null) {
2282                 if (iZeroOffsetParseText.length() == 0) {
2283                     // Peek ahead, looking for sign character.
2284                     if (limit > 0) {
2285                         char c = text.charAt(position);
2286                         if (c == '-' || c == '+') {
2287                             break zeroOffset;
2288                         }
2289                     }
2290                     bucket.setOffset(Integer.valueOf(0));
2291                     return position;
2292                 }
2293                 if (text.regionMatches(true, position, iZeroOffsetParseText, 0, iZeroOffsetParseText.length())) {
2294                     bucket.setOffset(Integer.valueOf(0));
2295                     return position + iZeroOffsetParseText.length();
2296                 }
2297             }
2298 
2299             // Format to expect is sign character followed by at least one digit.
2300 
2301             if (limit <= 1) {
2302                 return ~position;
2303             }
2304 
2305             boolean negative;
2306             char c = text.charAt(position);
2307             if (c == '-') {
2308                 negative = true;
2309             } else if (c == '+') {
2310                 negative = false;
2311             } else {
2312                 return ~position;
2313             }
2314 
2315             limit--;
2316             position++;
2317 
2318             // Format following sign is one of:
2319             //
2320             // hh
2321             // hhmm
2322             // hhmmss
2323             // hhmmssSSS
2324             // hh:mm
2325             // hh:mm:ss
2326             // hh:mm:ss.SSS
2327 
2328             // First parse hours.
2329 
2330             if (digitCount(text, position, 2) < 2) {
2331                 // Need two digits for hour.
2332                 return ~position;
2333             }
2334 
2335             int offset;
2336 
2337             int hours = FormatUtils.parseTwoDigits(text, position);
2338             if (hours > 23) {
2339                 return ~position;
2340             }
2341             offset = hours * DateTimeConstants.MILLIS_PER_HOUR;
2342             limit -= 2;
2343             position += 2;
2344 
2345             parse: {
2346                 // Need to decide now if separators are expected or parsing
2347                 // stops at hour field.
2348 
2349                 if (limit <= 0) {
2350                     break parse;
2351                 }
2352 
2353                 boolean expectSeparators;
2354                 c = text.charAt(position);
2355                 if (c == ':') {
2356                     expectSeparators = true;
2357                     limit--;
2358                     position++;
2359                 } else if (c >= '0' && c <= '9') {
2360                     expectSeparators = false;
2361                 } else {
2362                     break parse;
2363                 }
2364 
2365                 // Proceed to parse minutes.
2366 
2367                 int count = digitCount(text, position, 2);
2368                 if (count == 0 && !expectSeparators) {
2369                     break parse;
2370                 } else if (count < 2) {
2371                     // Need two digits for minute.
2372                     return ~position;
2373                 }
2374 
2375                 int minutes = FormatUtils.parseTwoDigits(text, position);
2376                 if (minutes > 59) {
2377                     return ~position;
2378                 }
2379                 offset += minutes * DateTimeConstants.MILLIS_PER_MINUTE;
2380                 limit -= 2;
2381                 position += 2;
2382 
2383                 // Proceed to parse seconds.
2384 
2385                 if (limit <= 0) {
2386                     break parse;
2387                 }
2388 
2389                 if (expectSeparators) {
2390                     if (text.charAt(position) != ':') {
2391                         break parse;
2392                     }
2393                     limit--;
2394                     position++;
2395                 }
2396 
2397                 count = digitCount(text, position, 2);
2398                 if (count == 0 && !expectSeparators) {
2399                     break parse;
2400                 } else if (count < 2) {
2401                     // Need two digits for second.
2402                     return ~position;
2403                 }
2404 
2405                 int seconds = FormatUtils.parseTwoDigits(text, position);
2406                 if (seconds > 59) {
2407                     return ~position;
2408                 }
2409                 offset += seconds * DateTimeConstants.MILLIS_PER_SECOND;
2410                 limit -= 2;
2411                 position += 2;
2412 
2413                 // Proceed to parse fraction of second.
2414 
2415                 if (limit <= 0) {
2416                     break parse;
2417                 }
2418 
2419                 if (expectSeparators) {
2420                     if (text.charAt(position) != '.' && text.charAt(position) != ',') {
2421                         break parse;
2422                     }
2423                     limit--;
2424                     position++;
2425                 }
2426                 
2427                 count = digitCount(text, position, 3);
2428                 if (count == 0 && !expectSeparators) {
2429                     break parse;
2430                 } else if (count < 1) {
2431                     // Need at least one digit for fraction of second.
2432                     return ~position;
2433                 }
2434 
2435                 offset += (text.charAt(position++) - '0') * 100;
2436                 if (count > 1) {
2437                     offset += (text.charAt(position++) - '0') * 10;
2438                     if (count > 2) {
2439                         offset += text.charAt(position++) - '0';
2440                     }
2441                 }
2442             }
2443 
2444             bucket.setOffset(Integer.valueOf(negative ? -offset : offset));
2445             return position;
2446         }
2447 
2448         /**
2449          * Returns actual amount of digits to parse, but no more than original
2450          * 'amount' parameter.
2451          */
2452         private int digitCount(String text, int position, int amount) {
2453             int limit = Math.min(text.length() - position, amount);
2454             amount = 0;
2455             for (; limit > 0; limit--) {
2456                 char c = text.charAt(position + amount);
2457                 if (c < '0' || c > '9') {
2458                     break;
2459                 }
2460                 amount++;
2461             }
2462             return amount;
2463         }
2464     }
2465 
2466     //-----------------------------------------------------------------------
2467     static class TimeZoneName
2468             implements DateTimePrinter, DateTimeParser {
2469 
2470         static final int LONG_NAME = 0;
2471         static final int SHORT_NAME = 1;
2472 
2473         private final Map<String, DateTimeZone> iParseLookup;
2474         private final int iType;
2475 
2476         TimeZoneName(int type, Map<String, DateTimeZone> parseLookup) {
2477             super();
2478             iType = type;
2479             iParseLookup = parseLookup;
2480         }
2481 
2482         public int estimatePrintedLength() {
2483             return (iType == SHORT_NAME ? 4 : 20);
2484         }
2485 
2486         public void printTo(
2487                 StringBuffer buf, long instant, Chronology chrono,
2488                 int displayOffset, DateTimeZone displayZone, Locale locale) {
2489             buf.append(print(instant - displayOffset, displayZone, locale));
2490         }
2491 
2492         public void printTo(
2493                 Writer out, long instant, Chronology chrono,
2494                 int displayOffset, DateTimeZone displayZone, Locale locale) throws IOException {
2495             out.write(print(instant - displayOffset, displayZone, locale));
2496         }
2497 
2498         private String print(long instant, DateTimeZone displayZone, Locale locale) {
2499             if (displayZone == null) {
2500                 return "";  // no zone
2501             }
2502             switch (iType) {
2503                 case LONG_NAME:
2504                     return displayZone.getName(instant, locale);
2505                 case SHORT_NAME:
2506                     return displayZone.getShortName(instant, locale);
2507             }
2508             return "";
2509         }
2510 
2511         public void printTo(StringBuffer buf, ReadablePartial partial, Locale locale) {
2512             // no zone info
2513         }
2514 
2515         public void printTo(Writer out, ReadablePartial partial, Locale locale) throws IOException {
2516             // no zone info
2517         }
2518 
2519         public int estimateParsedLength() {
2520             return (iType == SHORT_NAME ? 4 : 20);
2521         }
2522 
2523         public int parseInto(DateTimeParserBucket bucket, String text, int position) {
2524             Map<String, DateTimeZone> parseLookup = iParseLookup;
2525             parseLookup = (parseLookup != null ? parseLookup : DateTimeUtils.getDefaultTimeZoneNames());
2526             String str = text.substring(position);
2527             for (String name : parseLookup.keySet()) {
2528                 if (str.startsWith(name)) {
2529                     bucket.setZone(parseLookup.get(name));
2530                     return position + name.length();
2531                 }
2532             }
2533             return ~position;
2534         }
2535     }
2536 
2537     //-----------------------------------------------------------------------
2538     static enum TimeZoneId
2539             implements DateTimePrinter, DateTimeParser {
2540 
2541         INSTANCE;
2542         static final Set<String> ALL_IDS = DateTimeZone.getAvailableIDs();
2543         static final int MAX_LENGTH;
2544         static {
2545             int max = 0;
2546             for (String id : ALL_IDS) {
2547                 max = Math.max(max, id.length());
2548             }
2549             MAX_LENGTH = max;
2550         }
2551 
2552         public int estimatePrintedLength() {
2553             return MAX_LENGTH;
2554         }
2555 
2556         public void printTo(
2557                 StringBuffer buf, long instant, Chronology chrono,
2558                 int displayOffset, DateTimeZone displayZone, Locale locale) {
2559             buf.append(displayZone != null ? displayZone.getID() : "");
2560         }
2561 
2562         public void printTo(
2563                 Writer out, long instant, Chronology chrono,
2564                 int displayOffset, DateTimeZone displayZone, Locale locale) throws IOException {
2565             out.write(displayZone != null ? displayZone.getID() : "");
2566         }
2567 
2568         public void printTo(StringBuffer buf, ReadablePartial partial, Locale locale) {
2569             // no zone info
2570         }
2571 
2572         public void printTo(Writer out, ReadablePartial partial, Locale locale) throws IOException {
2573             // no zone info
2574         }
2575 
2576         public int estimateParsedLength() {
2577             return MAX_LENGTH;
2578         }
2579 
2580         public int parseInto(DateTimeParserBucket bucket, String text, int position) {
2581             String str = text.substring(position);
2582             String best = null;
2583             for (String id : ALL_IDS) {
2584                 if (str.startsWith(id)) {
2585                     if (best == null || id.length() > best.length()) {
2586                         best = id;
2587                     }
2588                 }
2589             }
2590             if (best != null) {
2591                 bucket.setZone(DateTimeZone.forID(best));
2592                 return position + best.length();
2593             }
2594             return ~position;
2595         }
2596     }
2597 
2598     //-----------------------------------------------------------------------
2599     static class Composite
2600             implements DateTimePrinter, DateTimeParser {
2601 
2602         private final DateTimePrinter[] iPrinters;
2603         private final DateTimeParser[] iParsers;
2604 
2605         private final int iPrintedLengthEstimate;
2606         private final int iParsedLengthEstimate;
2607 
2608         Composite(List<Object> elementPairs) {
2609             super();
2610 
2611             List<Object> printerList = new ArrayList<Object>();
2612             List<Object> parserList = new ArrayList<Object>();
2613 
2614             decompose(elementPairs, printerList, parserList);
2615 
2616             if (printerList.contains(null) || printerList.isEmpty()) {
2617                 iPrinters = null;
2618                 iPrintedLengthEstimate = 0;
2619             } else {
2620                 int size = printerList.size();
2621                 iPrinters = new DateTimePrinter[size];
2622                 int printEst = 0;
2623                 for (int i=0; i<size; i++) {
2624                     DateTimePrinter printer = (DateTimePrinter) printerList.get(i);
2625                     printEst += printer.estimatePrintedLength();
2626                     iPrinters[i] = printer;
2627                 }
2628                 iPrintedLengthEstimate = printEst;
2629             }
2630 
2631             if (parserList.contains(null) || parserList.isEmpty()) {
2632                 iParsers = null;
2633                 iParsedLengthEstimate = 0;
2634             } else {
2635                 int size = parserList.size();
2636                 iParsers = new DateTimeParser[size];
2637                 int parseEst = 0;
2638                 for (int i=0; i<size; i++) {
2639                     DateTimeParser parser = (DateTimeParser) parserList.get(i);
2640                     parseEst += parser.estimateParsedLength();
2641                     iParsers[i] = parser;
2642                 }
2643                 iParsedLengthEstimate = parseEst;
2644             }
2645         }
2646 
2647         public int estimatePrintedLength() {
2648             return iPrintedLengthEstimate;
2649         }
2650 
2651         public void printTo(
2652                 StringBuffer buf, long instant, Chronology chrono,
2653                 int displayOffset, DateTimeZone displayZone, Locale locale) {
2654             DateTimePrinter[] elements = iPrinters;
2655             if (elements == null) {
2656                 throw new UnsupportedOperationException();
2657             }
2658 
2659             if (locale == null) {
2660                 // Guard against default locale changing concurrently.
2661                 locale = Locale.getDefault();
2662             }
2663 
2664             int len = elements.length;
2665             for (int i = 0; i < len; i++) {
2666                 elements[i].printTo(buf, instant, chrono, displayOffset, displayZone, locale);
2667             }
2668         }
2669 
2670         public void printTo(
2671                 Writer out, long instant, Chronology chrono,
2672                 int displayOffset, DateTimeZone displayZone, Locale locale) throws IOException {
2673             DateTimePrinter[] elements = iPrinters;
2674             if (elements == null) {
2675                 throw new UnsupportedOperationException();
2676             }
2677 
2678             if (locale == null) {
2679                 // Guard against default locale changing concurrently.
2680                 locale = Locale.getDefault();
2681             }
2682 
2683             int len = elements.length;
2684             for (int i = 0; i < len; i++) {
2685                 elements[i].printTo(out, instant, chrono, displayOffset, displayZone, locale);
2686             }
2687         }
2688 
2689         public void printTo(StringBuffer buf, ReadablePartial partial, Locale locale) {
2690             DateTimePrinter[] elements = iPrinters;
2691             if (elements == null) {
2692                 throw new UnsupportedOperationException();
2693             }
2694 
2695             if (locale == null) {
2696                 // Guard against default locale changing concurrently.
2697                 locale = Locale.getDefault();
2698             }
2699 
2700             int len = elements.length;
2701             for (int i=0; i<len; i++) {
2702                 elements[i].printTo(buf, partial, locale);
2703             }
2704         }
2705 
2706         public void printTo(Writer out, ReadablePartial partial, Locale locale) throws IOException {
2707             DateTimePrinter[] elements = iPrinters;
2708             if (elements == null) {
2709                 throw new UnsupportedOperationException();
2710             }
2711 
2712             if (locale == null) {
2713                 // Guard against default locale changing concurrently.
2714                 locale = Locale.getDefault();
2715             }
2716 
2717             int len = elements.length;
2718             for (int i=0; i<len; i++) {
2719                 elements[i].printTo(out, partial, locale);
2720             }
2721         }
2722 
2723         public int estimateParsedLength() {
2724             return iParsedLengthEstimate;
2725         }
2726 
2727         public int parseInto(DateTimeParserBucket bucket, String text, int position) {
2728             DateTimeParser[] elements = iParsers;
2729             if (elements == null) {
2730                 throw new UnsupportedOperationException();
2731             }
2732 
2733             int len = elements.length;
2734             for (int i=0; i<len && position >= 0; i++) {
2735                 position = elements[i].parseInto(bucket, text, position);
2736             }
2737             return position;
2738         }
2739 
2740         boolean isPrinter() {
2741             return iPrinters != null;
2742         }
2743 
2744         boolean isParser() {
2745             return iParsers != null;
2746         }
2747 
2748         /**
2749          * Processes the element pairs, putting results into the given printer
2750          * and parser lists.
2751          */
2752         private void decompose(List<Object> elementPairs, List<Object> printerList, List<Object> parserList) {
2753             int size = elementPairs.size();
2754             for (int i=0; i<size; i+=2) {
2755                 Object element = elementPairs.get(i);
2756                 if (element instanceof Composite) {
2757                     addArrayToList(printerList, ((Composite)element).iPrinters);
2758                 } else {
2759                     printerList.add(element);
2760                 }
2761 
2762                 element = elementPairs.get(i + 1);
2763                 if (element instanceof Composite) {
2764                     addArrayToList(parserList, ((Composite)element).iParsers);
2765                 } else {
2766                     parserList.add(element);
2767                 }
2768             }
2769         }
2770 
2771         private void addArrayToList(List<Object> list, Object[] array) {
2772             if (array != null) {
2773                 for (int i=0; i<array.length; i++) {
2774                     list.add(array[i]);
2775                 }
2776             }
2777         }
2778     }
2779 
2780     //-----------------------------------------------------------------------
2781     static class MatchingParser
2782             implements DateTimeParser {
2783 
2784         private final DateTimeParser[] iParsers;
2785         private final int iParsedLengthEstimate;
2786 
2787         MatchingParser(DateTimeParser[] parsers) {
2788             super();
2789             iParsers = parsers;
2790             int est = 0;
2791             for (int i=parsers.length; --i>=0 ;) {
2792                 DateTimeParser parser = parsers[i];
2793                 if (parser != null) {
2794                     int len = parser.estimateParsedLength();
2795                     if (len > est) {
2796                         est = len;
2797                     }
2798                 }
2799             }
2800             iParsedLengthEstimate = est;
2801         }
2802 
2803         public int estimateParsedLength() {
2804             return iParsedLengthEstimate;
2805         }
2806 
2807         public int parseInto(DateTimeParserBucket bucket, String text, int position) {
2808             DateTimeParser[] parsers = iParsers;
2809             int length = parsers.length;
2810 
2811             final Object originalState = bucket.saveState();
2812             boolean isOptional = false;
2813 
2814             int bestValidPos = position;
2815             Object bestValidState = null;
2816 
2817             int bestInvalidPos = position;
2818 
2819             for (int i=0; i<length; i++) {
2820                 DateTimeParser parser = parsers[i];
2821                 if (parser == null) {
2822                     // The empty parser wins only if nothing is better.
2823                     if (bestValidPos <= position) {
2824                         return position;
2825                     }
2826                     isOptional = true;
2827                     break;
2828                 }
2829                 int parsePos = parser.parseInto(bucket, text, position);
2830                 if (parsePos >= position) {
2831                     if (parsePos > bestValidPos) {
2832                         if (parsePos >= text.length() ||
2833                             (i + 1) >= length || parsers[i + 1] == null) {
2834 
2835                             // Completely parsed text or no more parsers to
2836                             // check. Skip the rest.
2837                             return parsePos;
2838                         }
2839                         bestValidPos = parsePos;
2840                         bestValidState = bucket.saveState();
2841                     }
2842                 } else {
2843                     if (parsePos < 0) {
2844                         parsePos = ~parsePos;
2845                         if (parsePos > bestInvalidPos) {
2846                             bestInvalidPos = parsePos;
2847                         }
2848                     }
2849                 }
2850                 bucket.restoreState(originalState);
2851             }
2852 
2853             if (bestValidPos > position || (bestValidPos == position && isOptional)) {
2854                 // Restore the state to the best valid parse.
2855                 if (bestValidState != null) {
2856                     bucket.restoreState(bestValidState);
2857                 }
2858                 return bestValidPos;
2859             }
2860 
2861             return ~bestInvalidPos;
2862         }
2863     }
2864 
2865 }