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