0001: /*
0002: * Copyright 2001-2005 Stephen Colebourne
0003: *
0004: * Licensed under the Apache License, Version 2.0 (the "License");
0005: * you may not use this file except in compliance with the License.
0006: * You may obtain a copy of the License at
0007: *
0008: * http://www.apache.org/licenses/LICENSE-2.0
0009: *
0010: * Unless required by applicable law or agreed to in writing, software
0011: * distributed under the License is distributed on an "AS IS" BASIS,
0012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
0013: * See the License for the specific language governing permissions and
0014: * limitations under the License.
0015: */
0016: package org.joda.time.format;
0017:
0018: import java.io.IOException;
0019: import java.io.Writer;
0020: import java.util.ArrayList;
0021: import java.util.HashMap;
0022: import java.util.HashSet;
0023: import java.util.List;
0024: import java.util.Locale;
0025: import java.util.Map;
0026: import java.util.Set;
0027:
0028: import org.joda.time.Chronology;
0029: import org.joda.time.DateTimeConstants;
0030: import org.joda.time.DateTimeField;
0031: import org.joda.time.DateTimeFieldType;
0032: import org.joda.time.DateTimeZone;
0033: import org.joda.time.MutableDateTime;
0034: import org.joda.time.ReadablePartial;
0035: import org.joda.time.MutableDateTime.Property;
0036: import org.joda.time.field.MillisDurationField;
0037: import org.joda.time.field.PreciseDateTimeField;
0038:
0039: /**
0040: * Factory that creates complex instances of DateTimeFormatter via method calls.
0041: * <p>
0042: * Datetime formatting is performed by the {@link DateTimeFormatter} class.
0043: * Three classes provide factory methods to create formatters, and this is one.
0044: * The others are {@link DateTimeFormat} and {@link ISODateTimeFormat}.
0045: * <p>
0046: * DateTimeFormatterBuilder is used for constructing formatters which are then
0047: * used to print or parse. The formatters are built by appending specific fields
0048: * or other formatters to an instance of this builder.
0049: * <p>
0050: * For example, a formatter that prints month and year, like "January 1970",
0051: * can be constructed as follows:
0052: * <p>
0053: * <pre>
0054: * DateTimeFormatter monthAndYear = new DateTimeFormatterBuilder()
0055: * .appendMonthOfYearText()
0056: * .appendLiteral(' ')
0057: * .appendYear(4, 4)
0058: * .toFormatter();
0059: * </pre>
0060: * <p>
0061: * DateTimeFormatterBuilder itself is mutable and not thread-safe, but the
0062: * formatters that it builds are thread-safe and immutable.
0063: *
0064: * @author Brian S O'Neill
0065: * @author Stephen Colebourne
0066: * @author Fredrik Borgh
0067: * @since 1.0
0068: * @see DateTimeFormat
0069: * @see ISODateTimeFormat
0070: */
0071: public class DateTimeFormatterBuilder {
0072:
0073: /** Array of printers and parsers (alternating). */
0074: private ArrayList iElementPairs;
0075: /** Cache of the last returned formatter. */
0076: private Object iFormatter;
0077:
0078: //-----------------------------------------------------------------------
0079: /**
0080: * Creates a DateTimeFormatterBuilder.
0081: */
0082: public DateTimeFormatterBuilder() {
0083: super ();
0084: iElementPairs = new ArrayList();
0085: }
0086:
0087: //-----------------------------------------------------------------------
0088: /**
0089: * Constructs a DateTimeFormatter using all the appended elements.
0090: * <p>
0091: * This is the main method used by applications at the end of the build
0092: * process to create a usable formatter.
0093: * <p>
0094: * Subsequent changes to this builder do not affect the returned formatter.
0095: * <p>
0096: * The returned formatter may not support both printing and parsing.
0097: * The methods {@link DateTimeFormatter#isPrinter()} and
0098: * {@link DateTimeFormatter#isParser()} will help you determine the state
0099: * of the formatter.
0100: *
0101: * @throws UnsupportedOperationException if neither printing nor parsing is supported
0102: */
0103: public DateTimeFormatter toFormatter() {
0104: Object f = getFormatter();
0105: DateTimePrinter printer = null;
0106: if (isPrinter(f)) {
0107: printer = (DateTimePrinter) f;
0108: }
0109: DateTimeParser parser = null;
0110: if (isParser(f)) {
0111: parser = (DateTimeParser) f;
0112: }
0113: if (printer != null || parser != null) {
0114: return new DateTimeFormatter(printer, parser);
0115: }
0116: throw new UnsupportedOperationException(
0117: "Both printing and parsing not supported");
0118: }
0119:
0120: /**
0121: * Internal method to create a DateTimePrinter instance using all the
0122: * appended elements.
0123: * <p>
0124: * Most applications will not use this method.
0125: * If you want a printer in an application, call {@link #toFormatter()}
0126: * and just use the printing API.
0127: * <p>
0128: * Subsequent changes to this builder do not affect the returned printer.
0129: *
0130: * @throws UnsupportedOperationException if printing is not supported
0131: */
0132: public DateTimePrinter toPrinter() {
0133: Object f = getFormatter();
0134: if (isPrinter(f)) {
0135: return (DateTimePrinter) f;
0136: }
0137: throw new UnsupportedOperationException(
0138: "Printing is not supported");
0139: }
0140:
0141: /**
0142: * Internal method to create a DateTimeParser instance using all the
0143: * appended elements.
0144: * <p>
0145: * Most applications will not use this method.
0146: * If you want a parser in an application, call {@link #toFormatter()}
0147: * and just use the parsing API.
0148: * <p>
0149: * Subsequent changes to this builder do not affect the returned parser.
0150: *
0151: * @throws UnsupportedOperationException if parsing is not supported
0152: */
0153: public DateTimeParser toParser() {
0154: Object f = getFormatter();
0155: if (isParser(f)) {
0156: return (DateTimeParser) f;
0157: }
0158: throw new UnsupportedOperationException(
0159: "Parsing is not supported");
0160: }
0161:
0162: //-----------------------------------------------------------------------
0163: /**
0164: * Returns true if toFormatter can be called without throwing an
0165: * UnsupportedOperationException.
0166: *
0167: * @return true if a formatter can be built
0168: */
0169: public boolean canBuildFormatter() {
0170: return isFormatter(getFormatter());
0171: }
0172:
0173: /**
0174: * Returns true if toPrinter can be called without throwing an
0175: * UnsupportedOperationException.
0176: *
0177: * @return true if a printer can be built
0178: */
0179: public boolean canBuildPrinter() {
0180: return isPrinter(getFormatter());
0181: }
0182:
0183: /**
0184: * Returns true if toParser can be called without throwing an
0185: * UnsupportedOperationException.
0186: *
0187: * @return true if a parser can be built
0188: */
0189: public boolean canBuildParser() {
0190: return isParser(getFormatter());
0191: }
0192:
0193: //-----------------------------------------------------------------------
0194: /**
0195: * Clears out all the appended elements, allowing this builder to be
0196: * reused.
0197: */
0198: public void clear() {
0199: iFormatter = null;
0200: iElementPairs.clear();
0201: }
0202:
0203: //-----------------------------------------------------------------------
0204: /**
0205: * Appends another formatter.
0206: *
0207: * @param formatter the formatter to add
0208: * @return this DateTimeFormatterBuilder
0209: * @throws IllegalArgumentException if formatter is null or of an invalid type
0210: */
0211: public DateTimeFormatterBuilder append(DateTimeFormatter formatter) {
0212: if (formatter == null) {
0213: throw new IllegalArgumentException("No formatter supplied");
0214: }
0215: return append0(formatter.getPrinter(), formatter.getParser());
0216: }
0217:
0218: /**
0219: * Appends just a printer. With no matching parser, a parser cannot be
0220: * built from this DateTimeFormatterBuilder.
0221: *
0222: * @param printer the printer to add
0223: * @return this DateTimeFormatterBuilder
0224: * @throws IllegalArgumentException if printer is null or of an invalid type
0225: */
0226: public DateTimeFormatterBuilder append(DateTimePrinter printer) {
0227: checkPrinter(printer);
0228: return append0(printer, null);
0229: }
0230:
0231: /**
0232: * Appends just a parser. With no matching printer, a printer cannot be
0233: * built from this builder.
0234: *
0235: * @param parser the parser to add
0236: * @return this DateTimeFormatterBuilder
0237: * @throws IllegalArgumentException if parser is null or of an invalid type
0238: */
0239: public DateTimeFormatterBuilder append(DateTimeParser parser) {
0240: checkParser(parser);
0241: return append0(null, parser);
0242: }
0243:
0244: /**
0245: * Appends a printer/parser pair.
0246: *
0247: * @param printer the printer to add
0248: * @param parser the parser to add
0249: * @return this DateTimeFormatterBuilder
0250: * @throws IllegalArgumentException if printer or parser is null or of an invalid type
0251: */
0252: public DateTimeFormatterBuilder append(DateTimePrinter printer,
0253: DateTimeParser parser) {
0254: checkPrinter(printer);
0255: checkParser(parser);
0256: return append0(printer, parser);
0257: }
0258:
0259: /**
0260: * Appends a printer and a set of matching parsers. When parsing, the first
0261: * parser in the list is selected for parsing. If it fails, the next is
0262: * chosen, and so on. If none of these parsers succeeds, then the failed
0263: * position of the parser that made the greatest progress is returned.
0264: * <p>
0265: * Only the printer is optional. In addtion, it is illegal for any but the
0266: * last of the parser array elements to be null. If the last element is
0267: * null, this represents the empty parser. The presence of an empty parser
0268: * indicates that the entire array of parse formats is optional.
0269: *
0270: * @param printer the printer to add
0271: * @param parsers the parsers to add
0272: * @return this DateTimeFormatterBuilder
0273: * @throws IllegalArgumentException if any printer or parser is of an invalid type
0274: * @throws IllegalArgumentException if any parser element but the last is null
0275: */
0276: public DateTimeFormatterBuilder append(DateTimePrinter printer,
0277: DateTimeParser[] parsers) {
0278: if (printer != null) {
0279: checkPrinter(printer);
0280: }
0281: if (parsers == null) {
0282: throw new IllegalArgumentException("No parsers supplied");
0283: }
0284: int length = parsers.length;
0285: if (length == 1) {
0286: if (parsers[0] == null) {
0287: throw new IllegalArgumentException("No parser supplied");
0288: }
0289: return append0(printer, parsers[0]);
0290: }
0291:
0292: DateTimeParser[] copyOfParsers = new DateTimeParser[length];
0293: int i;
0294: for (i = 0; i < length - 1; i++) {
0295: if ((copyOfParsers[i] = parsers[i]) == null) {
0296: throw new IllegalArgumentException(
0297: "Incomplete parser array");
0298: }
0299: }
0300: copyOfParsers[i] = parsers[i];
0301:
0302: return append0(printer, new MatchingParser(copyOfParsers));
0303: }
0304:
0305: /**
0306: * Appends just a parser element which is optional. With no matching
0307: * printer, a printer cannot be built from this DateTimeFormatterBuilder.
0308: *
0309: * @return this DateTimeFormatterBuilder
0310: * @throws IllegalArgumentException if parser is null or of an invalid type
0311: */
0312: public DateTimeFormatterBuilder appendOptional(DateTimeParser parser) {
0313: checkParser(parser);
0314: DateTimeParser[] parsers = new DateTimeParser[] { parser, null };
0315: return append0(null, new MatchingParser(parsers));
0316: }
0317:
0318: //-----------------------------------------------------------------------
0319: /**
0320: * Checks if the parser is non null and a provider.
0321: *
0322: * @param parser the parser to check
0323: */
0324: private void checkParser(DateTimeParser parser) {
0325: if (parser == null) {
0326: throw new IllegalArgumentException("No parser supplied");
0327: }
0328: }
0329:
0330: /**
0331: * Checks if the printer is non null and a provider.
0332: *
0333: * @param printer the printer to check
0334: */
0335: private void checkPrinter(DateTimePrinter printer) {
0336: if (printer == null) {
0337: throw new IllegalArgumentException("No printer supplied");
0338: }
0339: }
0340:
0341: private DateTimeFormatterBuilder append0(Object element) {
0342: iFormatter = null;
0343: // Add the element as both a printer and parser.
0344: iElementPairs.add(element);
0345: iElementPairs.add(element);
0346: return this ;
0347: }
0348:
0349: private DateTimeFormatterBuilder append0(DateTimePrinter printer,
0350: DateTimeParser parser) {
0351: iFormatter = null;
0352: iElementPairs.add(printer);
0353: iElementPairs.add(parser);
0354: return this ;
0355: }
0356:
0357: //-----------------------------------------------------------------------
0358: /**
0359: * Instructs the printer to emit a specific character, and the parser to
0360: * expect it. The parser is case-insensitive.
0361: *
0362: * @return this DateTimeFormatterBuilder
0363: */
0364: public DateTimeFormatterBuilder appendLiteral(char c) {
0365: return append0(new CharacterLiteral(c));
0366: }
0367:
0368: /**
0369: * Instructs the printer to emit specific text, and the parser to expect
0370: * it. The parser is case-insensitive.
0371: *
0372: * @return this DateTimeFormatterBuilder
0373: * @throws IllegalArgumentException if text is null
0374: */
0375: public DateTimeFormatterBuilder appendLiteral(String text) {
0376: if (text == null) {
0377: throw new IllegalArgumentException(
0378: "Literal must not be null");
0379: }
0380: switch (text.length()) {
0381: case 0:
0382: return this ;
0383: case 1:
0384: return append0(new CharacterLiteral(text.charAt(0)));
0385: default:
0386: return append0(new StringLiteral(text));
0387: }
0388: }
0389:
0390: /**
0391: * Instructs the printer to emit a field value as a decimal number, and the
0392: * parser to expect an unsigned decimal number.
0393: *
0394: * @param fieldType type of field to append
0395: * @param minDigits minumum number of digits to <i>print</i>
0396: * @param maxDigits maximum number of digits to <i>parse</i>, or the estimated
0397: * maximum number of digits to print
0398: * @return this DateTimeFormatterBuilder
0399: * @throws IllegalArgumentException if field type is null
0400: */
0401: public DateTimeFormatterBuilder appendDecimal(
0402: DateTimeFieldType fieldType, int minDigits, int maxDigits) {
0403: if (fieldType == null) {
0404: throw new IllegalArgumentException(
0405: "Field type must not be null");
0406: }
0407: if (maxDigits < minDigits) {
0408: maxDigits = minDigits;
0409: }
0410: if (minDigits < 0 || maxDigits <= 0) {
0411: throw new IllegalArgumentException();
0412: }
0413: if (minDigits <= 1) {
0414: return append0(new UnpaddedNumber(fieldType, maxDigits,
0415: false));
0416: } else {
0417: return append0(new PaddedNumber(fieldType, maxDigits,
0418: false, minDigits));
0419: }
0420: }
0421:
0422: /**
0423: * Instructs the printer to emit a field value as a fixed-width decimal
0424: * number (smaller numbers will be left-padded with zeros), and the parser
0425: * to expect an unsigned decimal number with the same fixed width.
0426: *
0427: * @param fieldType type of field to append
0428: * @param numDigits the exact number of digits to parse or print, except if
0429: * printed value requires more digits
0430: * @return this DateTimeFormatterBuilder
0431: * @throws IllegalArgumentException if field type is null or if <code>numDigits <= 0</code>
0432: * @since 1.5
0433: */
0434: public DateTimeFormatterBuilder appendFixedDecimal(
0435: DateTimeFieldType fieldType, int numDigits) {
0436: if (fieldType == null) {
0437: throw new IllegalArgumentException(
0438: "Field type must not be null");
0439: }
0440: if (numDigits <= 0) {
0441: throw new IllegalArgumentException(
0442: "Illegal number of digits: " + numDigits);
0443: }
0444: return append0(new FixedNumber(fieldType, numDigits, false));
0445: }
0446:
0447: /**
0448: * Instructs the printer to emit a field value as a decimal number, and the
0449: * parser to expect a signed decimal number.
0450: *
0451: * @param fieldType type of field to append
0452: * @param minDigits minumum number of digits to <i>print</i>
0453: * @param maxDigits maximum number of digits to <i>parse</i>, or the estimated
0454: * maximum number of digits to print
0455: * @return this DateTimeFormatterBuilder
0456: * @throws IllegalArgumentException if field type is null
0457: */
0458: public DateTimeFormatterBuilder appendSignedDecimal(
0459: DateTimeFieldType fieldType, int minDigits, int maxDigits) {
0460: if (fieldType == null) {
0461: throw new IllegalArgumentException(
0462: "Field type must not be null");
0463: }
0464: if (maxDigits < minDigits) {
0465: maxDigits = minDigits;
0466: }
0467: if (minDigits < 0 || maxDigits <= 0) {
0468: throw new IllegalArgumentException();
0469: }
0470: if (minDigits <= 1) {
0471: return append0(new UnpaddedNumber(fieldType, maxDigits,
0472: true));
0473: } else {
0474: return append0(new PaddedNumber(fieldType, maxDigits, true,
0475: minDigits));
0476: }
0477: }
0478:
0479: /**
0480: * Instructs the printer to emit a field value as a fixed-width decimal
0481: * number (smaller numbers will be left-padded with zeros), and the parser
0482: * to expect an signed decimal number with the same fixed width.
0483: *
0484: * @param fieldType type of field to append
0485: * @param numDigits the exact number of digits to parse or print, except if
0486: * printed value requires more digits
0487: * @return this DateTimeFormatterBuilder
0488: * @throws IllegalArgumentException if field type is null or if <code>numDigits <= 0</code>
0489: * @since 1.5
0490: */
0491: public DateTimeFormatterBuilder appendFixedSignedDecimal(
0492: DateTimeFieldType fieldType, int numDigits) {
0493: if (fieldType == null) {
0494: throw new IllegalArgumentException(
0495: "Field type must not be null");
0496: }
0497: if (numDigits <= 0) {
0498: throw new IllegalArgumentException(
0499: "Illegal number of digits: " + numDigits);
0500: }
0501: return append0(new FixedNumber(fieldType, numDigits, true));
0502: }
0503:
0504: /**
0505: * Instructs the printer to emit a field value as text, and the
0506: * parser to expect text.
0507: *
0508: * @param fieldType type of field to append
0509: * @return this DateTimeFormatterBuilder
0510: * @throws IllegalArgumentException if field type is null
0511: */
0512: public DateTimeFormatterBuilder appendText(
0513: DateTimeFieldType fieldType) {
0514: if (fieldType == null) {
0515: throw new IllegalArgumentException(
0516: "Field type must not be null");
0517: }
0518: return append0(new TextField(fieldType, false));
0519: }
0520:
0521: /**
0522: * Instructs the printer to emit a field value as short text, and the
0523: * parser to expect text.
0524: *
0525: * @param fieldType type of field to append
0526: * @return this DateTimeFormatterBuilder
0527: * @throws IllegalArgumentException if field type is null
0528: */
0529: public DateTimeFormatterBuilder appendShortText(
0530: DateTimeFieldType fieldType) {
0531: if (fieldType == null) {
0532: throw new IllegalArgumentException(
0533: "Field type must not be null");
0534: }
0535: return append0(new TextField(fieldType, true));
0536: }
0537:
0538: /**
0539: * Instructs the printer to emit a remainder of time as a decimal fraction,
0540: * sans decimal point. For example, if the field is specified as
0541: * minuteOfHour and the time is 12:30:45, the value printed is 75. A
0542: * decimal point is implied, so the fraction is 0.75, or three-quarters of
0543: * a minute.
0544: *
0545: * @param fieldType type of field to append
0546: * @param minDigits minumum number of digits to print.
0547: * @param maxDigits maximum number of digits to print or parse.
0548: * @return this DateTimeFormatterBuilder
0549: * @throws IllegalArgumentException if field type is null
0550: */
0551: public DateTimeFormatterBuilder appendFraction(
0552: DateTimeFieldType fieldType, int minDigits, int maxDigits) {
0553: if (fieldType == null) {
0554: throw new IllegalArgumentException(
0555: "Field type must not be null");
0556: }
0557: if (maxDigits < minDigits) {
0558: maxDigits = minDigits;
0559: }
0560: if (minDigits < 0 || maxDigits <= 0) {
0561: throw new IllegalArgumentException();
0562: }
0563: return append0(new Fraction(fieldType, minDigits, maxDigits));
0564: }
0565:
0566: /**
0567: * @param minDigits minumum number of digits to print
0568: * @param maxDigits maximum number of digits to print or parse
0569: * @return this DateTimeFormatterBuilder
0570: */
0571: public DateTimeFormatterBuilder appendFractionOfSecond(
0572: int minDigits, int maxDigits) {
0573: return appendFraction(DateTimeFieldType.secondOfDay(),
0574: minDigits, maxDigits);
0575: }
0576:
0577: /**
0578: * @param minDigits minumum number of digits to print
0579: * @param maxDigits maximum number of digits to print or parse
0580: * @return this DateTimeFormatterBuilder
0581: */
0582: public DateTimeFormatterBuilder appendFractionOfMinute(
0583: int minDigits, int maxDigits) {
0584: return appendFraction(DateTimeFieldType.minuteOfDay(),
0585: minDigits, maxDigits);
0586: }
0587:
0588: /**
0589: * @param minDigits minumum number of digits to print
0590: * @param maxDigits maximum number of digits to print or parse
0591: * @return this DateTimeFormatterBuilder
0592: */
0593: public DateTimeFormatterBuilder appendFractionOfHour(int minDigits,
0594: int maxDigits) {
0595: return appendFraction(DateTimeFieldType.hourOfDay(), minDigits,
0596: maxDigits);
0597: }
0598:
0599: /**
0600: * @param minDigits minumum number of digits to print
0601: * @param maxDigits maximum number of digits to print or parse
0602: * @return this DateTimeFormatterBuilder
0603: */
0604: public DateTimeFormatterBuilder appendFractionOfDay(int minDigits,
0605: int maxDigits) {
0606: return appendFraction(DateTimeFieldType.dayOfYear(), minDigits,
0607: maxDigits);
0608: }
0609:
0610: /**
0611: * Instructs the printer to emit a numeric millisOfSecond field.
0612: *
0613: * @param minDigits minumum number of digits to print
0614: * @return this DateTimeFormatterBuilder
0615: */
0616: public DateTimeFormatterBuilder appendMillisOfSecond(int minDigits) {
0617: return appendDecimal(DateTimeFieldType.millisOfSecond(),
0618: minDigits, 3);
0619: }
0620:
0621: /**
0622: * Instructs the printer to emit a numeric millisOfDay field.
0623: *
0624: * @param minDigits minumum number of digits to print
0625: * @return this DateTimeFormatterBuilder
0626: */
0627: public DateTimeFormatterBuilder appendMillisOfDay(int minDigits) {
0628: return appendDecimal(DateTimeFieldType.millisOfDay(),
0629: minDigits, 8);
0630: }
0631:
0632: /**
0633: * Instructs the printer to emit a numeric secondOfMinute field.
0634: *
0635: * @param minDigits minumum number of digits to print
0636: * @return this DateTimeFormatterBuilder
0637: */
0638: public DateTimeFormatterBuilder appendSecondOfMinute(int minDigits) {
0639: return appendDecimal(DateTimeFieldType.secondOfMinute(),
0640: minDigits, 2);
0641: }
0642:
0643: /**
0644: * Instructs the printer to emit a numeric secondOfDay field.
0645: *
0646: * @param minDigits minumum number of digits to print
0647: * @return this DateTimeFormatterBuilder
0648: */
0649: public DateTimeFormatterBuilder appendSecondOfDay(int minDigits) {
0650: return appendDecimal(DateTimeFieldType.secondOfDay(),
0651: minDigits, 5);
0652: }
0653:
0654: /**
0655: * Instructs the printer to emit a numeric minuteOfHour field.
0656: *
0657: * @param minDigits minumum number of digits to print
0658: * @return this DateTimeFormatterBuilder
0659: */
0660: public DateTimeFormatterBuilder appendMinuteOfHour(int minDigits) {
0661: return appendDecimal(DateTimeFieldType.minuteOfHour(),
0662: minDigits, 2);
0663: }
0664:
0665: /**
0666: * Instructs the printer to emit a numeric minuteOfDay field.
0667: *
0668: * @param minDigits minumum number of digits to print
0669: * @return this DateTimeFormatterBuilder
0670: */
0671: public DateTimeFormatterBuilder appendMinuteOfDay(int minDigits) {
0672: return appendDecimal(DateTimeFieldType.minuteOfDay(),
0673: minDigits, 4);
0674: }
0675:
0676: /**
0677: * Instructs the printer to emit a numeric hourOfDay field.
0678: *
0679: * @param minDigits minumum number of digits to print
0680: * @return this DateTimeFormatterBuilder
0681: */
0682: public DateTimeFormatterBuilder appendHourOfDay(int minDigits) {
0683: return appendDecimal(DateTimeFieldType.hourOfDay(), minDigits,
0684: 2);
0685: }
0686:
0687: /**
0688: * Instructs the printer to emit a numeric clockhourOfDay field.
0689: *
0690: * @param minDigits minumum number of digits to print
0691: * @return this DateTimeFormatterBuilder
0692: */
0693: public DateTimeFormatterBuilder appendClockhourOfDay(int minDigits) {
0694: return appendDecimal(DateTimeFieldType.clockhourOfDay(),
0695: minDigits, 2);
0696: }
0697:
0698: /**
0699: * Instructs the printer to emit a numeric hourOfHalfday field.
0700: *
0701: * @param minDigits minumum number of digits to print
0702: * @return this DateTimeFormatterBuilder
0703: */
0704: public DateTimeFormatterBuilder appendHourOfHalfday(int minDigits) {
0705: return appendDecimal(DateTimeFieldType.hourOfHalfday(),
0706: minDigits, 2);
0707: }
0708:
0709: /**
0710: * Instructs the printer to emit a numeric clockhourOfHalfday field.
0711: *
0712: * @param minDigits minumum number of digits to print
0713: * @return this DateTimeFormatterBuilder
0714: */
0715: public DateTimeFormatterBuilder appendClockhourOfHalfday(
0716: int minDigits) {
0717: return appendDecimal(DateTimeFieldType.clockhourOfHalfday(),
0718: minDigits, 2);
0719: }
0720:
0721: /**
0722: * Instructs the printer to emit a numeric dayOfWeek field.
0723: *
0724: * @param minDigits minumum number of digits to print
0725: * @return this DateTimeFormatterBuilder
0726: */
0727: public DateTimeFormatterBuilder appendDayOfWeek(int minDigits) {
0728: return appendDecimal(DateTimeFieldType.dayOfWeek(), minDigits,
0729: 1);
0730: }
0731:
0732: /**
0733: * Instructs the printer to emit a numeric dayOfMonth field.
0734: *
0735: * @param minDigits minumum number of digits to print
0736: * @return this DateTimeFormatterBuilder
0737: */
0738: public DateTimeFormatterBuilder appendDayOfMonth(int minDigits) {
0739: return appendDecimal(DateTimeFieldType.dayOfMonth(), minDigits,
0740: 2);
0741: }
0742:
0743: /**
0744: * Instructs the printer to emit a numeric dayOfYear field.
0745: *
0746: * @param minDigits minumum number of digits to print
0747: * @return this DateTimeFormatterBuilder
0748: */
0749: public DateTimeFormatterBuilder appendDayOfYear(int minDigits) {
0750: return appendDecimal(DateTimeFieldType.dayOfYear(), minDigits,
0751: 3);
0752: }
0753:
0754: /**
0755: * Instructs the printer to emit a numeric weekOfWeekyear field.
0756: *
0757: * @param minDigits minumum number of digits to print
0758: * @return this DateTimeFormatterBuilder
0759: */
0760: public DateTimeFormatterBuilder appendWeekOfWeekyear(int minDigits) {
0761: return appendDecimal(DateTimeFieldType.weekOfWeekyear(),
0762: minDigits, 2);
0763: }
0764:
0765: /**
0766: * Instructs the printer to emit a numeric weekyear field.
0767: *
0768: * @param minDigits minumum number of digits to <i>print</i>
0769: * @param maxDigits maximum number of digits to <i>parse</i>, or the estimated
0770: * maximum number of digits to print
0771: * @return this DateTimeFormatterBuilder
0772: */
0773: public DateTimeFormatterBuilder appendWeekyear(int minDigits,
0774: int maxDigits) {
0775: return appendSignedDecimal(DateTimeFieldType.weekyear(),
0776: minDigits, maxDigits);
0777: }
0778:
0779: /**
0780: * Instructs the printer to emit a numeric monthOfYear field.
0781: *
0782: * @param minDigits minumum number of digits to print
0783: * @return this DateTimeFormatterBuilder
0784: */
0785: public DateTimeFormatterBuilder appendMonthOfYear(int minDigits) {
0786: return appendDecimal(DateTimeFieldType.monthOfYear(),
0787: minDigits, 2);
0788: }
0789:
0790: /**
0791: * Instructs the printer to emit a numeric year field.
0792: *
0793: * @param minDigits minumum number of digits to <i>print</i>
0794: * @param maxDigits maximum number of digits to <i>parse</i>, or the estimated
0795: * maximum number of digits to print
0796: * @return this DateTimeFormatterBuilder
0797: */
0798: public DateTimeFormatterBuilder appendYear(int minDigits,
0799: int maxDigits) {
0800: return appendSignedDecimal(DateTimeFieldType.year(), minDigits,
0801: maxDigits);
0802: }
0803:
0804: /**
0805: * Instructs the printer to emit a numeric year field which always prints
0806: * and parses two digits. A pivot year is used during parsing to determine
0807: * the range of supported years as <code>(pivot - 50) .. (pivot + 49)</code>.
0808: *
0809: * <pre>
0810: * pivot supported range 00 is 20 is 40 is 60 is 80 is
0811: * ---------------------------------------------------------------
0812: * 1950 1900..1999 1900 1920 1940 1960 1980
0813: * 1975 1925..2024 2000 2020 1940 1960 1980
0814: * 2000 1950..2049 2000 2020 2040 1960 1980
0815: * 2025 1975..2074 2000 2020 2040 2060 1980
0816: * 2050 2000..2099 2000 2020 2040 2060 2080
0817: * </pre>
0818: *
0819: * @param pivot pivot year to use when parsing
0820: * @return this DateTimeFormatterBuilder
0821: */
0822: public DateTimeFormatterBuilder appendTwoDigitYear(int pivot) {
0823: return appendTwoDigitYear(pivot, false);
0824: }
0825:
0826: /**
0827: * Instructs the printer to emit a numeric year field which always prints
0828: * two digits. A pivot year is used during parsing to determine the range
0829: * of supported years as <code>(pivot - 50) .. (pivot + 49)</code>. If
0830: * parse is instructed to be lenient and the digit count is not two, it is
0831: * treated as an absolute year. With lenient parsing, specifying a positive
0832: * or negative sign before the year also makes it absolute.
0833: *
0834: * @param pivot pivot year to use when parsing
0835: * @param lenientParse when true, if digit count is not two, it is treated
0836: * as an absolute year
0837: * @return this DateTimeFormatterBuilder
0838: * @since 1.1
0839: */
0840: public DateTimeFormatterBuilder appendTwoDigitYear(int pivot,
0841: boolean lenientParse) {
0842: return append0(new TwoDigitYear(DateTimeFieldType.year(),
0843: pivot, lenientParse));
0844: }
0845:
0846: /**
0847: * Instructs the printer to emit a numeric weekyear field which always prints
0848: * and parses two digits. A pivot year is used during parsing to determine
0849: * the range of supported years as <code>(pivot - 50) .. (pivot + 49)</code>.
0850: *
0851: * <pre>
0852: * pivot supported range 00 is 20 is 40 is 60 is 80 is
0853: * ---------------------------------------------------------------
0854: * 1950 1900..1999 1900 1920 1940 1960 1980
0855: * 1975 1925..2024 2000 2020 1940 1960 1980
0856: * 2000 1950..2049 2000 2020 2040 1960 1980
0857: * 2025 1975..2074 2000 2020 2040 2060 1980
0858: * 2050 2000..2099 2000 2020 2040 2060 2080
0859: * </pre>
0860: *
0861: * @param pivot pivot weekyear to use when parsing
0862: * @return this DateTimeFormatterBuilder
0863: */
0864: public DateTimeFormatterBuilder appendTwoDigitWeekyear(int pivot) {
0865: return appendTwoDigitWeekyear(pivot, false);
0866: }
0867:
0868: /**
0869: * Instructs the printer to emit a numeric weekyear field which always prints
0870: * two digits. A pivot year is used during parsing to determine the range
0871: * of supported years as <code>(pivot - 50) .. (pivot + 49)</code>. If
0872: * parse is instructed to be lenient and the digit count is not two, it is
0873: * treated as an absolute weekyear. With lenient parsing, specifying a positive
0874: * or negative sign before the weekyear also makes it absolute.
0875: *
0876: * @param pivot pivot weekyear to use when parsing
0877: * @param lenientParse when true, if digit count is not two, it is treated
0878: * as an absolute weekyear
0879: * @return this DateTimeFormatterBuilder
0880: * @since 1.1
0881: */
0882: public DateTimeFormatterBuilder appendTwoDigitWeekyear(int pivot,
0883: boolean lenientParse) {
0884: return append0(new TwoDigitYear(DateTimeFieldType.weekyear(),
0885: pivot, lenientParse));
0886: }
0887:
0888: /**
0889: * Instructs the printer to emit a numeric yearOfEra field.
0890: *
0891: * @param minDigits minumum number of digits to <i>print</i>
0892: * @param maxDigits maximum number of digits to <i>parse</i>, or the estimated
0893: * maximum number of digits to print
0894: * @return this DateTimeFormatterBuilder
0895: */
0896: public DateTimeFormatterBuilder appendYearOfEra(int minDigits,
0897: int maxDigits) {
0898: return appendDecimal(DateTimeFieldType.yearOfEra(), minDigits,
0899: maxDigits);
0900: }
0901:
0902: /**
0903: * Instructs the printer to emit a numeric year of century field.
0904: *
0905: * @param minDigits minumum number of digits to print
0906: * @param maxDigits maximum number of digits to <i>parse</i>, or the estimated
0907: * maximum number of digits to print
0908: * @return this DateTimeFormatterBuilder
0909: */
0910: public DateTimeFormatterBuilder appendYearOfCentury(int minDigits,
0911: int maxDigits) {
0912: return appendDecimal(DateTimeFieldType.yearOfCentury(),
0913: minDigits, maxDigits);
0914: }
0915:
0916: /**
0917: * Instructs the printer to emit a numeric century of era field.
0918: *
0919: * @param minDigits minumum number of digits to print
0920: * @param maxDigits maximum number of digits to <i>parse</i>, or the estimated
0921: * maximum number of digits to print
0922: * @return this DateTimeFormatterBuilder
0923: */
0924: public DateTimeFormatterBuilder appendCenturyOfEra(int minDigits,
0925: int maxDigits) {
0926: return appendSignedDecimal(DateTimeFieldType.centuryOfEra(),
0927: minDigits, maxDigits);
0928: }
0929:
0930: /**
0931: * Instructs the printer to emit a locale-specific AM/PM text, and the
0932: * parser to expect it. The parser is case-insensitive.
0933: *
0934: * @return this DateTimeFormatterBuilder
0935: */
0936: public DateTimeFormatterBuilder appendHalfdayOfDayText() {
0937: return appendText(DateTimeFieldType.halfdayOfDay());
0938: }
0939:
0940: /**
0941: * Instructs the printer to emit a locale-specific dayOfWeek text. The
0942: * parser will accept a long or short dayOfWeek text, case-insensitive.
0943: *
0944: * @return this DateTimeFormatterBuilder
0945: */
0946: public DateTimeFormatterBuilder appendDayOfWeekText() {
0947: return appendText(DateTimeFieldType.dayOfWeek());
0948: }
0949:
0950: /**
0951: * Instructs the printer to emit a short locale-specific dayOfWeek
0952: * text. The parser will accept a long or short dayOfWeek text,
0953: * case-insensitive.
0954: *
0955: * @return this DateTimeFormatterBuilder
0956: */
0957: public DateTimeFormatterBuilder appendDayOfWeekShortText() {
0958: return appendShortText(DateTimeFieldType.dayOfWeek());
0959: }
0960:
0961: /**
0962: * Instructs the printer to emit a short locale-specific monthOfYear
0963: * text. The parser will accept a long or short monthOfYear text,
0964: * case-insensitive.
0965: *
0966: * @return this DateTimeFormatterBuilder
0967: */
0968: public DateTimeFormatterBuilder appendMonthOfYearText() {
0969: return appendText(DateTimeFieldType.monthOfYear());
0970: }
0971:
0972: /**
0973: * Instructs the printer to emit a locale-specific monthOfYear text. The
0974: * parser will accept a long or short monthOfYear text, case-insensitive.
0975: *
0976: * @return this DateTimeFormatterBuilder
0977: */
0978: public DateTimeFormatterBuilder appendMonthOfYearShortText() {
0979: return appendShortText(DateTimeFieldType.monthOfYear());
0980: }
0981:
0982: /**
0983: * Instructs the printer to emit a locale-specific era text (BC/AD), and
0984: * the parser to expect it. The parser is case-insensitive.
0985: *
0986: * @return this DateTimeFormatterBuilder
0987: */
0988: public DateTimeFormatterBuilder appendEraText() {
0989: return appendText(DateTimeFieldType.era());
0990: }
0991:
0992: /**
0993: * Instructs the printer to emit a locale-specific time zone name. A
0994: * parser cannot be created from this builder if a time zone name is
0995: * appended.
0996: *
0997: * @return this DateTimeFormatterBuilder
0998: */
0999: public DateTimeFormatterBuilder appendTimeZoneName() {
1000: return append0(new TimeZoneName(TimeZoneName.LONG_NAME), null);
1001: }
1002:
1003: /**
1004: * Instructs the printer to emit a short locale-specific time zone
1005: * name. A parser cannot be created from this builder if time zone
1006: * name is appended.
1007: *
1008: * @return this DateTimeFormatterBuilder
1009: */
1010: public DateTimeFormatterBuilder appendTimeZoneShortName() {
1011: return append0(new TimeZoneName(TimeZoneName.SHORT_NAME), null);
1012: }
1013:
1014: /**
1015: * Instructs the printer to emit the identifier of the time zone.
1016: * This field cannot currently be parsed.
1017: *
1018: * @return this DateTimeFormatterBuilder
1019: */
1020: public DateTimeFormatterBuilder appendTimeZoneId() {
1021: return append0(new TimeZoneName(TimeZoneName.ID), null);
1022: }
1023:
1024: /**
1025: * Instructs the printer to emit text and numbers to display time zone
1026: * offset from UTC. A parser will use the parsed time zone offset to adjust
1027: * the datetime.
1028: *
1029: * @param zeroOffsetText Text to use if time zone offset is zero. If
1030: * null, offset is always shown.
1031: * @param showSeparators If true, prints ':' separator before minute and
1032: * second field and prints '.' separator before fraction field.
1033: * @param minFields minimum number of fields to print, stopping when no
1034: * more precision is required. 1=hours, 2=minutes, 3=seconds, 4=fraction
1035: * @param maxFields maximum number of fields to print
1036: * @return this DateTimeFormatterBuilder
1037: */
1038: public DateTimeFormatterBuilder appendTimeZoneOffset(
1039: String zeroOffsetText, boolean showSeparators,
1040: int minFields, int maxFields) {
1041: return append0(new TimeZoneOffset(zeroOffsetText,
1042: showSeparators, minFields, maxFields));
1043: }
1044:
1045: //-----------------------------------------------------------------------
1046: /**
1047: * Calls upon {@link DateTimeFormat} to parse the pattern and append the
1048: * results into this builder.
1049: *
1050: * @param pattern pattern specification
1051: * @throws IllegalArgumentException if the pattern is invalid
1052: * @see DateTimeFormat
1053: */
1054: public DateTimeFormatterBuilder appendPattern(String pattern) {
1055: DateTimeFormat.appendPatternTo(this , pattern);
1056: return this ;
1057: }
1058:
1059: //-----------------------------------------------------------------------
1060: private Object getFormatter() {
1061: Object f = iFormatter;
1062:
1063: if (f == null) {
1064: if (iElementPairs.size() == 2) {
1065: Object printer = iElementPairs.get(0);
1066: Object parser = iElementPairs.get(1);
1067:
1068: if (printer != null) {
1069: if (printer == parser || parser == null) {
1070: f = printer;
1071: }
1072: } else {
1073: f = parser;
1074: }
1075: }
1076:
1077: if (f == null) {
1078: f = new Composite(iElementPairs);
1079: }
1080:
1081: iFormatter = f;
1082: }
1083:
1084: return f;
1085: }
1086:
1087: private boolean isPrinter(Object f) {
1088: if (f instanceof DateTimePrinter) {
1089: if (f instanceof Composite) {
1090: return ((Composite) f).isPrinter();
1091: }
1092: return true;
1093: }
1094: return false;
1095: }
1096:
1097: private boolean isParser(Object f) {
1098: if (f instanceof DateTimeParser) {
1099: if (f instanceof Composite) {
1100: return ((Composite) f).isParser();
1101: }
1102: return true;
1103: }
1104: return false;
1105: }
1106:
1107: private boolean isFormatter(Object f) {
1108: return (isPrinter(f) || isParser(f));
1109: }
1110:
1111: static void appendUnknownString(StringBuffer buf, int len) {
1112: for (int i = len; --i >= 0;) {
1113: buf.append('\ufffd');
1114: }
1115: }
1116:
1117: static void printUnknownString(Writer out, int len)
1118: throws IOException {
1119: for (int i = len; --i >= 0;) {
1120: out.write('\ufffd');
1121: }
1122: }
1123:
1124: //-----------------------------------------------------------------------
1125: static class CharacterLiteral implements DateTimePrinter,
1126: DateTimeParser {
1127:
1128: private final char iValue;
1129:
1130: CharacterLiteral(char value) {
1131: super ();
1132: iValue = value;
1133: }
1134:
1135: public int estimatePrintedLength() {
1136: return 1;
1137: }
1138:
1139: public void printTo(StringBuffer buf, long instant,
1140: Chronology chrono, int displayOffset,
1141: DateTimeZone displayZone, Locale locale) {
1142: buf.append(iValue);
1143: }
1144:
1145: public void printTo(Writer out, long instant,
1146: Chronology chrono, int displayOffset,
1147: DateTimeZone displayZone, Locale locale)
1148: throws IOException {
1149: out.write(iValue);
1150: }
1151:
1152: public void printTo(StringBuffer buf, ReadablePartial partial,
1153: Locale locale) {
1154: buf.append(iValue);
1155: }
1156:
1157: public void printTo(Writer out, ReadablePartial partial,
1158: Locale locale) throws IOException {
1159: out.write(iValue);
1160: }
1161:
1162: public int estimateParsedLength() {
1163: return 1;
1164: }
1165:
1166: public int parseInto(DateTimeParserBucket bucket, String text,
1167: int position) {
1168: if (position >= text.length()) {
1169: return ~position;
1170: }
1171:
1172: char a = text.charAt(position);
1173: char b = iValue;
1174:
1175: if (a != b) {
1176: a = Character.toUpperCase(a);
1177: b = Character.toUpperCase(b);
1178: if (a != b) {
1179: a = Character.toLowerCase(a);
1180: b = Character.toLowerCase(b);
1181: if (a != b) {
1182: return ~position;
1183: }
1184: }
1185: }
1186:
1187: return position + 1;
1188: }
1189: }
1190:
1191: //-----------------------------------------------------------------------
1192: static class StringLiteral implements DateTimePrinter,
1193: DateTimeParser {
1194:
1195: private final String iValue;
1196:
1197: StringLiteral(String value) {
1198: super ();
1199: iValue = value;
1200: }
1201:
1202: public int estimatePrintedLength() {
1203: return iValue.length();
1204: }
1205:
1206: public void printTo(StringBuffer buf, long instant,
1207: Chronology chrono, int displayOffset,
1208: DateTimeZone displayZone, Locale locale) {
1209: buf.append(iValue);
1210: }
1211:
1212: public void printTo(Writer out, long instant,
1213: Chronology chrono, int displayOffset,
1214: DateTimeZone displayZone, Locale locale)
1215: throws IOException {
1216: out.write(iValue);
1217: }
1218:
1219: public void printTo(StringBuffer buf, ReadablePartial partial,
1220: Locale locale) {
1221: buf.append(iValue);
1222: }
1223:
1224: public void printTo(Writer out, ReadablePartial partial,
1225: Locale locale) throws IOException {
1226: out.write(iValue);
1227: }
1228:
1229: public int estimateParsedLength() {
1230: return iValue.length();
1231: }
1232:
1233: public int parseInto(DateTimeParserBucket bucket, String text,
1234: int position) {
1235: if (text.regionMatches(true, position, iValue, 0, iValue
1236: .length())) {
1237: return position + iValue.length();
1238: }
1239: return ~position;
1240: }
1241: }
1242:
1243: //-----------------------------------------------------------------------
1244: static abstract class NumberFormatter implements DateTimePrinter,
1245: DateTimeParser {
1246: protected final DateTimeFieldType iFieldType;
1247: protected final int iMaxParsedDigits;
1248: protected final boolean iSigned;
1249:
1250: NumberFormatter(DateTimeFieldType fieldType,
1251: int maxParsedDigits, boolean signed) {
1252: super ();
1253: iFieldType = fieldType;
1254: iMaxParsedDigits = maxParsedDigits;
1255: iSigned = signed;
1256: }
1257:
1258: public int estimateParsedLength() {
1259: return iMaxParsedDigits;
1260: }
1261:
1262: public int parseInto(DateTimeParserBucket bucket, String text,
1263: int position) {
1264: int limit = Math.min(iMaxParsedDigits, text.length()
1265: - position);
1266:
1267: boolean negative = false;
1268: int length = 0;
1269: while (length < limit) {
1270: char c = text.charAt(position + length);
1271: if (length == 0 && (c == '-' || c == '+') && iSigned) {
1272: negative = c == '-';
1273:
1274: // Next character must be a digit.
1275: if (length + 1 >= limit
1276: || (c = text.charAt(position + length + 1)) < '0'
1277: || c > '9') {
1278: break;
1279: }
1280:
1281: if (negative) {
1282: length++;
1283: } else {
1284: // Skip the '+' for parseInt to succeed.
1285: position++;
1286: }
1287: // Expand the limit to disregard the sign character.
1288: limit = Math.min(limit + 1, text.length()
1289: - position);
1290: continue;
1291: }
1292: if (c < '0' || c > '9') {
1293: break;
1294: }
1295: length++;
1296: }
1297:
1298: if (length == 0) {
1299: return ~position;
1300: }
1301:
1302: int value;
1303: if (length >= 9) {
1304: // Since value may exceed integer limits, use stock parser
1305: // which checks for this.
1306: value = Integer.parseInt(text.substring(position,
1307: position += length));
1308: } else {
1309: int i = position;
1310: if (negative) {
1311: i++;
1312: }
1313: try {
1314: value = text.charAt(i++) - '0';
1315: } catch (StringIndexOutOfBoundsException e) {
1316: return ~position;
1317: }
1318: position += length;
1319: while (i < position) {
1320: value = ((value << 3) + (value << 1))
1321: + text.charAt(i++) - '0';
1322: }
1323: if (negative) {
1324: value = -value;
1325: }
1326: }
1327:
1328: bucket.saveField(iFieldType, value);
1329: return position;
1330: }
1331: }
1332:
1333: //-----------------------------------------------------------------------
1334: static class UnpaddedNumber extends NumberFormatter {
1335:
1336: protected UnpaddedNumber(DateTimeFieldType fieldType,
1337: int maxParsedDigits, boolean signed) {
1338: super (fieldType, maxParsedDigits, signed);
1339: }
1340:
1341: public int estimatePrintedLength() {
1342: return iMaxParsedDigits;
1343: }
1344:
1345: public void printTo(StringBuffer buf, long instant,
1346: Chronology chrono, int displayOffset,
1347: DateTimeZone displayZone, Locale locale) {
1348: try {
1349: DateTimeField field = iFieldType.getField(chrono);
1350: FormatUtils.appendUnpaddedInteger(buf, field
1351: .get(instant));
1352: } catch (RuntimeException e) {
1353: buf.append('\ufffd');
1354: }
1355: }
1356:
1357: public void printTo(Writer out, long instant,
1358: Chronology chrono, int displayOffset,
1359: DateTimeZone displayZone, Locale locale)
1360: throws IOException {
1361: try {
1362: DateTimeField field = iFieldType.getField(chrono);
1363: FormatUtils.writeUnpaddedInteger(out, field
1364: .get(instant));
1365: } catch (RuntimeException e) {
1366: out.write('\ufffd');
1367: }
1368: }
1369:
1370: public void printTo(StringBuffer buf, ReadablePartial partial,
1371: Locale locale) {
1372: if (partial.isSupported(iFieldType)) {
1373: try {
1374: FormatUtils.appendUnpaddedInteger(buf, partial
1375: .get(iFieldType));
1376: } catch (RuntimeException e) {
1377: buf.append('\ufffd');
1378: }
1379: } else {
1380: buf.append('\ufffd');
1381: }
1382: }
1383:
1384: public void printTo(Writer out, ReadablePartial partial,
1385: Locale locale) throws IOException {
1386: if (partial.isSupported(iFieldType)) {
1387: try {
1388: FormatUtils.writeUnpaddedInteger(out, partial
1389: .get(iFieldType));
1390: } catch (RuntimeException e) {
1391: out.write('\ufffd');
1392: }
1393: } else {
1394: out.write('\ufffd');
1395: }
1396: }
1397: }
1398:
1399: //-----------------------------------------------------------------------
1400: static class PaddedNumber extends NumberFormatter {
1401:
1402: protected final int iMinPrintedDigits;
1403:
1404: protected PaddedNumber(DateTimeFieldType fieldType,
1405: int maxParsedDigits, boolean signed,
1406: int minPrintedDigits) {
1407: super (fieldType, maxParsedDigits, signed);
1408: iMinPrintedDigits = minPrintedDigits;
1409: }
1410:
1411: public int estimatePrintedLength() {
1412: return iMaxParsedDigits;
1413: }
1414:
1415: public void printTo(StringBuffer buf, long instant,
1416: Chronology chrono, int displayOffset,
1417: DateTimeZone displayZone, Locale locale) {
1418: try {
1419: DateTimeField field = iFieldType.getField(chrono);
1420: FormatUtils.appendPaddedInteger(buf,
1421: field.get(instant), iMinPrintedDigits);
1422: } catch (RuntimeException e) {
1423: appendUnknownString(buf, iMinPrintedDigits);
1424: }
1425: }
1426:
1427: public void printTo(Writer out, long instant,
1428: Chronology chrono, int displayOffset,
1429: DateTimeZone displayZone, Locale locale)
1430: throws IOException {
1431: try {
1432: DateTimeField field = iFieldType.getField(chrono);
1433: FormatUtils.writePaddedInteger(out, field.get(instant),
1434: iMinPrintedDigits);
1435: } catch (RuntimeException e) {
1436: printUnknownString(out, iMinPrintedDigits);
1437: }
1438: }
1439:
1440: public void printTo(StringBuffer buf, ReadablePartial partial,
1441: Locale locale) {
1442: if (partial.isSupported(iFieldType)) {
1443: try {
1444: FormatUtils.appendPaddedInteger(buf, partial
1445: .get(iFieldType), iMinPrintedDigits);
1446: } catch (RuntimeException e) {
1447: appendUnknownString(buf, iMinPrintedDigits);
1448: }
1449: } else {
1450: appendUnknownString(buf, iMinPrintedDigits);
1451: }
1452: }
1453:
1454: public void printTo(Writer out, ReadablePartial partial,
1455: Locale locale) throws IOException {
1456: if (partial.isSupported(iFieldType)) {
1457: try {
1458: FormatUtils.writePaddedInteger(out, partial
1459: .get(iFieldType), iMinPrintedDigits);
1460: } catch (RuntimeException e) {
1461: printUnknownString(out, iMinPrintedDigits);
1462: }
1463: } else {
1464: printUnknownString(out, iMinPrintedDigits);
1465: }
1466: }
1467: }
1468:
1469: //-----------------------------------------------------------------------
1470: static class FixedNumber extends PaddedNumber {
1471:
1472: protected FixedNumber(DateTimeFieldType fieldType,
1473: int numDigits, boolean signed) {
1474: super (fieldType, numDigits, signed, numDigits);
1475: }
1476:
1477: public int parseInto(DateTimeParserBucket bucket, String text,
1478: int position) {
1479: int newPos = super .parseInto(bucket, text, position);
1480: if (newPos < 0) {
1481: return newPos;
1482: }
1483: int expectedPos = position + iMaxParsedDigits;
1484: if (newPos != expectedPos) {
1485: if (iSigned) {
1486: char c = text.charAt(position);
1487: if (c == '-' || c == '+') {
1488: expectedPos++;
1489: }
1490: }
1491: if (newPos > expectedPos) {
1492: // The failure is at the position of the first extra digit.
1493: return ~(expectedPos + 1);
1494: } else if (newPos < expectedPos) {
1495: // The failure is at the position where the next digit should be.
1496: return ~newPos;
1497: }
1498: }
1499: return newPos;
1500: }
1501: }
1502:
1503: //-----------------------------------------------------------------------
1504: static class TwoDigitYear implements DateTimePrinter,
1505: DateTimeParser {
1506:
1507: /** The field to print/parse. */
1508: private final DateTimeFieldType iType;
1509: /** The pivot year. */
1510: private final int iPivot;
1511: private final boolean iLenientParse;
1512:
1513: TwoDigitYear(DateTimeFieldType type, int pivot,
1514: boolean lenientParse) {
1515: super ();
1516: iType = type;
1517: iPivot = pivot;
1518: iLenientParse = lenientParse;
1519: }
1520:
1521: public int estimateParsedLength() {
1522: return iLenientParse ? 4 : 2;
1523: }
1524:
1525: public int parseInto(DateTimeParserBucket bucket, String text,
1526: int position) {
1527: int limit = text.length() - position;
1528:
1529: if (!iLenientParse) {
1530: limit = Math.min(2, limit);
1531: if (limit < 2) {
1532: return ~position;
1533: }
1534: } else {
1535: boolean hasSignChar = false;
1536: boolean negative = false;
1537: int length = 0;
1538: while (length < limit) {
1539: char c = text.charAt(position + length);
1540: if (length == 0 && (c == '-' || c == '+')) {
1541: hasSignChar = true;
1542: negative = c == '-';
1543: if (negative) {
1544: length++;
1545: } else {
1546: // Skip the '+' for parseInt to succeed.
1547: position++;
1548: limit--;
1549: }
1550: continue;
1551: }
1552: if (c < '0' || c > '9') {
1553: break;
1554: }
1555: length++;
1556: }
1557:
1558: if (length == 0) {
1559: return ~position;
1560: }
1561:
1562: if (hasSignChar || length != 2) {
1563: int value;
1564: if (length >= 9) {
1565: // Since value may exceed integer limits, use stock
1566: // parser which checks for this.
1567: value = Integer.parseInt(text.substring(
1568: position, position += length));
1569: } else {
1570: int i = position;
1571: if (negative) {
1572: i++;
1573: }
1574: try {
1575: value = text.charAt(i++) - '0';
1576: } catch (StringIndexOutOfBoundsException e) {
1577: return ~position;
1578: }
1579: position += length;
1580: while (i < position) {
1581: value = ((value << 3) + (value << 1))
1582: + text.charAt(i++) - '0';
1583: }
1584: if (negative) {
1585: value = -value;
1586: }
1587: }
1588:
1589: bucket.saveField(iType, value);
1590: return position;
1591: }
1592: }
1593:
1594: int year;
1595: char c = text.charAt(position);
1596: if (c < '0' || c > '9') {
1597: return ~position;
1598: }
1599: year = c - '0';
1600: c = text.charAt(position + 1);
1601: if (c < '0' || c > '9') {
1602: return ~position;
1603: }
1604: year = ((year << 3) + (year << 1)) + c - '0';
1605:
1606: int pivot = iPivot;
1607: // If the bucket pivot year is non-null, use that when parsing
1608: if (bucket.getPivotYear() != null) {
1609: pivot = bucket.getPivotYear().intValue();
1610: }
1611:
1612: int low = pivot - 50;
1613:
1614: int t;
1615: if (low >= 0) {
1616: t = low % 100;
1617: } else {
1618: t = 99 + ((low + 1) % 100);
1619: }
1620:
1621: year += low + ((year < t) ? 100 : 0) - t;
1622:
1623: bucket.saveField(iType, year);
1624: return position + 2;
1625: }
1626:
1627: public int estimatePrintedLength() {
1628: return 2;
1629: }
1630:
1631: public void printTo(StringBuffer buf, long instant,
1632: Chronology chrono, int displayOffset,
1633: DateTimeZone displayZone, Locale locale) {
1634: int year = getTwoDigitYear(instant, chrono);
1635: if (year < 0) {
1636: buf.append('\ufffd');
1637: buf.append('\ufffd');
1638: } else {
1639: FormatUtils.appendPaddedInteger(buf, year, 2);
1640: }
1641: }
1642:
1643: public void printTo(Writer out, long instant,
1644: Chronology chrono, int displayOffset,
1645: DateTimeZone displayZone, Locale locale)
1646: throws IOException {
1647: int year = getTwoDigitYear(instant, chrono);
1648: if (year < 0) {
1649: out.write('\ufffd');
1650: out.write('\ufffd');
1651: } else {
1652: FormatUtils.writePaddedInteger(out, year, 2);
1653: }
1654: }
1655:
1656: private int getTwoDigitYear(long instant, Chronology chrono) {
1657: try {
1658: int year = iType.getField(chrono).get(instant);
1659: if (year < 0) {
1660: year = -year;
1661: }
1662: return year % 100;
1663: } catch (RuntimeException e) {
1664: return -1;
1665: }
1666: }
1667:
1668: public void printTo(StringBuffer buf, ReadablePartial partial,
1669: Locale locale) {
1670: int year = getTwoDigitYear(partial);
1671: if (year < 0) {
1672: buf.append('\ufffd');
1673: buf.append('\ufffd');
1674: } else {
1675: FormatUtils.appendPaddedInteger(buf, year, 2);
1676: }
1677: }
1678:
1679: public void printTo(Writer out, ReadablePartial partial,
1680: Locale locale) throws IOException {
1681: int year = getTwoDigitYear(partial);
1682: if (year < 0) {
1683: out.write('\ufffd');
1684: out.write('\ufffd');
1685: } else {
1686: FormatUtils.writePaddedInteger(out, year, 2);
1687: }
1688: }
1689:
1690: private int getTwoDigitYear(ReadablePartial partial) {
1691: if (partial.isSupported(iType)) {
1692: try {
1693: int year = partial.get(iType);
1694: if (year < 0) {
1695: year = -year;
1696: }
1697: return year % 100;
1698: } catch (RuntimeException e) {
1699: }
1700: }
1701: return -1;
1702: }
1703: }
1704:
1705: //-----------------------------------------------------------------------
1706: static class TextField implements DateTimePrinter, DateTimeParser {
1707:
1708: private static Map cParseCache = new HashMap();
1709: private final DateTimeFieldType iFieldType;
1710: private final boolean iShort;
1711:
1712: TextField(DateTimeFieldType fieldType, boolean isShort) {
1713: super ();
1714: iFieldType = fieldType;
1715: iShort = isShort;
1716: }
1717:
1718: public int estimatePrintedLength() {
1719: return iShort ? 6 : 20;
1720: }
1721:
1722: public void printTo(StringBuffer buf, long instant,
1723: Chronology chrono, int displayOffset,
1724: DateTimeZone displayZone, Locale locale) {
1725: try {
1726: buf.append(print(instant, chrono, locale));
1727: } catch (RuntimeException e) {
1728: buf.append('\ufffd');
1729: }
1730: }
1731:
1732: public void printTo(Writer out, long instant,
1733: Chronology chrono, int displayOffset,
1734: DateTimeZone displayZone, Locale locale)
1735: throws IOException {
1736: try {
1737: out.write(print(instant, chrono, locale));
1738: } catch (RuntimeException e) {
1739: out.write('\ufffd');
1740: }
1741: }
1742:
1743: public void printTo(StringBuffer buf, ReadablePartial partial,
1744: Locale locale) {
1745: try {
1746: buf.append(print(partial, locale));
1747: } catch (RuntimeException e) {
1748: buf.append('\ufffd');
1749: }
1750: }
1751:
1752: public void printTo(Writer out, ReadablePartial partial,
1753: Locale locale) throws IOException {
1754: try {
1755: out.write(print(partial, locale));
1756: } catch (RuntimeException e) {
1757: out.write('\ufffd');
1758: }
1759: }
1760:
1761: private String print(long instant, Chronology chrono,
1762: Locale locale) {
1763: DateTimeField field = iFieldType.getField(chrono);
1764: if (iShort) {
1765: return field.getAsShortText(instant, locale);
1766: } else {
1767: return field.getAsText(instant, locale);
1768: }
1769: }
1770:
1771: private String print(ReadablePartial partial, Locale locale) {
1772: if (partial.isSupported(iFieldType)) {
1773: DateTimeField field = iFieldType.getField(partial
1774: .getChronology());
1775: if (iShort) {
1776: return field.getAsShortText(partial, locale);
1777: } else {
1778: return field.getAsText(partial, locale);
1779: }
1780: } else {
1781: return "\ufffd";
1782: }
1783: }
1784:
1785: public int estimateParsedLength() {
1786: return estimatePrintedLength();
1787: }
1788:
1789: public int parseInto(DateTimeParserBucket bucket, String text,
1790: int position) {
1791: Locale locale = bucket.getLocale();
1792: // handle languages which might have non ASCII A-Z or punctuation
1793: // bug 1788282
1794: Set validValues = null;
1795: int maxLength = 0;
1796: synchronized (cParseCache) {
1797: Map innerMap = (Map) cParseCache.get(locale);
1798: if (innerMap == null) {
1799: innerMap = new HashMap();
1800: cParseCache.put(locale, innerMap);
1801: }
1802: Object[] array = (Object[]) innerMap.get(iFieldType);
1803: if (array == null) {
1804: validValues = new HashSet(32);
1805: MutableDateTime dt = new MutableDateTime(0L,
1806: DateTimeZone.UTC);
1807: Property property = dt.property(iFieldType);
1808: int min = property.getMinimumValueOverall();
1809: int max = property.getMaximumValueOverall();
1810: if (max - min > 32) { // protect against invalid fields
1811: return ~position;
1812: }
1813: maxLength = property.getMaximumTextLength(locale);
1814: for (int i = min; i <= max; i++) {
1815: property.set(i);
1816: validValues
1817: .add(property.getAsShortText(locale));
1818: validValues.add(property.getAsShortText(locale)
1819: .toLowerCase(locale));
1820: validValues.add(property.getAsShortText(locale)
1821: .toUpperCase(locale));
1822: validValues.add(property.getAsText(locale));
1823: validValues.add(property.getAsText(locale)
1824: .toLowerCase(locale));
1825: validValues.add(property.getAsText(locale)
1826: .toUpperCase(locale));
1827: }
1828: if ("en".equals(locale.getLanguage())
1829: && iFieldType == DateTimeFieldType.era()) {
1830: // hack to support for parsing "BCE" and "CE" if the language is English
1831: validValues.add("BCE");
1832: validValues.add("bce");
1833: validValues.add("CE");
1834: validValues.add("ce");
1835: maxLength = 3;
1836: }
1837: array = new Object[] { validValues,
1838: new Integer(maxLength) };
1839: innerMap.put(iFieldType, array);
1840: } else {
1841: validValues = (Set) array[0];
1842: maxLength = ((Integer) array[1]).intValue();
1843: }
1844: }
1845: // match the longest string first using our knowledge of the max length
1846: int limit = Math.min(text.length(), position + maxLength);
1847: for (int i = limit; i > position; i--) {
1848: String match = text.substring(position, i);
1849: if (validValues.contains(match)) {
1850: bucket.saveField(iFieldType, match, locale);
1851: return i;
1852: }
1853: }
1854: return ~position;
1855: }
1856: }
1857:
1858: //-----------------------------------------------------------------------
1859: static class Fraction implements DateTimePrinter, DateTimeParser {
1860:
1861: private final DateTimeFieldType iFieldType;
1862: protected int iMinDigits;
1863: protected int iMaxDigits;
1864:
1865: protected Fraction(DateTimeFieldType fieldType, int minDigits,
1866: int maxDigits) {
1867: super ();
1868: iFieldType = fieldType;
1869: // Limit the precision requirements.
1870: if (maxDigits > 18) {
1871: maxDigits = 18;
1872: }
1873: iMinDigits = minDigits;
1874: iMaxDigits = maxDigits;
1875: }
1876:
1877: public int estimatePrintedLength() {
1878: return iMaxDigits;
1879: }
1880:
1881: public void printTo(StringBuffer buf, long instant,
1882: Chronology chrono, int displayOffset,
1883: DateTimeZone displayZone, Locale locale) {
1884: try {
1885: printTo(buf, null, instant, chrono);
1886: } catch (IOException e) {
1887: // Not gonna happen.
1888: }
1889: }
1890:
1891: public void printTo(Writer out, long instant,
1892: Chronology chrono, int displayOffset,
1893: DateTimeZone displayZone, Locale locale)
1894: throws IOException {
1895: printTo(null, out, instant, chrono);
1896: }
1897:
1898: public void printTo(StringBuffer buf, ReadablePartial partial,
1899: Locale locale) {
1900: if (partial.isSupported(iFieldType)) {
1901: long millis = partial.getChronology().set(partial, 0L);
1902: try {
1903: printTo(buf, null, millis, partial.getChronology());
1904: } catch (IOException e) {
1905: // Not gonna happen.
1906: }
1907: } else {
1908: buf.append('\ufffd');
1909: }
1910: }
1911:
1912: public void printTo(Writer out, ReadablePartial partial,
1913: Locale locale) throws IOException {
1914: if (partial.isSupported(iFieldType)) {
1915: long millis = partial.getChronology().set(partial, 0L);
1916: printTo(null, out, millis, partial.getChronology());
1917: } else {
1918: out.write('\ufffd');
1919: }
1920: }
1921:
1922: protected void printTo(StringBuffer buf, Writer out,
1923: long instant, Chronology chrono) throws IOException {
1924: DateTimeField field = iFieldType.getField(chrono);
1925: int minDigits = iMinDigits;
1926:
1927: long fraction;
1928: try {
1929: fraction = field.remainder(instant);
1930: } catch (RuntimeException e) {
1931: if (buf != null) {
1932: appendUnknownString(buf, minDigits);
1933: } else {
1934: printUnknownString(out, minDigits);
1935: }
1936: return;
1937: }
1938:
1939: if (fraction == 0) {
1940: if (buf != null) {
1941: while (--minDigits >= 0) {
1942: buf.append('0');
1943: }
1944: } else {
1945: while (--minDigits >= 0) {
1946: out.write('0');
1947: }
1948: }
1949: return;
1950: }
1951:
1952: String str;
1953: long[] fractionData = getFractionData(fraction, field);
1954: long scaled = fractionData[0];
1955: int maxDigits = (int) fractionData[1];
1956:
1957: if ((scaled & 0x7fffffff) == scaled) {
1958: str = Integer.toString((int) scaled);
1959: } else {
1960: str = Long.toString(scaled);
1961: }
1962:
1963: int length = str.length();
1964: int digits = maxDigits;
1965: while (length < digits) {
1966: if (buf != null) {
1967: buf.append('0');
1968: } else {
1969: out.write('0');
1970: }
1971: minDigits--;
1972: digits--;
1973: }
1974:
1975: if (minDigits < digits) {
1976: // Chop off as many trailing zero digits as necessary.
1977: while (minDigits < digits) {
1978: if (length <= 1 || str.charAt(length - 1) != '0') {
1979: break;
1980: }
1981: digits--;
1982: length--;
1983: }
1984: if (length < str.length()) {
1985: if (buf != null) {
1986: for (int i = 0; i < length; i++) {
1987: buf.append(str.charAt(i));
1988: }
1989: } else {
1990: for (int i = 0; i < length; i++) {
1991: out.write(str.charAt(i));
1992: }
1993: }
1994: return;
1995: }
1996: }
1997:
1998: if (buf != null) {
1999: buf.append(str);
2000: } else {
2001: out.write(str);
2002: }
2003: }
2004:
2005: private long[] getFractionData(long fraction,
2006: DateTimeField field) {
2007: long rangeMillis = field.getDurationField().getUnitMillis();
2008: long scalar;
2009: int maxDigits = iMaxDigits;
2010: while (true) {
2011: switch (maxDigits) {
2012: default:
2013: scalar = 1L;
2014: break;
2015: case 1:
2016: scalar = 10L;
2017: break;
2018: case 2:
2019: scalar = 100L;
2020: break;
2021: case 3:
2022: scalar = 1000L;
2023: break;
2024: case 4:
2025: scalar = 10000L;
2026: break;
2027: case 5:
2028: scalar = 100000L;
2029: break;
2030: case 6:
2031: scalar = 1000000L;
2032: break;
2033: case 7:
2034: scalar = 10000000L;
2035: break;
2036: case 8:
2037: scalar = 100000000L;
2038: break;
2039: case 9:
2040: scalar = 1000000000L;
2041: break;
2042: case 10:
2043: scalar = 10000000000L;
2044: break;
2045: case 11:
2046: scalar = 100000000000L;
2047: break;
2048: case 12:
2049: scalar = 1000000000000L;
2050: break;
2051: case 13:
2052: scalar = 10000000000000L;
2053: break;
2054: case 14:
2055: scalar = 100000000000000L;
2056: break;
2057: case 15:
2058: scalar = 1000000000000000L;
2059: break;
2060: case 16:
2061: scalar = 10000000000000000L;
2062: break;
2063: case 17:
2064: scalar = 100000000000000000L;
2065: break;
2066: case 18:
2067: scalar = 1000000000000000000L;
2068: break;
2069: }
2070: if (((rangeMillis * scalar) / scalar) == rangeMillis) {
2071: break;
2072: }
2073: // Overflowed: scale down.
2074: maxDigits--;
2075: }
2076:
2077: return new long[] { fraction * scalar / rangeMillis,
2078: maxDigits };
2079: }
2080:
2081: public int estimateParsedLength() {
2082: return iMaxDigits;
2083: }
2084:
2085: public int parseInto(DateTimeParserBucket bucket, String text,
2086: int position) {
2087: DateTimeField field = iFieldType.getField(bucket
2088: .getChronology());
2089:
2090: int limit = Math.min(iMaxDigits, text.length() - position);
2091:
2092: long value = 0;
2093: long n = field.getDurationField().getUnitMillis() * 10;
2094: int length = 0;
2095: while (length < limit) {
2096: char c = text.charAt(position + length);
2097: if (c < '0' || c > '9') {
2098: break;
2099: }
2100: length++;
2101: long nn = n / 10;
2102: value += (c - '0') * nn;
2103: n = nn;
2104: }
2105:
2106: value /= 10;
2107:
2108: if (length == 0) {
2109: return ~position;
2110: }
2111:
2112: if (value > Integer.MAX_VALUE) {
2113: return ~position;
2114: }
2115:
2116: DateTimeField parseField = new PreciseDateTimeField(
2117: DateTimeFieldType.millisOfSecond(),
2118: MillisDurationField.INSTANCE, field
2119: .getDurationField());
2120:
2121: bucket.saveField(parseField, (int) value);
2122:
2123: return position + length;
2124: }
2125: }
2126:
2127: //-----------------------------------------------------------------------
2128: static class TimeZoneOffset implements DateTimePrinter,
2129: DateTimeParser {
2130:
2131: private final String iZeroOffsetText;
2132: private final boolean iShowSeparators;
2133: private final int iMinFields;
2134: private final int iMaxFields;
2135:
2136: TimeZoneOffset(String zeroOffsetText, boolean showSeparators,
2137: int minFields, int maxFields) {
2138: super ();
2139: iZeroOffsetText = zeroOffsetText;
2140: iShowSeparators = showSeparators;
2141: if (minFields <= 0 || maxFields < minFields) {
2142: throw new IllegalArgumentException();
2143: }
2144: if (minFields > 4) {
2145: minFields = 4;
2146: maxFields = 4;
2147: }
2148: iMinFields = minFields;
2149: iMaxFields = maxFields;
2150: }
2151:
2152: public int estimatePrintedLength() {
2153: int est = 1 + iMinFields << 1;
2154: if (iShowSeparators) {
2155: est += iMinFields - 1;
2156: }
2157: if (iZeroOffsetText != null
2158: && iZeroOffsetText.length() > est) {
2159: est = iZeroOffsetText.length();
2160: }
2161: return est;
2162: }
2163:
2164: public void printTo(StringBuffer buf, long instant,
2165: Chronology chrono, int displayOffset,
2166: DateTimeZone displayZone, Locale locale) {
2167: if (displayZone == null) {
2168: return; // no zone
2169: }
2170: if (displayOffset == 0 && iZeroOffsetText != null) {
2171: buf.append(iZeroOffsetText);
2172: return;
2173: }
2174: if (displayOffset >= 0) {
2175: buf.append('+');
2176: } else {
2177: buf.append('-');
2178: displayOffset = -displayOffset;
2179: }
2180:
2181: int hours = displayOffset
2182: / DateTimeConstants.MILLIS_PER_HOUR;
2183: FormatUtils.appendPaddedInteger(buf, hours, 2);
2184: if (iMaxFields == 1) {
2185: return;
2186: }
2187: displayOffset -= hours
2188: * (int) DateTimeConstants.MILLIS_PER_HOUR;
2189: if (displayOffset == 0 && iMinFields <= 1) {
2190: return;
2191: }
2192:
2193: int minutes = displayOffset
2194: / DateTimeConstants.MILLIS_PER_MINUTE;
2195: if (iShowSeparators) {
2196: buf.append(':');
2197: }
2198: FormatUtils.appendPaddedInteger(buf, minutes, 2);
2199: if (iMaxFields == 2) {
2200: return;
2201: }
2202: displayOffset -= minutes
2203: * DateTimeConstants.MILLIS_PER_MINUTE;
2204: if (displayOffset == 0 && iMinFields <= 2) {
2205: return;
2206: }
2207:
2208: int seconds = displayOffset
2209: / DateTimeConstants.MILLIS_PER_SECOND;
2210: if (iShowSeparators) {
2211: buf.append(':');
2212: }
2213: FormatUtils.appendPaddedInteger(buf, seconds, 2);
2214: if (iMaxFields == 3) {
2215: return;
2216: }
2217: displayOffset -= seconds
2218: * DateTimeConstants.MILLIS_PER_SECOND;
2219: if (displayOffset == 0 && iMinFields <= 3) {
2220: return;
2221: }
2222:
2223: if (iShowSeparators) {
2224: buf.append('.');
2225: }
2226: FormatUtils.appendPaddedInteger(buf, displayOffset, 3);
2227: }
2228:
2229: public void printTo(Writer out, long instant,
2230: Chronology chrono, int displayOffset,
2231: DateTimeZone displayZone, Locale locale)
2232: throws IOException {
2233: if (displayZone == null) {
2234: return; // no zone
2235: }
2236: if (displayOffset == 0 && iZeroOffsetText != null) {
2237: out.write(iZeroOffsetText);
2238: return;
2239: }
2240: if (displayOffset >= 0) {
2241: out.write('+');
2242: } else {
2243: out.write('-');
2244: displayOffset = -displayOffset;
2245: }
2246:
2247: int hours = displayOffset
2248: / DateTimeConstants.MILLIS_PER_HOUR;
2249: FormatUtils.writePaddedInteger(out, hours, 2);
2250: if (iMaxFields == 1) {
2251: return;
2252: }
2253: displayOffset -= hours
2254: * (int) DateTimeConstants.MILLIS_PER_HOUR;
2255: if (displayOffset == 0 && iMinFields == 1) {
2256: return;
2257: }
2258:
2259: int minutes = displayOffset
2260: / DateTimeConstants.MILLIS_PER_MINUTE;
2261: if (iShowSeparators) {
2262: out.write(':');
2263: }
2264: FormatUtils.writePaddedInteger(out, minutes, 2);
2265: if (iMaxFields == 2) {
2266: return;
2267: }
2268: displayOffset -= minutes
2269: * DateTimeConstants.MILLIS_PER_MINUTE;
2270: if (displayOffset == 0 && iMinFields == 2) {
2271: return;
2272: }
2273:
2274: int seconds = displayOffset
2275: / DateTimeConstants.MILLIS_PER_SECOND;
2276: if (iShowSeparators) {
2277: out.write(':');
2278: }
2279: FormatUtils.writePaddedInteger(out, seconds, 2);
2280: if (iMaxFields == 3) {
2281: return;
2282: }
2283: displayOffset -= seconds
2284: * DateTimeConstants.MILLIS_PER_SECOND;
2285: if (displayOffset == 0 && iMinFields == 3) {
2286: return;
2287: }
2288:
2289: if (iShowSeparators) {
2290: out.write('.');
2291: }
2292: FormatUtils.writePaddedInteger(out, displayOffset, 3);
2293: }
2294:
2295: public void printTo(StringBuffer buf, ReadablePartial partial,
2296: Locale locale) {
2297: // no zone info
2298: }
2299:
2300: public void printTo(Writer out, ReadablePartial partial,
2301: Locale locale) throws IOException {
2302: // no zone info
2303: }
2304:
2305: public int estimateParsedLength() {
2306: return estimatePrintedLength();
2307: }
2308:
2309: public int parseInto(DateTimeParserBucket bucket, String text,
2310: int position) {
2311: int limit = text.length() - position;
2312:
2313: zeroOffset: if (iZeroOffsetText != null) {
2314: if (iZeroOffsetText.length() == 0) {
2315: // Peek ahead, looking for sign character.
2316: if (limit > 0) {
2317: char c = text.charAt(position);
2318: if (c == '-' || c == '+') {
2319: break zeroOffset;
2320: }
2321: }
2322: bucket.setOffset(0);
2323: return position;
2324: }
2325: if (text.regionMatches(true, position, iZeroOffsetText,
2326: 0, iZeroOffsetText.length())) {
2327: bucket.setOffset(0);
2328: return position + iZeroOffsetText.length();
2329: }
2330: }
2331:
2332: // Format to expect is sign character followed by at least one digit.
2333:
2334: if (limit <= 1) {
2335: return ~position;
2336: }
2337:
2338: boolean negative;
2339: char c = text.charAt(position);
2340: if (c == '-') {
2341: negative = true;
2342: } else if (c == '+') {
2343: negative = false;
2344: } else {
2345: return ~position;
2346: }
2347:
2348: limit--;
2349: position++;
2350:
2351: // Format following sign is one of:
2352: //
2353: // hh
2354: // hhmm
2355: // hhmmss
2356: // hhmmssSSS
2357: // hh:mm
2358: // hh:mm:ss
2359: // hh:mm:ss.SSS
2360:
2361: // First parse hours.
2362:
2363: if (digitCount(text, position, 2) < 2) {
2364: // Need two digits for hour.
2365: return ~position;
2366: }
2367:
2368: int offset;
2369:
2370: int hours = FormatUtils.parseTwoDigits(text, position);
2371: if (hours > 23) {
2372: return ~position;
2373: }
2374: offset = hours * DateTimeConstants.MILLIS_PER_HOUR;
2375: limit -= 2;
2376: position += 2;
2377:
2378: parse: {
2379: // Need to decide now if separators are expected or parsing
2380: // stops at hour field.
2381:
2382: if (limit <= 0) {
2383: break parse;
2384: }
2385:
2386: boolean expectSeparators;
2387: c = text.charAt(position);
2388: if (c == ':') {
2389: expectSeparators = true;
2390: limit--;
2391: position++;
2392: } else if (c >= '0' && c <= '9') {
2393: expectSeparators = false;
2394: } else {
2395: break parse;
2396: }
2397:
2398: // Proceed to parse minutes.
2399:
2400: int count = digitCount(text, position, 2);
2401: if (count == 0 && !expectSeparators) {
2402: break parse;
2403: } else if (count < 2) {
2404: // Need two digits for minute.
2405: return ~position;
2406: }
2407:
2408: int minutes = FormatUtils
2409: .parseTwoDigits(text, position);
2410: if (minutes > 59) {
2411: return ~position;
2412: }
2413: offset += minutes * DateTimeConstants.MILLIS_PER_MINUTE;
2414: limit -= 2;
2415: position += 2;
2416:
2417: // Proceed to parse seconds.
2418:
2419: if (limit <= 0) {
2420: break parse;
2421: }
2422:
2423: if (expectSeparators) {
2424: if (text.charAt(position) != ':') {
2425: break parse;
2426: }
2427: limit--;
2428: position++;
2429: }
2430:
2431: count = digitCount(text, position, 2);
2432: if (count == 0 && !expectSeparators) {
2433: break parse;
2434: } else if (count < 2) {
2435: // Need two digits for second.
2436: return ~position;
2437: }
2438:
2439: int seconds = FormatUtils
2440: .parseTwoDigits(text, position);
2441: if (seconds > 59) {
2442: return ~position;
2443: }
2444: offset += seconds * DateTimeConstants.MILLIS_PER_SECOND;
2445: limit -= 2;
2446: position += 2;
2447:
2448: // Proceed to parse fraction of second.
2449:
2450: if (limit <= 0) {
2451: break parse;
2452: }
2453:
2454: if (expectSeparators) {
2455: if (text.charAt(position) != '.'
2456: && text.charAt(position) != ',') {
2457: break parse;
2458: }
2459: limit--;
2460: position++;
2461: }
2462:
2463: count = digitCount(text, position, 3);
2464: if (count == 0 && !expectSeparators) {
2465: break parse;
2466: } else if (count < 1) {
2467: // Need at least one digit for fraction of second.
2468: return ~position;
2469: }
2470:
2471: offset += (text.charAt(position++) - '0') * 100;
2472: if (count > 1) {
2473: offset += (text.charAt(position++) - '0') * 10;
2474: if (count > 2) {
2475: offset += text.charAt(position++) - '0';
2476: }
2477: }
2478: }
2479:
2480: bucket.setOffset(negative ? -offset : offset);
2481: return position;
2482: }
2483:
2484: /**
2485: * Returns actual amount of digits to parse, but no more than original
2486: * 'amount' parameter.
2487: */
2488: private int digitCount(String text, int position, int amount) {
2489: int limit = Math.min(text.length() - position, amount);
2490: amount = 0;
2491: for (; limit > 0; limit--) {
2492: char c = text.charAt(position + amount);
2493: if (c < '0' || c > '9') {
2494: break;
2495: }
2496: amount++;
2497: }
2498: return amount;
2499: }
2500: }
2501:
2502: //-----------------------------------------------------------------------
2503: static class TimeZoneName implements DateTimePrinter {
2504:
2505: static final int LONG_NAME = 0;
2506: static final int SHORT_NAME = 1;
2507: static final int ID = 2;
2508:
2509: private final int iType;
2510:
2511: TimeZoneName(int type) {
2512: super ();
2513: iType = type;
2514: }
2515:
2516: public int estimatePrintedLength() {
2517: return (iType == SHORT_NAME ? 4 : 20);
2518: }
2519:
2520: public void printTo(StringBuffer buf, long instant,
2521: Chronology chrono, int displayOffset,
2522: DateTimeZone displayZone, Locale locale) {
2523: buf.append(print(instant - displayOffset, displayZone,
2524: locale));
2525: }
2526:
2527: public void printTo(Writer out, long instant,
2528: Chronology chrono, int displayOffset,
2529: DateTimeZone displayZone, Locale locale)
2530: throws IOException {
2531: out.write(print(instant - displayOffset, displayZone,
2532: locale));
2533: }
2534:
2535: private String print(long instant, DateTimeZone displayZone,
2536: Locale locale) {
2537: if (displayZone == null) {
2538: return ""; // no zone
2539: }
2540: switch (iType) {
2541: case LONG_NAME:
2542: return displayZone.getName(instant, locale);
2543: case SHORT_NAME:
2544: return displayZone.getShortName(instant, locale);
2545: case ID:
2546: return displayZone.getID();
2547: }
2548: return "";
2549: }
2550:
2551: public void printTo(StringBuffer buf, ReadablePartial partial,
2552: Locale locale) {
2553: // no zone info
2554: }
2555:
2556: public void printTo(Writer out, ReadablePartial partial,
2557: Locale locale) throws IOException {
2558: // no zone info
2559: }
2560: }
2561:
2562: //-----------------------------------------------------------------------
2563: static class Composite implements DateTimePrinter, DateTimeParser {
2564:
2565: private final DateTimePrinter[] iPrinters;
2566: private final DateTimeParser[] iParsers;
2567:
2568: private final int iPrintedLengthEstimate;
2569: private final int iParsedLengthEstimate;
2570:
2571: Composite(List elementPairs) {
2572: super ();
2573:
2574: List printerList = new ArrayList();
2575: List parserList = new ArrayList();
2576:
2577: decompose(elementPairs, printerList, parserList);
2578:
2579: if (printerList.size() <= 0) {
2580: iPrinters = null;
2581: iPrintedLengthEstimate = 0;
2582: } else {
2583: int size = printerList.size();
2584: iPrinters = new DateTimePrinter[size];
2585: int printEst = 0;
2586: for (int i = 0; i < size; i++) {
2587: DateTimePrinter printer = (DateTimePrinter) printerList
2588: .get(i);
2589: printEst += printer.estimatePrintedLength();
2590: iPrinters[i] = printer;
2591: }
2592: iPrintedLengthEstimate = printEst;
2593: }
2594:
2595: if (parserList.size() <= 0) {
2596: iParsers = null;
2597: iParsedLengthEstimate = 0;
2598: } else {
2599: int size = parserList.size();
2600: iParsers = new DateTimeParser[size];
2601: int parseEst = 0;
2602: for (int i = 0; i < size; i++) {
2603: DateTimeParser parser = (DateTimeParser) parserList
2604: .get(i);
2605: parseEst += parser.estimateParsedLength();
2606: iParsers[i] = parser;
2607: }
2608: iParsedLengthEstimate = parseEst;
2609: }
2610: }
2611:
2612: public int estimatePrintedLength() {
2613: return iPrintedLengthEstimate;
2614: }
2615:
2616: public void printTo(StringBuffer buf, long instant,
2617: Chronology chrono, int displayOffset,
2618: DateTimeZone displayZone, Locale locale) {
2619: DateTimePrinter[] elements = iPrinters;
2620: if (elements == null) {
2621: throw new UnsupportedOperationException();
2622: }
2623:
2624: if (locale == null) {
2625: // Guard against default locale changing concurrently.
2626: locale = Locale.getDefault();
2627: }
2628:
2629: int len = elements.length;
2630: for (int i = 0; i < len; i++) {
2631: elements[i].printTo(buf, instant, chrono,
2632: displayOffset, displayZone, locale);
2633: }
2634: }
2635:
2636: public void printTo(Writer out, long instant,
2637: Chronology chrono, int displayOffset,
2638: DateTimeZone displayZone, Locale locale)
2639: throws IOException {
2640: DateTimePrinter[] elements = iPrinters;
2641: if (elements == null) {
2642: throw new UnsupportedOperationException();
2643: }
2644:
2645: if (locale == null) {
2646: // Guard against default locale changing concurrently.
2647: locale = Locale.getDefault();
2648: }
2649:
2650: int len = elements.length;
2651: for (int i = 0; i < len; i++) {
2652: elements[i].printTo(out, instant, chrono,
2653: displayOffset, displayZone, locale);
2654: }
2655: }
2656:
2657: public void printTo(StringBuffer buf, ReadablePartial partial,
2658: Locale locale) {
2659: DateTimePrinter[] elements = iPrinters;
2660: if (elements == null) {
2661: throw new UnsupportedOperationException();
2662: }
2663:
2664: if (locale == null) {
2665: // Guard against default locale changing concurrently.
2666: locale = Locale.getDefault();
2667: }
2668:
2669: int len = elements.length;
2670: for (int i = 0; i < len; i++) {
2671: elements[i].printTo(buf, partial, locale);
2672: }
2673: }
2674:
2675: public void printTo(Writer out, ReadablePartial partial,
2676: Locale locale) throws IOException {
2677: DateTimePrinter[] elements = iPrinters;
2678: if (elements == null) {
2679: throw new UnsupportedOperationException();
2680: }
2681:
2682: if (locale == null) {
2683: // Guard against default locale changing concurrently.
2684: locale = Locale.getDefault();
2685: }
2686:
2687: int len = elements.length;
2688: for (int i = 0; i < len; i++) {
2689: elements[i].printTo(out, partial, locale);
2690: }
2691: }
2692:
2693: public int estimateParsedLength() {
2694: return iParsedLengthEstimate;
2695: }
2696:
2697: public int parseInto(DateTimeParserBucket bucket, String text,
2698: int position) {
2699: DateTimeParser[] elements = iParsers;
2700: if (elements == null) {
2701: throw new UnsupportedOperationException();
2702: }
2703:
2704: int len = elements.length;
2705: for (int i = 0; i < len && position >= 0; i++) {
2706: position = elements[i]
2707: .parseInto(bucket, text, position);
2708: }
2709: return position;
2710: }
2711:
2712: boolean isPrinter() {
2713: return iPrinters != null;
2714: }
2715:
2716: boolean isParser() {
2717: return iParsers != null;
2718: }
2719:
2720: /**
2721: * Processes the element pairs, putting results into the given printer
2722: * and parser lists.
2723: */
2724: private void decompose(List elementPairs, List printerList,
2725: List parserList) {
2726: int size = elementPairs.size();
2727: for (int i = 0; i < size; i += 2) {
2728: Object element = elementPairs.get(i);
2729: if (element instanceof DateTimePrinter) {
2730: if (element instanceof Composite) {
2731: addArrayToList(printerList,
2732: ((Composite) element).iPrinters);
2733: } else {
2734: printerList.add(element);
2735: }
2736: }
2737:
2738: element = elementPairs.get(i + 1);
2739: if (element instanceof DateTimeParser) {
2740: if (element instanceof Composite) {
2741: addArrayToList(parserList,
2742: ((Composite) element).iParsers);
2743: } else {
2744: parserList.add(element);
2745: }
2746: }
2747: }
2748: }
2749:
2750: private void addArrayToList(List list, Object[] array) {
2751: if (array != null) {
2752: for (int i = 0; i < array.length; i++) {
2753: list.add(array[i]);
2754: }
2755: }
2756: }
2757: }
2758:
2759: //-----------------------------------------------------------------------
2760: static class MatchingParser implements DateTimeParser {
2761:
2762: private final DateTimeParser[] iParsers;
2763: private final int iParsedLengthEstimate;
2764:
2765: MatchingParser(DateTimeParser[] parsers) {
2766: super ();
2767: iParsers = parsers;
2768: int est = 0;
2769: for (int i = parsers.length; --i >= 0;) {
2770: DateTimeParser parser = parsers[i];
2771: if (parser != null) {
2772: int len = parser.estimateParsedLength();
2773: if (len > est) {
2774: est = len;
2775: }
2776: }
2777: }
2778: iParsedLengthEstimate = est;
2779: }
2780:
2781: public int estimateParsedLength() {
2782: return iParsedLengthEstimate;
2783: }
2784:
2785: public int parseInto(DateTimeParserBucket bucket, String text,
2786: int position) {
2787: DateTimeParser[] parsers = iParsers;
2788: int length = parsers.length;
2789:
2790: final Object originalState = bucket.saveState();
2791: boolean isOptional = false;
2792:
2793: int bestValidPos = position;
2794: Object bestValidState = null;
2795:
2796: int bestInvalidPos = position;
2797:
2798: for (int i = 0; i < length; i++) {
2799: DateTimeParser parser = parsers[i];
2800: if (parser == null) {
2801: // The empty parser wins only if nothing is better.
2802: if (bestValidPos <= position) {
2803: return position;
2804: }
2805: isOptional = true;
2806: break;
2807: }
2808: int parsePos = parser.parseInto(bucket, text, position);
2809: if (parsePos >= position) {
2810: if (parsePos > bestValidPos) {
2811: if (parsePos >= text.length()
2812: || (i + 1) >= length
2813: || parsers[i + 1] == null) {
2814:
2815: // Completely parsed text or no more parsers to
2816: // check. Skip the rest.
2817: return parsePos;
2818: }
2819: bestValidPos = parsePos;
2820: bestValidState = bucket.saveState();
2821: }
2822: } else {
2823: if (parsePos < 0) {
2824: parsePos = ~parsePos;
2825: if (parsePos > bestInvalidPos) {
2826: bestInvalidPos = parsePos;
2827: }
2828: }
2829: }
2830: bucket.restoreState(originalState);
2831: }
2832:
2833: if (bestValidPos > position
2834: || (bestValidPos == position && isOptional)) {
2835: // Restore the state to the best valid parse.
2836: if (bestValidState != null) {
2837: bucket.restoreState(bestValidState);
2838: }
2839: return bestValidPos;
2840: }
2841:
2842: return ~bestInvalidPos;
2843: }
2844: }
2845:
2846: }
|