EMMA Coverage Report (generated Tue Oct 28 00:01:11 GMT 2008)
[all classes][org.joda.time.format]

COVERAGE SUMMARY FOR SOURCE FILE [DateTimeFormatterBuilder.java]

nameclass, %method, %block, %line, %
DateTimeFormatterBuilder.java100% (14/14)86%  (143/167)79%  (2972/3764)76%  (749/987)

COVERAGE BREAKDOWN BY CLASS AND METHOD

nameclass, %method, %block, %line, %
     
class DateTimeFormatterBuilder$UnpaddedNumber100% (1/1)67%  (4/6)40%  (34/86)36%  (10.5/29)
printTo (Writer, ReadablePartial, Locale): void 0%   (0/1)0%   (0/21)0%   (0/7)
printTo (Writer, long, Chronology, int, DateTimeZone, Locale): void 0%   (0/1)0%   (0/16)0%   (0/6)
printTo (StringBuffer, ReadablePartial, Locale): void 100% (1/1)57%  (13/23)50%  (3.5/7)
printTo (StringBuffer, long, Chronology, int, DateTimeZone, Locale): void 100% (1/1)71%  (12/17)67%  (4/6)
DateTimeFormatterBuilder$UnpaddedNumber (DateTimeFieldType, int, boolean): void 100% (1/1)100% (6/6)100% (2/2)
estimatePrintedLength (): int 100% (1/1)100% (3/3)100% (1/1)
     
class DateTimeFormatterBuilder$TwoDigitYear100% (1/1)60%  (6/10)70%  (267/382)68%  (69/102)
getTwoDigitYear (ReadablePartial): int 0%   (0/1)0%   (0/22)0%   (0/7)
printTo (StringBuffer, ReadablePartial, Locale): void 0%   (0/1)0%   (0/20)0%   (0/6)
printTo (Writer, ReadablePartial, Locale): void 0%   (0/1)0%   (0/18)0%   (0/6)
printTo (Writer, long, Chronology, int, DateTimeZone, Locale): void 0%   (0/1)0%   (0/19)0%   (0/6)
printTo (StringBuffer, long, Chronology, int, DateTimeZone, Locale): void 100% (1/1)57%  (12/21)67%  (4/6)
getTwoDigitYear (long, Chronology): int 100% (1/1)84%  (16/19)67%  (4/6)
parseInto (DateTimeParserBucket, String, int): int 100% (1/1)90%  (218/242)93%  (54/58)
DateTimeFormatterBuilder$TwoDigitYear (DateTimeFieldType, int, boolean): void 100% (1/1)100% (12/12)100% (5/5)
estimateParsedLength (): int 100% (1/1)100% (7/7)100% (1/1)
estimatePrintedLength (): int 100% (1/1)100% (2/2)100% (1/1)
     
class DateTimeFormatterBuilder$Fraction100% (1/1)80%  (8/10)71%  (293/412)67%  (78/117)
printTo (Writer, ReadablePartial, Locale): void 0%   (0/1)0%   (0/14)0%   (0/3)
printTo (Writer, long, Chronology, int, DateTimeZone, Locale): void 0%   (0/1)0%   (0/7)0%   (0/2)
getFractionData (long, DateTimeField): long [] 100% (1/1)50%  (46/92)38%  (10/26)
printTo (StringBuffer, Writer, long, Chronology): void 100% (1/1)75%  (119/159)72%  (34/47)
DateTimeFormatterBuilder$Fraction (DateTimeFieldType, int, int): void 100% (1/1)88%  (15/17)86%  (6/7)
printTo (StringBuffer, long, Chronology, int, DateTimeZone, Locale): void 100% (1/1)89%  (8/9)75%  (3/4)
parseInto (DateTimeParserBucket, String, int): int 100% (1/1)91%  (84/92)90%  (19/21)
printTo (StringBuffer, ReadablePartial, Locale): void 100% (1/1)94%  (15/16)80%  (4/5)
estimateParsedLength (): int 100% (1/1)100% (3/3)100% (1/1)
estimatePrintedLength (): int 100% (1/1)100% (3/3)100% (1/1)
     
class DateTimeFormatterBuilder$StringLiteral100% (1/1)62%  (5/8)72%  (41/57)62%  (10/16)
printTo (StringBuffer, ReadablePartial, Locale): void 0%   (0/1)0%   (0/6)0%   (0/2)
printTo (Writer, ReadablePartial, Locale): void 0%   (0/1)0%   (0/5)0%   (0/2)
printTo (Writer, long, Chronology, int, DateTimeZone, Locale): void 0%   (0/1)0%   (0/5)0%   (0/2)
DateTimeFormatterBuilder$StringLiteral (String): void 100% (1/1)100% (6/6)100% (3/3)
estimateParsedLength (): int 100% (1/1)100% (4/4)100% (1/1)
estimatePrintedLength (): int 100% (1/1)100% (4/4)100% (1/1)
parseInto (DateTimeParserBucket, String, int): int 100% (1/1)100% (21/21)100% (3/3)
printTo (StringBuffer, long, Chronology, int, DateTimeZone, Locale): void 100% (1/1)100% (6/6)100% (2/2)
     
class DateTimeFormatterBuilder$TimeZoneName100% (1/1)57%  (4/7)72%  (44/61)65%  (11/17)
printTo (StringBuffer, ReadablePartial, Locale): void 0%   (0/1)0%   (0/1)0%   (0/1)
printTo (Writer, ReadablePartial, Locale): void 0%   (0/1)0%   (0/1)0%   (0/1)
printTo (Writer, long, Chronology, int, DateTimeZone, Locale): void 0%   (0/1)0%   (0/11)0%   (0/2)
print (long, DateTimeZone, Locale): String 100% (1/1)82%  (18/22)71%  (5/7)
DateTimeFormatterBuilder$TimeZoneName (int): void 100% (1/1)100% (6/6)100% (3/3)
estimatePrintedLength (): int 100% (1/1)100% (8/8)100% (1/1)
printTo (StringBuffer, long, Chronology, int, DateTimeZone, Locale): void 100% (1/1)100% (12/12)100% (2/2)
     
class DateTimeFormatterBuilder$TimeZoneOffset100% (1/1)89%  (8/9)72%  (473/654)68%  (129.9/191)
printTo (Writer, ReadablePartial, Locale): void 0%   (0/1)0%   (0/1)0%   (0/1)
printTo (Writer, long, Chronology, int, DateTimeZone, Locale): void 100% (1/1)58%  (73/126)50%  (19/38)
printTo (StringBuffer, long, Chronology, int, DateTimeZone, Locale): void 100% (1/1)64%  (84/132)58%  (22/38)
DateTimeFormatterBuilder$TimeZoneOffset (String, boolean, int, int): void 100% (1/1)74%  (23/31)73%  (8/11)
parseInto (DateTimeParserBucket, String, int): int 100% (1/1)78%  (234/300)77%  (66.9/87)
estimatePrintedLength (): int 100% (1/1)87%  (27/31)83%  (5/6)
digitCount (String, int, int): int 100% (1/1)97%  (28/29)88%  (7/8)
estimateParsedLength (): int 100% (1/1)100% (3/3)100% (1/1)
printTo (StringBuffer, ReadablePartial, Locale): void 100% (1/1)100% (1/1)100% (1/1)
     
class DateTimeFormatterBuilder$PaddedNumber100% (1/1)100% (6/6)74%  (74/100)67%  (20.2/30)
printTo (Writer, ReadablePartial, Locale): void 100% (1/1)60%  (15/25)51%  (3.6/7)
printTo (StringBuffer, long, Chronology, int, DateTimeZone, Locale): void 100% (1/1)74%  (14/19)67%  (4/6)
printTo (Writer, long, Chronology, int, DateTimeZone, Locale): void 100% (1/1)74%  (14/19)67%  (4/6)
printTo (StringBuffer, ReadablePartial, Locale): void 100% (1/1)76%  (19/25)66%  (4.6/7)
DateTimeFormatterBuilder$PaddedNumber (DateTimeFieldType, int, boolean, int):... 100% (1/1)100% (9/9)100% (3/3)
estimatePrintedLength (): int 100% (1/1)100% (3/3)100% (1/1)
     
class DateTimeFormatterBuilder100% (1/1)89%  (64/72)77%  (663/857)78%  (145/187)
append (DateTimePrinter, DateTimeParser): DateTimeFormatterBuilder 0%   (0/1)0%   (0/11)0%   (0/3)
appendFractionOfDay (int, int): DateTimeFormatterBuilder 0%   (0/1)0%   (0/6)0%   (0/1)
appendMillisOfDay (int): DateTimeFormatterBuilder 0%   (0/1)0%   (0/6)0%   (0/1)
appendMinuteOfDay (int): DateTimeFormatterBuilder 0%   (0/1)0%   (0/6)0%   (0/1)
appendPattern (String): DateTimeFormatterBuilder 0%   (0/1)0%   (0/5)0%   (0/2)
appendYearOfCentury (int, int): DateTimeFormatterBuilder 0%   (0/1)0%   (0/6)0%   (0/1)
clear (): void 0%   (0/1)0%   (0/7)0%   (0/3)
printUnknownString (Writer, int): void 0%   (0/1)0%   (0/10)0%   (0/3)
checkParser (DateTimeParser): void 100% (1/1)38%  (3/8)67%  (2/3)
checkPrinter (DateTimePrinter): void 100% (1/1)38%  (3/8)67%  (2/3)
appendLiteral (String): DateTimeFormatterBuilder 100% (1/1)43%  (12/28)50%  (3/6)
appendFixedDecimal (DateTimeFieldType, int): DateTimeFormatterBuilder 100% (1/1)43%  (13/30)60%  (3/5)
appendFixedSignedDecimal (DateTimeFieldType, int): DateTimeFormatterBuilder 100% (1/1)43%  (13/30)60%  (3/5)
append (DateTimePrinter, DateTimeParser []): DateTimeFormatterBuilder 100% (1/1)60%  (44/73)60%  (9/15)
appendFraction (DateTimeFieldType, int, int): DateTimeFormatterBuilder 100% (1/1)62%  (18/29)57%  (4/7)
append (DateTimeFormatter): DateTimeFormatterBuilder 100% (1/1)64%  (9/14)67%  (2/3)
appendShortText (DateTimeFieldType): DateTimeFormatterBuilder 100% (1/1)67%  (10/15)67%  (2/3)
appendText (DateTimeFieldType): DateTimeFormatterBuilder 100% (1/1)67%  (10/15)67%  (2/3)
appendDecimal (DateTimeFieldType, int, int): DateTimeFormatterBuilder 100% (1/1)74%  (31/42)67%  (6/9)
appendSignedDecimal (DateTimeFieldType, int, int): DateTimeFormatterBuilder 100% (1/1)74%  (31/42)67%  (6/9)
DateTimeFormatterBuilder (): void 100% (1/1)100% (8/8)100% (3/3)
append (DateTimeParser): DateTimeFormatterBuilder 100% (1/1)100% (8/8)100% (2/2)
append (DateTimePrinter): DateTimeFormatterBuilder 100% (1/1)100% (8/8)100% (2/2)
append0 (DateTimePrinter, DateTimeParser): DateTimeFormatterBuilder 100% (1/1)100% (15/15)100% (4/4)
append0 (Object): DateTimeFormatterBuilder 100% (1/1)100% (15/15)100% (4/4)
appendCenturyOfEra (int, int): DateTimeFormatterBuilder 100% (1/1)100% (6/6)100% (1/1)
appendClockhourOfDay (int): DateTimeFormatterBuilder 100% (1/1)100% (6/6)100% (1/1)
appendClockhourOfHalfday (int): DateTimeFormatterBuilder 100% (1/1)100% (6/6)100% (1/1)
appendDayOfMonth (int): DateTimeFormatterBuilder 100% (1/1)100% (6/6)100% (1/1)
appendDayOfWeek (int): DateTimeFormatterBuilder 100% (1/1)100% (6/6)100% (1/1)
appendDayOfWeekShortText (): DateTimeFormatterBuilder 100% (1/1)100% (4/4)100% (1/1)
appendDayOfWeekText (): DateTimeFormatterBuilder 100% (1/1)100% (4/4)100% (1/1)
appendDayOfYear (int): DateTimeFormatterBuilder 100% (1/1)100% (6/6)100% (1/1)
appendEraText (): DateTimeFormatterBuilder 100% (1/1)100% (4/4)100% (1/1)
appendFractionOfHour (int, int): DateTimeFormatterBuilder 100% (1/1)100% (6/6)100% (1/1)
appendFractionOfMinute (int, int): DateTimeFormatterBuilder 100% (1/1)100% (6/6)100% (1/1)
appendFractionOfSecond (int, int): DateTimeFormatterBuilder 100% (1/1)100% (6/6)100% (1/1)
appendHalfdayOfDayText (): DateTimeFormatterBuilder 100% (1/1)100% (4/4)100% (1/1)
appendHourOfDay (int): DateTimeFormatterBuilder 100% (1/1)100% (6/6)100% (1/1)
appendHourOfHalfday (int): DateTimeFormatterBuilder 100% (1/1)100% (6/6)100% (1/1)
appendLiteral (char): DateTimeFormatterBuilder 100% (1/1)100% (7/7)100% (1/1)
appendMillisOfSecond (int): DateTimeFormatterBuilder 100% (1/1)100% (6/6)100% (1/1)
appendMinuteOfHour (int): DateTimeFormatterBuilder 100% (1/1)100% (6/6)100% (1/1)
appendMonthOfYear (int): DateTimeFormatterBuilder 100% (1/1)100% (6/6)100% (1/1)
appendMonthOfYearShortText (): DateTimeFormatterBuilder 100% (1/1)100% (4/4)100% (1/1)
appendMonthOfYearText (): DateTimeFormatterBuilder 100% (1/1)100% (4/4)100% (1/1)
appendOptional (DateTimeParser): DateTimeFormatterBuilder 100% (1/1)100% (22/22)100% (3/3)
appendSecondOfDay (int): DateTimeFormatterBuilder 100% (1/1)100% (6/6)100% (1/1)
appendSecondOfMinute (int): DateTimeFormatterBuilder 100% (1/1)100% (6/6)100% (1/1)
appendTimeZoneId (): DateTimeFormatterBuilder 100% (1/1)100% (8/8)100% (1/1)
appendTimeZoneName (): DateTimeFormatterBuilder 100% (1/1)100% (8/8)100% (1/1)
appendTimeZoneOffset (String, boolean, int, int): DateTimeFormatterBuilder 100% (1/1)100% (10/10)100% (1/1)
appendTimeZoneShortName (): DateTimeFormatterBuilder 100% (1/1)100% (8/8)100% (1/1)
appendTwoDigitWeekyear (int): DateTimeFormatterBuilder 100% (1/1)100% (5/5)100% (1/1)
appendTwoDigitWeekyear (int, boolean): DateTimeFormatterBuilder 100% (1/1)100% (9/9)100% (1/1)
appendTwoDigitYear (int): DateTimeFormatterBuilder 100% (1/1)100% (5/5)100% (1/1)
appendTwoDigitYear (int, boolean): DateTimeFormatterBuilder 100% (1/1)100% (9/9)100% (1/1)
appendUnknownString (StringBuffer, int): void 100% (1/1)100% (11/11)100% (3/3)
appendWeekOfWeekyear (int): DateTimeFormatterBuilder 100% (1/1)100% (6/6)100% (1/1)
appendWeekyear (int, int): DateTimeFormatterBuilder 100% (1/1)100% (6/6)100% (1/1)
appendYear (int, int): DateTimeFormatterBuilder 100% (1/1)100% (6/6)100% (1/1)
appendYearOfEra (int, int): DateTimeFormatterBuilder 100% (1/1)100% (6/6)100% (1/1)
canBuildFormatter (): boolean 100% (1/1)100% (5/5)100% (1/1)
canBuildParser (): boolean 100% (1/1)100% (5/5)100% (1/1)
canBuildPrinter (): boolean 100% (1/1)100% (5/5)100% (1/1)
getFormatter (): Object 100% (1/1)100% (45/45)100% (13/13)
isFormatter (Object): boolean 100% (1/1)100% (12/12)100% (1/1)
isParser (Object): boolean 100% (1/1)100% (14/14)100% (5/5)
isPrinter (Object): boolean 100% (1/1)100% (14/14)100% (5/5)
toFormatter (): DateTimeFormatter 100% (1/1)100% (36/36)100% (10/10)
toParser (): DateTimeParser 100% (1/1)100% (15/15)100% (4/4)
toPrinter (): DateTimePrinter 100% (1/1)100% (15/15)100% (4/4)
     
class DateTimeFormatterBuilder$TextField100% (1/1)91%  (10/11)89%  (311/351)83%  (67.4/81)
printTo (Writer, ReadablePartial, Locale): void 0%   (0/1)0%   (0/12)0%   (0/5)
printTo (StringBuffer, ReadablePartial, Locale): void 100% (1/1)64%  (9/14)60%  (3/5)
printTo (StringBuffer, long, Chronology, int, DateTimeZone, Locale): void 100% (1/1)67%  (10/15)60%  (3/5)
printTo (Writer, long, Chronology, int, DateTimeZone, Locale): void 100% (1/1)69%  (9/13)60%  (3/5)
print (ReadablePartial, Locale): String 100% (1/1)81%  (21/26)83%  (5/6)
parseInto (DateTimeParserBucket, String, int): int 100% (1/1)96%  (220/229)96%  (42.4/44)
<static initializer> 100% (1/1)100% (5/5)100% (1/1)
DateTimeFormatterBuilder$TextField (DateTimeFieldType, boolean): void 100% (1/1)100% (9/9)100% (4/4)
estimateParsedLength (): int 100% (1/1)100% (3/3)100% (1/1)
estimatePrintedLength (): int 100% (1/1)100% (7/7)100% (1/1)
print (long, Chronology, Locale): String 100% (1/1)100% (18/18)100% (4/4)
     
class DateTimeFormatterBuilder$FixedNumber100% (1/1)100% (2/2)89%  (51/57)94%  (15/16)
parseInto (DateTimeParserBucket, String, int): int 100% (1/1)88%  (44/50)93%  (13/14)
DateTimeFormatterBuilder$FixedNumber (DateTimeFieldType, int, boolean): void 100% (1/1)100% (7/7)100% (2/2)
     
class DateTimeFormatterBuilder$Composite100% (1/1)100% (12/12)94%  (337/357)95%  (86/91)
parseInto (DateTimeParserBucket, String, int): int 100% (1/1)87%  (27/31)86%  (6/7)
printTo (StringBuffer, ReadablePartial, Locale): void 100% (1/1)87%  (27/31)89%  (8/9)
printTo (Writer, ReadablePartial, Locale): void 100% (1/1)87%  (27/31)89%  (8/9)
printTo (StringBuffer, long, Chronology, int, DateTimeZone, Locale): void 100% (1/1)88%  (30/34)89%  (8/9)
printTo (Writer, long, Chronology, int, DateTimeZone, Locale): void 100% (1/1)88%  (30/34)89%  (8/9)
DateTimeFormatterBuilder$Composite (List): void 100% (1/1)100% (104/104)100% (27/27)
addArrayToList (List, Object []): void 100% (1/1)100% (17/17)100% (4/4)
decompose (List, List, List): void 100% (1/1)100% (55/55)100% (13/13)
estimateParsedLength (): int 100% (1/1)100% (3/3)100% (1/1)
estimatePrintedLength (): int 100% (1/1)100% (3/3)100% (1/1)
isParser (): boolean 100% (1/1)100% (7/7)100% (1/1)
isPrinter (): boolean 100% (1/1)100% (7/7)100% (1/1)
     
class DateTimeFormatterBuilder$NumberFormatter100% (1/1)100% (3/3)96%  (165/171)93%  (38/41)
parseInto (DateTimeParserBucket, String, int): int 100% (1/1)96%  (150/156)91%  (32/35)
DateTimeFormatterBuilder$NumberFormatter (DateTimeFieldType, int, boolean): void 100% (1/1)100% (12/12)100% (5/5)
estimateParsedLength (): int 100% (1/1)100% (3/3)100% (1/1)
     
class DateTimeFormatterBuilder$CharacterLiteral100% (1/1)100% (8/8)100% (76/76)100% (26/26)
DateTimeFormatterBuilder$CharacterLiteral (char): void 100% (1/1)100% (6/6)100% (3/3)
estimateParsedLength (): int 100% (1/1)100% (2/2)100% (1/1)
estimatePrintedLength (): int 100% (1/1)100% (2/2)100% (1/1)
parseInto (DateTimeParserBucket, String, int): int 100% (1/1)100% (44/44)100% (13/13)
printTo (StringBuffer, ReadablePartial, Locale): void 100% (1/1)100% (6/6)100% (2/2)
printTo (StringBuffer, long, Chronology, int, DateTimeZone, Locale): void 100% (1/1)100% (6/6)100% (2/2)
printTo (Writer, ReadablePartial, Locale): void 100% (1/1)100% (5/5)100% (2/2)
printTo (Writer, long, Chronology, int, DateTimeZone, Locale): void 100% (1/1)100% (5/5)100% (2/2)
     
class DateTimeFormatterBuilder$MatchingParser100% (1/1)100% (3/3)100% (143/143)100% (43/43)
DateTimeFormatterBuilder$MatchingParser (DateTimeParser []): void 100% (1/1)100% (32/32)100% (11/11)
estimateParsedLength (): int 100% (1/1)100% (3/3)100% (1/1)
parseInto (DateTimeParserBucket, String, int): int 100% (1/1)100% (108/108)100% (31/31)

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

[all classes][org.joda.time.format]
EMMA 2.0.5312 (C) Vladimir Roubtsov