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

70 public class DateTimeFormatterBuilder {
71
72     /** Array of printers and parsers (alternating). */
73     private ArrayList<Object> iElementPairs;
74     /** Cache of the last returned formatter. */
75     private Object iFormatter;
76
77     //-----------------------------------------------------------------------
78     /**
79      * Creates a DateTimeFormatterBuilder.
80      */

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

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

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

151     public DateTimeParser toParser() {
152         Object f = getFormatter();
153         if (isParser(f)) {
154             InternalParser ip = (InternalParser) f;
155             return InternalParserDateTimeParser.of(ip);
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.getPrinter0(), formatter.getParser0());
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(DateTimePrinterInternalPrinter.of(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, DateTimeParserInternalParser.of(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(DateTimePrinterInternalPrinter.of(printer), DateTimeParserInternalParser.of(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      * nullthis 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(DateTimePrinterInternalPrinter.of(printer), DateTimeParserInternalParser.of(parsers[0]));
317         }
318
319         InternalParser[] copyOfParsers = new InternalParser[length];
320         int i;
321         for (i = 0; i < length - 1; i++) {
322             if ((copyOfParsers[i] = DateTimeParserInternalParser.of(parsers[i])) == null) {
323                 throw new IllegalArgumentException("Incomplete parser array");
324             }
325         }
326         copyOfParsers[i] = DateTimeParserInternalParser.of(parsers[i]);
327
328         return append0(DateTimePrinterInternalPrinter.of(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         InternalParser[] parsers = new InternalParser[] {DateTimeParserInternalParser.of(parser), null};
347         return append0(nullnew 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             InternalPrinter printer, InternalParser 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 trueif 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 trueif 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 InternalPrinter) {
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 InternalParser) {
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(Appendable appendable, int len) throws IOException {
1189         for (int i = len; --i >= 0;) {
1190             appendable.append('\ufffd');
1191         }
1192     }
1193
1194     //-----------------------------------------------------------------------
1195     static class CharacterLiteral
1196             implements InternalPrinter, InternalParser {
1197
1198         private final char iValue;
1199
1200         CharacterLiteral(char value) {
1201             super();
1202             iValue = value;
1203         }
1204
1205         public int estimatePrintedLength() {
1206             return 1;
1207         }
1208
1209         public void printTo(
1210                 Appendable appendable, long instant, Chronology chrono,
1211                 int displayOffset, DateTimeZone displayZone, Locale locale) throws IOException {
1212             appendable.append(iValue);
1213         }
1214
1215         public void printTo(Appendable appendable, ReadablePartial partial, Locale locale) throws IOException {
1216             appendable.append(iValue);
1217         }
1218
1219         public int estimateParsedLength() {
1220             return 1;
1221         }
1222
1223         public int parseInto(DateTimeParserBucket bucket, CharSequence text, int position) {
1224             if (position >= text.length()) {
1225                 return ~position;
1226             }
1227
1228             char a = text.charAt(position);
1229             char b = iValue;
1230
1231             if (a != b) {
1232                 a = Character.toUpperCase(a);
1233                 b = Character.toUpperCase(b);
1234                 if (a != b) {
1235                     a = Character.toLowerCase(a);
1236                     b = Character.toLowerCase(b);
1237                     if (a != b) {
1238                         return ~position;
1239                     }
1240                 }
1241             }
1242
1243             return position + 1;
1244         }
1245     }
1246
1247     //-----------------------------------------------------------------------
1248     static class StringLiteral
1249             implements InternalPrinter, InternalParser {
1250
1251         private final String iValue;
1252
1253         StringLiteral(String value) {
1254             super();
1255             iValue = value;
1256         }
1257
1258         public int estimatePrintedLength() {
1259             return iValue.length();
1260         }
1261
1262         public void printTo(
1263                 Appendable appendable, long instant, Chronology chrono,
1264                 int displayOffset, DateTimeZone displayZone, Locale locale) throws IOException {
1265             appendable.append(iValue);
1266         }
1267
1268         public void printTo(Appendable appendable, ReadablePartial partial, Locale locale) throws IOException {
1269             appendable.append(iValue);
1270         }
1271
1272         public int estimateParsedLength() {
1273             return iValue.length();
1274         }
1275
1276         public int parseInto(DateTimeParserBucket bucket, CharSequence text, int position) {
1277             if (csStartsWithIgnoreCase(text, position, iValue)) {
1278                 return position + iValue.length();
1279             }
1280             return ~position;
1281         }
1282     }
1283
1284     //-----------------------------------------------------------------------
1285     static abstract class NumberFormatter
1286             implements InternalPrinter, InternalParser {
1287         protected final DateTimeFieldType iFieldType;
1288         protected final int iMaxParsedDigits;
1289         protected final boolean iSigned;
1290
1291         NumberFormatter(DateTimeFieldType fieldType,
1292                 int maxParsedDigits, boolean signed) {
1293             super();
1294             iFieldType = fieldType;
1295             iMaxParsedDigits = maxParsedDigits;
1296             iSigned = signed;
1297         }
1298
1299         public int estimateParsedLength() {
1300             return iMaxParsedDigits;
1301         }
1302
1303         public int parseInto(DateTimeParserBucket bucket, CharSequence text, int position) {
1304             int limit = Math.min(iMaxParsedDigits, text.length() - position);
1305
1306             boolean negative = false;
1307             int length = 0;
1308             while (length < limit) {
1309                 char c = text.charAt(position + length);
1310                 if (length == 0 && (c == '-' || c == '+') && iSigned) {
1311                     negative = c == '-';
1312
1313                     // Next character must be a digit.
1314                     if (length + 1 >= limit || 
1315                         (c = text.charAt(position + length + 1)) < '0' || c > '9')
1316                     {
1317                         break;
1318                     }
1319
1320                     if (negative) {
1321                         length++;
1322                     } else {
1323                         // Skip the '+' for parseInt to succeed.
1324                         position++;
1325                     }
1326                     // Expand the limit to disregard the sign character.
1327                     limit = Math.min(limit + 1, text.length() - position);
1328                     continue;
1329                 }
1330                 if (c < '0' || c > '9') {
1331                     break;
1332                 }
1333                 length++;
1334             }
1335
1336             if (length == 0) {
1337                 return ~position;
1338             }
1339
1340             int value;
1341             if (length >= 9) {
1342                 // Since value may exceed integer limits, use stock parser
1343                 // which checks for this.
1344                 value = Integer.parseInt(text.subSequence(position, position += length).toString());
1345             } else {
1346                 int i = position;
1347                 if (negative) {
1348                     i++;
1349                 }
1350                 try {
1351                     value = text.charAt(i++) - '0';
1352                 } catch (StringIndexOutOfBoundsException e) {
1353                     return ~position;
1354                 }
1355                 position += length;
1356                 while (i < position) {
1357                     value = ((value << 3) + (value << 1)) + text.charAt(i++) - '0';
1358                 }
1359                 if (negative) {
1360                     value = -value;
1361                 }
1362             }
1363
1364             bucket.saveField(iFieldType, value);
1365             return position;
1366         }
1367     }
1368
1369     //-----------------------------------------------------------------------
1370     static class UnpaddedNumber extends NumberFormatter {
1371
1372         protected UnpaddedNumber(DateTimeFieldType fieldType,
1373                        int maxParsedDigits, boolean signed)
1374         {
1375             super(fieldType, maxParsedDigits, signed);
1376         }
1377
1378         public int estimatePrintedLength() {
1379             return iMaxParsedDigits;
1380         }
1381
1382         public void printTo(
1383                 Appendable appendable, long instant, Chronology chrono,
1384                 int displayOffset, DateTimeZone displayZone, Locale locale) throws IOException {
1385             try {
1386                 DateTimeField field = iFieldType.getField(chrono);
1387                 FormatUtils.appendUnpaddedInteger(appendable, field.get(instant));
1388             } catch (RuntimeException e) {
1389                 appendable.append('\ufffd');
1390             }
1391         }
1392
1393         public void printTo(Appendable appendable, ReadablePartial partial, Locale locale) throws IOException {
1394             if (partial.isSupported(iFieldType)) {
1395                 try {
1396                     FormatUtils.appendUnpaddedInteger(appendable, partial.get(iFieldType));
1397                 } catch (RuntimeException e) {
1398                     appendable.append('\ufffd');
1399                 }
1400             } else {
1401                 appendable.append('\ufffd');
1402             }
1403         }
1404     }
1405
1406     //-----------------------------------------------------------------------
1407     static class PaddedNumber extends NumberFormatter {
1408
1409         protected final int iMinPrintedDigits;
1410
1411         protected PaddedNumber(DateTimeFieldType fieldType, int maxParsedDigits,
1412                      boolean signed, int minPrintedDigits)
1413         {
1414             super(fieldType, maxParsedDigits, signed);
1415             iMinPrintedDigits = minPrintedDigits;
1416         }
1417
1418         public int estimatePrintedLength() {
1419             return iMaxParsedDigits;
1420         }
1421
1422         public void printTo(
1423                 Appendable appendable, long instant, Chronology chrono,
1424                 int displayOffset, DateTimeZone displayZone, Locale locale) throws IOException {
1425             try {
1426                 DateTimeField field = iFieldType.getField(chrono);
1427                 FormatUtils.appendPaddedInteger(appendable, field.get(instant), iMinPrintedDigits);
1428             } catch (RuntimeException e) {
1429                 appendUnknownString(appendable, iMinPrintedDigits);
1430             }
1431         }
1432
1433         public void printTo(Appendable appendable, ReadablePartial partial, Locale locale) throws IOException {
1434             if (partial.isSupported(iFieldType)) {
1435                 try {
1436                     FormatUtils.appendPaddedInteger(appendable, partial.get(iFieldType), iMinPrintedDigits);
1437                 } catch (RuntimeException e) {
1438                     appendUnknownString(appendable, iMinPrintedDigits);
1439                 }
1440             } else {
1441                 appendUnknownString(appendable, iMinPrintedDigits);
1442             }
1443         }
1444     }
1445
1446     //-----------------------------------------------------------------------
1447     static class FixedNumber extends PaddedNumber {
1448
1449         protected FixedNumber(DateTimeFieldType fieldType, int numDigits, boolean signed) {
1450             super(fieldType, numDigits, signed, numDigits);
1451         }
1452
1453         @Override
1454         public int parseInto(DateTimeParserBucket bucket, CharSequence text, int position) {
1455             int newPos = super.parseInto(bucket, text, position);
1456             if (newPos < 0) {
1457                 return newPos;
1458             }
1459             int expectedPos = position + iMaxParsedDigits;
1460             if (newPos != expectedPos) {
1461                 if (iSigned) {
1462                     char c = text.charAt(position);
1463                     if (c == '-' || c == '+') {
1464                         expectedPos++;
1465                     }
1466                 }
1467                 if (newPos > expectedPos) {
1468                     // The failure is at the position of the first extra digit.
1469                     return ~(expectedPos + 1);
1470                 } else if (newPos < expectedPos) {
1471                     // The failure is at the position where the next digit should be.
1472                     return ~newPos;
1473                 }
1474             }
1475             return newPos;
1476         }
1477     }
1478
1479     //-----------------------------------------------------------------------
1480     static class TwoDigitYear
1481             implements InternalPrinter, InternalParser {
1482
1483         /** The field to print/parse. */
1484         private final DateTimeFieldType iType;
1485         /** The pivot year. */
1486         private final int iPivot;
1487         private final boolean iLenientParse;
1488
1489         TwoDigitYear(DateTimeFieldType type, int pivot, boolean lenientParse) {
1490             super();
1491             iType = type;
1492             iPivot = pivot;
1493             iLenientParse = lenientParse;
1494         }
1495
1496         public int estimateParsedLength() {
1497             return iLenientParse ? 4 : 2;
1498         }
1499
1500         public int parseInto(DateTimeParserBucket bucket, CharSequence text, int position) {
1501             int limit = text.length() - position;
1502
1503             if (!iLenientParse) {
1504                 limit = Math.min(2, limit);
1505                 if (limit < 2) {
1506                     return ~position;
1507                 }
1508             } else {
1509                 boolean hasSignChar = false;
1510                 boolean negative = false;
1511                 int length = 0;
1512                 while (length < limit) {
1513                     char c = text.charAt(position + length);
1514                     if (length == 0 && (c == '-' || c == '+')) {
1515                         hasSignChar = true;
1516                         negative = c == '-';
1517                         if (negative) {
1518                             length++;
1519                         } else {
1520                             // Skip the '+' for parseInt to succeed.
1521                             position++;
1522                             limit--;
1523                         }
1524                         continue;
1525                     }
1526                     if (c < '0' || c > '9') {
1527                         break;
1528                     }
1529                     length++;
1530                 }
1531                 
1532                 if (length == 0) {
1533                     return ~position;
1534                 }
1535
1536                 if (hasSignChar || length != 2) {
1537                     int value;
1538                     if (length >= 9) {
1539                         // Since value may exceed integer limits, use stock
1540                         // parser which checks for this.
1541                         value = Integer.parseInt(text.subSequence(position, position += length).toString());
1542                     } else {
1543                         int i = position;
1544                         if (negative) {
1545                             i++;
1546                         }
1547                         try {
1548                             value = text.charAt(i++) - '0';
1549                         } catch (StringIndexOutOfBoundsException e) {
1550                             return ~position;
1551                         }
1552                         position += length;
1553                         while (i < position) {
1554                             value = ((value << 3) + (value << 1)) + text.charAt(i++) - '0';
1555                         }
1556                         if (negative) {
1557                             value = -value;
1558                         }
1559                     }
1560                     
1561                     bucket.saveField(iType, value);
1562                     return position;
1563                 }
1564             }
1565
1566             int year;
1567             char c = text.charAt(position);
1568             if (c < '0' || c > '9') {
1569                 return ~position;
1570             }
1571             year = c - '0';
1572             c = text.charAt(position + 1);
1573             if (c < '0' || c > '9') {
1574                 return ~position;
1575             }
1576             year = ((year << 3) + (year << 1)) + c - '0';
1577
1578             int pivot = iPivot;
1579             // If the bucket pivot year is non-null, use that when parsing
1580             if (bucket.getPivotYear() != null) {
1581                 pivot = bucket.getPivotYear().intValue();
1582             }
1583
1584             int low = pivot - 50;
1585
1586             int t;
1587             if (low >= 0) {
1588                 t = low % 100;
1589             } else {
1590                 t = 99 + ((low + 1) % 100);
1591             }
1592
1593             year += low + ((year < t) ? 100 : 0) - t;
1594
1595             bucket.saveField(iType, year);
1596             return position + 2;
1597         }
1598         
1599         public int estimatePrintedLength() {
1600             return 2;
1601         }
1602
1603         public void printTo(
1604                 Appendable appendable, long instant, Chronology chrono,
1605                 int displayOffset, DateTimeZone displayZone, Locale locale) throws IOException {
1606             int year = getTwoDigitYear(instant, chrono);
1607             if (year < 0) {
1608                 appendable.append('\ufffd');
1609                 appendable.append('\ufffd');
1610             } else {
1611                 FormatUtils.appendPaddedInteger(appendable, year, 2);
1612             }
1613         }
1614
1615         private int getTwoDigitYear(long instant, Chronology chrono) {
1616             try {
1617                 int year = iType.getField(chrono).get(instant);
1618                 if (year < 0) {
1619                     year = -year;
1620                 }
1621                 return year % 100;
1622             } catch (RuntimeException e) {
1623                 return -1;
1624             }
1625         }
1626
1627         public void printTo(Appendable appendable, ReadablePartial partial, Locale locale) throws IOException {
1628             int year = getTwoDigitYear(partial);
1629             if (year < 0) {
1630                 appendable.append('\ufffd');
1631                 appendable.append('\ufffd');
1632             } else {
1633                 FormatUtils.appendPaddedInteger(appendable, year, 2);
1634             }
1635         }
1636
1637         private int getTwoDigitYear(ReadablePartial partial) {
1638             if (partial.isSupported(iType)) {
1639                 try {
1640                     int year = partial.get(iType);
1641                     if (year < 0) {
1642                         year = -year;
1643                     }
1644                     return year % 100;
1645                 } catch (RuntimeException e) {}
1646             } 
1647             return -1;
1648         }
1649     }
1650
1651     //-----------------------------------------------------------------------
1652     static class TextField
1653             implements InternalPrinter, InternalParser {
1654
1655         private static Map<Locale, Map<DateTimeFieldType, Object[]>> cParseCache =
1656                     new ConcurrentHashMap<Locale, Map<DateTimeFieldType, Object[]>>();
1657         private final DateTimeFieldType iFieldType;
1658         private final boolean iShort;
1659
1660         TextField(DateTimeFieldType fieldType, boolean isShort) {
1661             super();
1662             iFieldType = fieldType;
1663             iShort = isShort;
1664         }
1665
1666         public int estimatePrintedLength() {
1667             return iShort ? 6 : 20;
1668         }
1669
1670         public void printTo(
1671                 Appendable appendable, long instant, Chronology chrono,
1672                 int displayOffset, DateTimeZone displayZone, Locale locale) throws IOException {
1673             try {
1674                 appendable.append(print(instant, chrono, locale));
1675             } catch (RuntimeException e) {
1676                 appendable.append('\ufffd');
1677             }
1678         }
1679
1680         public void printTo(Appendable appendable, ReadablePartial partial, Locale locale) throws IOException {
1681             try {
1682                 appendable.append(print(partial, locale));
1683             } catch (RuntimeException e) {
1684                 appendable.append('\ufffd');
1685             }
1686         }
1687
1688         private String print(long instant, Chronology chrono, Locale locale) {
1689             DateTimeField field = iFieldType.getField(chrono);
1690             if (iShort) {
1691                 return field.getAsShortText(instant, locale);
1692             } else {
1693                 return field.getAsText(instant, locale);
1694             }
1695         }
1696
1697         private String print(ReadablePartial partial, Locale locale) {
1698             if (partial.isSupported(iFieldType)) {
1699                 DateTimeField field = iFieldType.getField(partial.getChronology());
1700                 if (iShort) {
1701                     return field.getAsShortText(partial, locale);
1702                 } else {
1703                     return field.getAsText(partial, locale);
1704                 }
1705             } else {
1706                 return "\ufffd";
1707             }
1708         }
1709
1710         public int estimateParsedLength() {
1711             return estimatePrintedLength();
1712         }
1713
1714         @SuppressWarnings("unchecked")
1715         public int parseInto(DateTimeParserBucket bucket, CharSequence text, int position) {
1716             Locale locale = bucket.getLocale();
1717             // handle languages which might have non ASCII A-Z or punctuation
1718             // bug 1788282
1719             Map<String, Boolean> validValues = null;
1720             int maxLength = 0;
1721             Map<DateTimeFieldType, Object[]> innerMap = cParseCache.get(locale);
1722             if (innerMap == null) {
1723                 innerMap = new ConcurrentHashMap<DateTimeFieldType, Object[]>();
1724                 cParseCache.put(locale, innerMap);
1725             }
1726             Object[] array = innerMap.get(iFieldType);
1727             if (array == null) {
1728                 validValues = new ConcurrentHashMap<String, Boolean>(32);  // use map as no concurrent Set
1729                 MutableDateTime dt = new MutableDateTime(0L, DateTimeZone.UTC);
1730                 Property property = dt.property(iFieldType);
1731                 int min = property.getMinimumValueOverall();
1732                 int max = property.getMaximumValueOverall();
1733                 if (max - min > 32) {  // protect against invalid fields
1734                     return ~position;
1735                 }
1736                 maxLength = property.getMaximumTextLength(locale);
1737                 for (int i = min; i <= max; i++) {
1738                     property.set(i);
1739                     validValues.put(property.getAsShortText(locale), Boolean.TRUE);
1740                     validValues.put(property.getAsShortText(locale).toLowerCase(locale), Boolean.TRUE);
1741                     validValues.put(property.getAsShortText(locale).toUpperCase(locale), Boolean.TRUE);
1742                     validValues.put(property.getAsText(locale), Boolean.TRUE);
1743                     validValues.put(property.getAsText(locale).toLowerCase(locale), Boolean.TRUE);
1744                     validValues.put(property.getAsText(locale).toUpperCase(locale), Boolean.TRUE);
1745                 }
1746                 if ("en".equals(locale.getLanguage()) && iFieldType == DateTimeFieldType.era()) {
1747                     // hack to support for parsing "BCE" and "CE" if the language is English
1748                     validValues.put("BCE", Boolean.TRUE);
1749                     validValues.put("bce", Boolean.TRUE);
1750                     validValues.put("CE", Boolean.TRUE);
1751                     validValues.put("ce", Boolean.TRUE);
1752                     maxLength = 3;
1753                 }
1754                 array = new Object[] {validValues, Integer.valueOf(maxLength)};
1755                 innerMap.put(iFieldType, array);
1756             } else {
1757                 validValues = (Map<String, Boolean>) array[0];
1758                 maxLength = ((Integer) array[1]).intValue();
1759             }
1760             // match the longest string first using our knowledge of the max length
1761             int limit = Math.min(text.length(), position + maxLength);
1762             for (int i = limit; i > position; i--) {
1763                 String match = text.subSequence(position, i).toString();
1764                 if (validValues.containsKey(match)) {
1765                     bucket.saveField(iFieldType, match, locale);
1766                     return i;
1767                 }
1768             }
1769             return ~position;
1770         }
1771     }
1772
1773     //-----------------------------------------------------------------------
1774     static class Fraction
1775             implements InternalPrinter, InternalParser {
1776
1777         private final DateTimeFieldType iFieldType;
1778         protected int iMinDigits;
1779         protected int iMaxDigits;
1780
1781         protected Fraction(DateTimeFieldType fieldType, int minDigits, int maxDigits) {
1782             super();
1783             iFieldType = fieldType;
1784             // Limit the precision requirements.
1785             if (maxDigits > 18) {
1786                 maxDigits = 18;
1787             }
1788             iMinDigits = minDigits;
1789             iMaxDigits = maxDigits;
1790         }
1791
1792         public int estimatePrintedLength() {
1793             return iMaxDigits;
1794         }
1795
1796         public void printTo(
1797                 Appendable appendable, long instant, Chronology chrono,
1798                 int displayOffset, DateTimeZone displayZone, Locale locale) throws IOException {
1799             printTo(appendable, instant, chrono);
1800         }
1801
1802         public void printTo(Appendable appendable, ReadablePartial partial, Locale locale) throws IOException {
1803             // removed check whether field is supported, as input field is typically
1804             // secondOfDay which is unsupported by TimeOfDay
1805             long millis = partial.getChronology().set(partial, 0L);
1806             printTo(appendable, millis, partial.getChronology());
1807         }
1808
1809         protected void printTo(Appendable appendable, long instant, Chronology chrono)
1810             throws IOException
1811         {
1812             DateTimeField field = iFieldType.getField(chrono);
1813             int minDigits = iMinDigits;
1814
1815             long fraction;
1816             try {
1817                 fraction = field.remainder(instant);
1818             } catch (RuntimeException e) {
1819                 appendUnknownString(appendable, minDigits);
1820                 return;
1821             }
1822
1823             if (fraction == 0) {
1824                 while (--minDigits >= 0) {
1825                     appendable.append('0');
1826                 }
1827                 return;
1828             }
1829
1830             String str;
1831             long[] fractionData = getFractionData(fraction, field);
1832             long scaled = fractionData[0];
1833             int maxDigits = (int) fractionData[1];
1834             
1835             if ((scaled & 0x7fffffff) == scaled) {
1836                 str = Integer.toString((int) scaled);
1837             } else {
1838                 str = Long.toString(scaled);
1839             }
1840
1841             int length = str.length();
1842             int digits = maxDigits;
1843             while (length < digits) {
1844                 appendable.append('0');
1845                 minDigits--;
1846                 digits--;
1847             }
1848
1849             if (minDigits < digits) {
1850                 // Chop off as many trailing zero digits as necessary.
1851                 while (minDigits < digits) {
1852                     if (length <= 1 || str.charAt(length - 1) != '0') {
1853                         break;
1854                     }
1855                     digits--;
1856                     length--;
1857                 }
1858                 if (length < str.length()) {
1859                     for (int i=0; i<length; i++) {
1860                         appendable.append(str.charAt(i));
1861                     }
1862                     return;
1863                 }
1864             }
1865
1866             appendable.append(str);
1867         }
1868         
1869         private long[] getFractionData(long fraction, DateTimeField field) {
1870             long rangeMillis = field.getDurationField().getUnitMillis();
1871             long scalar;
1872             int maxDigits = iMaxDigits;
1873             while (true) {
1874                 switch (maxDigits) {
1875                 default: scalar = 1L; break;
1876                 case 1:  scalar = 10L; break;
1877                 case 2:  scalar = 100L; break;
1878                 case 3:  scalar = 1000L; break;
1879                 case 4:  scalar = 10000L; break;
1880                 case 5:  scalar = 100000L; break;
1881                 case 6:  scalar = 1000000L; break;
1882                 case 7:  scalar = 10000000L; break;
1883                 case 8:  scalar = 100000000L; break;
1884                 case 9:  scalar = 1000000000L; break;
1885                 case 10: scalar = 10000000000L; break;
1886                 case 11: scalar = 100000000000L; break;
1887                 case 12: scalar = 1000000000000L; break;
1888                 case 13: scalar = 10000000000000L; break;
1889                 case 14: scalar = 100000000000000L; break;
1890                 case 15: scalar = 1000000000000000L; break;
1891                 case 16: scalar = 10000000000000000L; break;
1892                 case 17: scalar = 100000000000000000L; break;
1893                 case 18: scalar = 1000000000000000000L; break;
1894                 }
1895                 if (((rangeMillis * scalar) / scalar) == rangeMillis) {
1896                     break;
1897                 }
1898                 // Overflowed: scale down.
1899                 maxDigits--;
1900             }
1901             
1902             return new long[] {fraction * scalar / rangeMillis, maxDigits};
1903         }
1904
1905         public int estimateParsedLength() {
1906             return iMaxDigits;
1907         }
1908
1909         public int parseInto(DateTimeParserBucket bucket, CharSequence text, int position) {
1910             DateTimeField field = iFieldType.getField(bucket.getChronology());
1911             
1912             int limit = Math.min(iMaxDigits, text.length() - position);
1913
1914             long value = 0;
1915             long n = field.getDurationField().getUnitMillis() * 10;
1916             int length = 0;
1917             while (length < limit) {
1918                 char c = text.charAt(position + length);
1919                 if (c < '0' || c > '9') {
1920                     break;
1921                 }
1922                 length++;
1923                 long nn = n / 10;
1924                 value += (c - '0') * nn;
1925                 n = nn;
1926             }
1927
1928             value /= 10;
1929
1930             if (length == 0) {
1931                 return ~position;
1932             }
1933
1934             if (value > Integer.MAX_VALUE) {
1935                 return ~position;
1936             }
1937
1938             DateTimeField parseField = new PreciseDateTimeField(
1939                 DateTimeFieldType.millisOfSecond(),
1940                 MillisDurationField.INSTANCE,
1941                 field.getDurationField());
1942
1943             bucket.saveField(parseField, (int) value);
1944
1945             return position + length;
1946         }
1947     }
1948
1949     //-----------------------------------------------------------------------
1950     static class TimeZoneOffset
1951             implements InternalPrinter, InternalParser {
1952
1953         private final String iZeroOffsetPrintText;
1954         private final String iZeroOffsetParseText;
1955         private final boolean iShowSeparators;
1956         private final int iMinFields;
1957         private final int iMaxFields;
1958
1959         TimeZoneOffset(String zeroOffsetPrintText, String zeroOffsetParseText,
1960                                 boolean showSeparators,
1961                                 int minFields, int maxFields)
1962         {
1963             super();
1964             iZeroOffsetPrintText = zeroOffsetPrintText;
1965             iZeroOffsetParseText = zeroOffsetParseText;
1966             iShowSeparators = showSeparators;
1967             if (minFields <= 0 || maxFields < minFields) {
1968                 throw new IllegalArgumentException();
1969             }
1970             if (minFields > 4) {
1971                 minFields = 4;
1972                 maxFields = 4;
1973             }
1974             iMinFields = minFields;
1975             iMaxFields = maxFields;
1976         }
1977             
1978         public int estimatePrintedLength() {
1979             int est = 1 + iMinFields << 1;
1980             if (iShowSeparators) {
1981                 est += iMinFields - 1;
1982             }
1983             if (iZeroOffsetPrintText != null && iZeroOffsetPrintText.length() > est) {
1984                 est = iZeroOffsetPrintText.length();
1985             }
1986             return est;
1987         }
1988
1989         public void printTo(
1990                 Appendable buf, long instant, Chronology chrono,
1991                 int displayOffset, DateTimeZone displayZone, Locale locale) throws IOException {
1992             if (displayZone == null) {
1993                 return;  // no zone
1994             }
1995             if (displayOffset == 0 && iZeroOffsetPrintText != null) {
1996                 buf.append(iZeroOffsetPrintText);
1997                 return;
1998             }
1999             if (displayOffset >= 0) {
2000                 buf.append('+');
2001             } else {
2002                 buf.append('-');
2003                 displayOffset = -displayOffset;
2004             }
2005
2006             int hours = displayOffset / DateTimeConstants.MILLIS_PER_HOUR;
2007             FormatUtils.appendPaddedInteger(buf, hours, 2);
2008             if (iMaxFields == 1) {
2009                 return;
2010             }
2011             displayOffset -= hours * (int)DateTimeConstants.MILLIS_PER_HOUR;
2012             if (displayOffset == 0 && iMinFields <= 1) {
2013                 return;
2014             }
2015
2016             int minutes = displayOffset / DateTimeConstants.MILLIS_PER_MINUTE;
2017             if (iShowSeparators) {
2018                 buf.append(':');
2019             }
2020             FormatUtils.appendPaddedInteger(buf, minutes, 2);
2021             if (iMaxFields == 2) {
2022                 return;
2023             }
2024             displayOffset -= minutes * DateTimeConstants.MILLIS_PER_MINUTE;
2025             if (displayOffset == 0 && iMinFields <= 2) {
2026                 return;
2027             }
2028
2029             int seconds = displayOffset / DateTimeConstants.MILLIS_PER_SECOND;
2030             if (iShowSeparators) {
2031                 buf.append(':');
2032             }
2033             FormatUtils.appendPaddedInteger(buf, seconds, 2);
2034             if (iMaxFields == 3) {
2035                 return;
2036             }
2037             displayOffset -= seconds * DateTimeConstants.MILLIS_PER_SECOND;
2038             if (displayOffset == 0 && iMinFields <= 3) {
2039                 return;
2040             }
2041
2042             if (iShowSeparators) {
2043                 buf.append('.');
2044             }
2045             FormatUtils.appendPaddedInteger(buf, displayOffset, 3);
2046         }
2047
2048         public void printTo(Appendable appendable, ReadablePartial partial, Locale locale) throws IOException {
2049             // no zone info
2050         }
2051
2052         public int estimateParsedLength() {
2053             return estimatePrintedLength();
2054         }
2055
2056         public int parseInto(DateTimeParserBucket bucket, CharSequence text, int position) {
2057             int limit = text.length() - position;
2058
2059             zeroOffset:
2060             if (iZeroOffsetParseText != null) {
2061                 if (iZeroOffsetParseText.length() == 0) {
2062                     // Peek ahead, looking for sign character.
2063                     if (limit > 0) {
2064                         char c = text.charAt(position);
2065                         if (c == '-' || c == '+') {
2066                             break zeroOffset;
2067                         }
2068                     }
2069                     bucket.setOffset(Integer.valueOf(0));
2070                     return position;
2071                 }
2072                 if (csStartsWithIgnoreCase(text, position, iZeroOffsetParseText)) {
2073                     bucket.setOffset(Integer.valueOf(0));
2074                     return position + iZeroOffsetParseText.length();
2075                 }
2076             }
2077
2078             // Format to expect is sign character followed by at least one digit.
2079
2080             if (limit <= 1) {
2081                 return ~position;
2082             }
2083
2084             boolean negative;
2085             char c = text.charAt(position);
2086             if (c == '-') {
2087                 negative = true;
2088             } else if (c == '+') {
2089                 negative = false;
2090             } else {
2091                 return ~position;
2092             }
2093
2094             limit--;
2095             position++;
2096
2097             // Format following sign is one of:
2098             //
2099             // hh
2100             // hhmm
2101             // hhmmss
2102             // hhmmssSSS
2103             // hh:mm
2104             // hh:mm:ss
2105             // hh:mm:ss.SSS
2106
2107             // First parse hours.
2108
2109             if (digitCount(text, position, 2) < 2) {
2110                 // Need two digits for hour.
2111                 return ~position;
2112             }
2113
2114             int offset;
2115
2116             int hours = FormatUtils.parseTwoDigits(text, position);
2117             if (hours > 23) {
2118                 return ~position;
2119             }
2120             offset = hours * DateTimeConstants.MILLIS_PER_HOUR;
2121             limit -= 2;
2122             position += 2;
2123
2124             parse: {
2125                 // Need to decide now if separators are expected or parsing
2126                 // stops at hour field.
2127
2128                 if (limit <= 0) {
2129                     break parse;
2130                 }
2131
2132                 boolean expectSeparators;
2133                 c = text.charAt(position);
2134                 if (c == ':') {
2135                     expectSeparators = true;
2136                     limit--;
2137                     position++;
2138                 } else if (c >= '0' && c <= '9') {
2139                     expectSeparators = false;
2140                 } else {
2141                     break parse;
2142                 }
2143
2144                 // Proceed to parse minutes.
2145
2146                 int count = digitCount(text, position, 2);
2147                 if (count == 0 && !expectSeparators) {
2148                     break parse;
2149                 } else if (count < 2) {
2150                     // Need two digits for minute.
2151                     return ~position;
2152                 }
2153
2154                 int minutes = FormatUtils.parseTwoDigits(text, position);
2155                 if (minutes > 59) {
2156                     return ~position;
2157                 }
2158                 offset += minutes * DateTimeConstants.MILLIS_PER_MINUTE;
2159                 limit -= 2;
2160                 position += 2;
2161
2162                 // Proceed to parse seconds.
2163
2164                 if (limit <= 0) {
2165                     break parse;
2166                 }
2167
2168                 if (expectSeparators) {
2169                     if (text.charAt(position) != ':') {
2170                         break parse;
2171                     }
2172                     limit--;
2173                     position++;
2174                 }
2175
2176                 count = digitCount(text, position, 2);
2177                 if (count == 0 && !expectSeparators) {
2178                     break parse;
2179                 } else if (count < 2) {
2180                     // Need two digits for second.
2181                     return ~position;
2182                 }
2183
2184                 int seconds = FormatUtils.parseTwoDigits(text, position);
2185                 if (seconds > 59) {
2186                     return ~position;
2187                 }
2188                 offset += seconds * DateTimeConstants.MILLIS_PER_SECOND;
2189                 limit -= 2;
2190                 position += 2;
2191
2192                 // Proceed to parse fraction of second.
2193
2194                 if (limit <= 0) {
2195                     break parse;
2196                 }
2197
2198                 if (expectSeparators) {
2199                     if (text.charAt(position) != '.' && text.charAt(position) != ',') {
2200                         break parse;
2201                     }
2202                     limit--;
2203                     position++;
2204                 }
2205                 
2206                 count = digitCount(text, position, 3);
2207                 if (count == 0 && !expectSeparators) {
2208                     break parse;
2209                 } else if (count < 1) {
2210                     // Need at least one digit for fraction of second.
2211                     return ~position;
2212                 }
2213
2214                 offset += (text.charAt(position++) - '0') * 100;
2215                 if (count > 1) {
2216                     offset += (text.charAt(position++) - '0') * 10;
2217                     if (count > 2) {
2218                         offset += text.charAt(position++) - '0';
2219                     }
2220                 }
2221             }
2222
2223             bucket.setOffset(Integer.valueOf(negative ? -offset : offset));
2224             return position;
2225         }
2226
2227         /**
2228          * Returns actual amount of digits to parse, but no more than original
2229          * 'amount' parameter.
2230          */

2231         private int digitCount(CharSequence text, int position, int amount) {
2232             int limit = Math.min(text.length() - position, amount);
2233             amount = 0;
2234             for (; limit > 0; limit--) {
2235                 char c = text.charAt(position + amount);
2236                 if (c < '0' || c > '9') {
2237                     break;
2238                 }
2239                 amount++;
2240             }
2241             return amount;
2242         }
2243     }
2244
2245     //-----------------------------------------------------------------------
2246     static class TimeZoneName
2247             implements InternalPrinter, InternalParser {
2248
2249         static final int LONG_NAME = 0;
2250         static final int SHORT_NAME = 1;
2251
2252         private final Map<String, DateTimeZone> iParseLookup;
2253         private final int iType;
2254
2255         TimeZoneName(int type, Map<String, DateTimeZone> parseLookup) {
2256             super();
2257             iType = type;
2258             iParseLookup = parseLookup;
2259         }
2260
2261         public int estimatePrintedLength() {
2262             return (iType == SHORT_NAME ? 4 : 20);
2263         }
2264
2265         public void printTo(
2266                 Appendable appendable, long instant, Chronology chrono,
2267                 int displayOffset, DateTimeZone displayZone, Locale locale) throws IOException {
2268             appendable.append(print(instant - displayOffset, displayZone, locale));
2269         }
2270
2271         private String print(long instant, DateTimeZone displayZone, Locale locale) {
2272             if (displayZone == null) {
2273                 return "";  // no zone
2274             }
2275             switch (iType) {
2276                 case LONG_NAME:
2277                     return displayZone.getName(instant, locale);
2278                 case SHORT_NAME:
2279                     return displayZone.getShortName(instant, locale);
2280             }
2281             return "";
2282         }
2283
2284         public void printTo(Appendable appendable, ReadablePartial partial, Locale locale) throws IOException {
2285             // no zone info
2286         }
2287
2288         public int estimateParsedLength() {
2289             return (iType == SHORT_NAME ? 4 : 20);
2290         }
2291
2292         public int parseInto(DateTimeParserBucket bucket, CharSequence text, int position) {
2293             Map<String, DateTimeZone> parseLookup = iParseLookup;
2294             parseLookup = (parseLookup != null ? parseLookup : DateTimeUtils.getDefaultTimeZoneNames());
2295             String matched = null;
2296             for (String name : parseLookup.keySet()) {
2297                 if (csStartsWith(text, position, name)) {
2298                     if (matched == null || name.length() > matched.length()) {
2299                         matched = name;
2300                     }
2301                 }
2302             }
2303             if (matched != null) {
2304                 bucket.setZone(parseLookup.get(matched));
2305                 return position + matched.length();
2306             }
2307             return ~position;
2308         }
2309     }
2310
2311     //-----------------------------------------------------------------------
2312     static enum TimeZoneId
2313             implements InternalPrinter, InternalParser {
2314
2315         INSTANCE;
2316         static final Set<String> ALL_IDS = DateTimeZone.getAvailableIDs();
2317         static final int MAX_LENGTH;
2318         static {
2319             int max = 0;
2320             for (String id : ALL_IDS) {
2321                 max = Math.max(max, id.length());
2322             }
2323             MAX_LENGTH = max;
2324         }
2325
2326         public int estimatePrintedLength() {
2327             return MAX_LENGTH;
2328         }
2329
2330         public void printTo(
2331                 Appendable appendable, long instant, Chronology chrono,
2332                 int displayOffset, DateTimeZone displayZone, Locale locale) throws IOException {
2333             appendable.append(displayZone != null ? displayZone.getID() : "");
2334         }
2335
2336         public void printTo(Appendable appendable, ReadablePartial partial, Locale locale) throws IOException {
2337             // no zone info
2338         }
2339
2340         public int estimateParsedLength() {
2341             return MAX_LENGTH;
2342         }
2343
2344         public int parseInto(DateTimeParserBucket bucket, CharSequence text, int position) {
2345             String best = null;
2346             for (String id : ALL_IDS) {
2347                 if (csStartsWith(text, position, id)) {
2348                     if (best == null || id.length() > best.length()) {
2349                         best = id;
2350                     }
2351                 }
2352             }
2353             if (best != null) {
2354                 bucket.setZone(DateTimeZone.forID(best));
2355                 return position + best.length();
2356             }
2357             return ~position;
2358         }
2359     }
2360
2361     //-----------------------------------------------------------------------
2362     static class Composite
2363             implements InternalPrinter, InternalParser {
2364
2365         private final InternalPrinter[] iPrinters;
2366         private final InternalParser[] iParsers;
2367
2368         private final int iPrintedLengthEstimate;
2369         private final int iParsedLengthEstimate;
2370
2371         Composite(List<Object> elementPairs) {
2372             super();
2373
2374             List<Object> printerList = new ArrayList<Object>();
2375             List<Object> parserList = new ArrayList<Object>();
2376
2377             decompose(elementPairs, printerList, parserList);
2378
2379             if (printerList.contains(null) || printerList.isEmpty()) {
2380                 iPrinters = null;
2381                 iPrintedLengthEstimate = 0;
2382             } else {
2383                 int size = printerList.size();
2384                 iPrinters = new InternalPrinter[size];
2385                 int printEst = 0;
2386                 for (int i=0; i<size; i++) {
2387                     InternalPrinter printer = (InternalPrinter) printerList.get(i);
2388                     printEst += printer.estimatePrintedLength();
2389                     iPrinters[i] = printer;
2390                 }
2391                 iPrintedLengthEstimate = printEst;
2392             }
2393
2394             if (parserList.contains(null) || parserList.isEmpty()) {
2395                 iParsers = null;
2396                 iParsedLengthEstimate = 0;
2397             } else {
2398                 int size = parserList.size();
2399                 iParsers = new InternalParser[size];
2400                 int parseEst = 0;
2401                 for (int i=0; i<size; i++) {
2402                     InternalParser parser = (InternalParser) parserList.get(i);
2403                     parseEst += parser.estimateParsedLength();
2404                     iParsers[i] = parser;
2405                 }
2406                 iParsedLengthEstimate = parseEst;
2407             }
2408         }
2409
2410         public int estimatePrintedLength() {
2411             return iPrintedLengthEstimate;
2412         }
2413
2414         public void printTo(
2415                 Appendable appendable, long instant, Chronology chrono,
2416                 int displayOffset, DateTimeZone displayZone, Locale locale) throws IOException {
2417             InternalPrinter[] elements = iPrinters;
2418             if (elements == null) {
2419                 throw new UnsupportedOperationException();
2420             }
2421
2422             if (locale == null) {
2423                 // Guard against default locale changing concurrently.
2424                 locale = Locale.getDefault();
2425             }
2426
2427             int len = elements.length;
2428             for (int i = 0; i < len; i++) {
2429                 elements[i].printTo(appendable, instant, chrono, displayOffset, displayZone, locale);
2430             }
2431         }
2432
2433         public void printTo(Appendable appendable, ReadablePartial partial, Locale locale) throws IOException {
2434             InternalPrinter[] elements = iPrinters;
2435             if (elements == null) {
2436                 throw new UnsupportedOperationException();
2437             }
2438
2439             if (locale == null) {
2440                 // Guard against default locale changing concurrently.
2441                 locale = Locale.getDefault();
2442             }
2443
2444             int len = elements.length;
2445             for (int i=0; i<len; i++) {
2446                 elements[i].printTo(appendable, partial, locale);
2447             }
2448         }
2449
2450         public int estimateParsedLength() {
2451             return iParsedLengthEstimate;
2452         }
2453
2454         public int parseInto(DateTimeParserBucket bucket, CharSequence text, int position) {
2455             InternalParser[] elements = iParsers;
2456             if (elements == null) {
2457                 throw new UnsupportedOperationException();
2458             }
2459
2460             int len = elements.length;
2461             for (int i=0; i<len && position >= 0; i++) {
2462                 position = elements[i].parseInto(bucket, text, position);
2463             }
2464             return position;
2465         }
2466
2467         boolean isPrinter() {
2468             return iPrinters != null;
2469         }
2470
2471         boolean isParser() {
2472             return iParsers != null;
2473         }
2474
2475         /**
2476          * Processes the element pairs, putting results into the given printer
2477          * and parser lists.
2478          */

2479         private void decompose(List<Object> elementPairs, List<Object> printerList, List<Object> parserList) {
2480             int size = elementPairs.size();
2481             for (int i=0; i<size; i+=2) {
2482                 Object element = elementPairs.get(i);
2483                 if (element instanceof Composite) {
2484                     addArrayToList(printerList, ((Composite)element).iPrinters);
2485                 } else {
2486                     printerList.add(element);
2487                 }
2488
2489                 element = elementPairs.get(i + 1);
2490                 if (element instanceof Composite) {
2491                     addArrayToList(parserList, ((Composite)element).iParsers);
2492                 } else {
2493                     parserList.add(element);
2494                 }
2495             }
2496         }
2497
2498         private void addArrayToList(List<Object> list, Object[] array) {
2499             if (array != null) {
2500                 for (int i=0; i<array.length; i++) {
2501                     list.add(array[i]);
2502                 }
2503             }
2504         }
2505     }
2506
2507     //-----------------------------------------------------------------------
2508     static class MatchingParser
2509             implements InternalParser {
2510
2511         private final InternalParser[] iParsers;
2512         private final int iParsedLengthEstimate;
2513
2514         MatchingParser(InternalParser[] parsers) {
2515             super();
2516             iParsers = parsers;
2517             int est = 0;
2518             for (int i=parsers.length; --i>=0 ;) {
2519                 InternalParser parser = parsers[i];
2520                 if (parser != null) {
2521                     int len = parser.estimateParsedLength();
2522                     if (len > est) {
2523                         est = len;
2524                     }
2525                 }
2526             }
2527             iParsedLengthEstimate = est;
2528         }
2529
2530         public int estimateParsedLength() {
2531             return iParsedLengthEstimate;
2532         }
2533
2534         public int parseInto(DateTimeParserBucket bucket, CharSequence text, int position) {
2535             InternalParser[] parsers = iParsers;
2536             int length = parsers.length;
2537
2538             final Object originalState = bucket.saveState();
2539             boolean isOptional = false;
2540
2541             int bestValidPos = position;
2542             Object bestValidState = null;
2543
2544             int bestInvalidPos = position;
2545
2546             for (int i=0; i<length; i++) {
2547                 InternalParser parser = parsers[i];
2548                 if (parser == null) {
2549                     // The empty parser wins only if nothing is better.
2550                     if (bestValidPos <= position) {
2551                         return position;
2552                     }
2553                     isOptional = true;
2554                     break;
2555                 }
2556                 int parsePos = parser.parseInto(bucket, text, position);
2557                 if (parsePos >= position) {
2558                     if (parsePos > bestValidPos) {
2559                         if (parsePos >= text.length() ||
2560                             (i + 1) >= length || parsers[i + 1] == null) {
2561
2562                             // Completely parsed text or no more parsers to
2563                             // check. Skip the rest.
2564                             return parsePos;
2565                         }
2566                         bestValidPos = parsePos;
2567                         bestValidState = bucket.saveState();
2568                     }
2569                 } else {
2570                     if (parsePos < 0) {
2571                         parsePos = ~parsePos;
2572                         if (parsePos > bestInvalidPos) {
2573                             bestInvalidPos = parsePos;
2574                         }
2575                     }
2576                 }
2577                 bucket.restoreState(originalState);
2578             }
2579
2580             if (bestValidPos > position || (bestValidPos == position && isOptional)) {
2581                 // Restore the state to the best valid parse.
2582                 if (bestValidState != null) {
2583                     bucket.restoreState(bestValidState);
2584                 }
2585                 return bestValidPos;
2586             }
2587
2588             return ~bestInvalidPos;
2589         }
2590     }
2591
2592     static boolean csStartsWith(CharSequence text, int position, String search) {
2593         int searchLen = search.length();
2594         if ((text.length() - position) < searchLen) {
2595             return false;
2596         }
2597         for (int i = 0; i < searchLen; i++) {
2598             if (text.charAt(position + i) != search.charAt(i)) {
2599                 return false;
2600             }
2601         }
2602         return true;
2603     }
2604
2605     static boolean csStartsWithIgnoreCase(CharSequence text, int position, String search) {
2606         int searchLen = search.length();
2607         if ((text.length() - position) < searchLen) {
2608             return false;
2609         }
2610         for (int i = 0; i < searchLen; i++) {
2611             char ch1 = text.charAt(position + i);
2612             char ch2 = search.charAt(i);
2613             if (ch1 != ch2) {
2614                 char u1 = Character.toUpperCase(ch1);
2615                 char u2 = Character.toUpperCase(ch2);
2616                 if (u1 != u2 && Character.toLowerCase(u1) != Character.toLowerCase(u2)) {
2617                     return false;
2618                 }
2619             }
2620         }
2621         return true;
2622     }
2623
2624 }
2625