|
Java example source code file (DateTimeFormatterBuilder.java)
The DateTimeFormatterBuilder.java Java example source code/* * Copyright 2001-2014 Stephen Colebourne * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.joda.time.format; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import org.joda.time.Chronology; import org.joda.time.DateTimeConstants; import org.joda.time.DateTimeField; import org.joda.time.DateTimeFieldType; import org.joda.time.DateTimeUtils; import org.joda.time.DateTimeZone; import org.joda.time.MutableDateTime; import org.joda.time.MutableDateTime.Property; import org.joda.time.ReadablePartial; import org.joda.time.field.MillisDurationField; import org.joda.time.field.PreciseDateTimeField; /** * Factory that creates complex instances of DateTimeFormatter via method calls. * <p> * Datetime formatting is performed by the {@link DateTimeFormatter} class. * Three classes provide factory methods to create formatters, and this is one. * The others are {@link DateTimeFormat} and {@link ISODateTimeFormat}. * <p> * DateTimeFormatterBuilder is used for constructing formatters which are then * used to print or parse. The formatters are built by appending specific fields * or other formatters to an instance of this builder. * <p> * For example, a formatter that prints month and year, like "January 1970", * can be constructed as follows: * <p> * <pre> * DateTimeFormatter monthAndYear = new DateTimeFormatterBuilder() * .appendMonthOfYearText() * .appendLiteral(' ') * .appendYear(4, 4) * .toFormatter(); * </pre> * <p> * DateTimeFormatterBuilder itself is mutable and not thread-safe, but the * formatters that it builds are thread-safe and immutable. * * @author Brian S O'Neill * @author Stephen Colebourne * @author Fredrik Borgh * @since 1.0 * @see DateTimeFormat * @see ISODateTimeFormat */ public class DateTimeFormatterBuilder { /** Array of printers and parsers (alternating). */ private ArrayList<Object> iElementPairs; /** Cache of the last returned formatter. */ private Object iFormatter; //----------------------------------------------------------------------- /** * Creates a DateTimeFormatterBuilder. */ public DateTimeFormatterBuilder() { super(); iElementPairs = new ArrayList<Object>(); } //----------------------------------------------------------------------- /** * Constructs a DateTimeFormatter using all the appended elements. * <p> * This is the main method used by applications at the end of the build * process to create a usable formatter. * <p> * Subsequent changes to this builder do not affect the returned formatter. * <p> * The returned formatter may not support both printing and parsing. * The methods {@link DateTimeFormatter#isPrinter()} and * {@link DateTimeFormatter#isParser()} will help you determine the state * of the formatter. * * @throws UnsupportedOperationException if neither printing nor parsing is supported */ public DateTimeFormatter toFormatter() { Object f = getFormatter(); InternalPrinter printer = null; if (isPrinter(f)) { printer = (InternalPrinter) f; } InternalParser parser = null; if (isParser(f)) { parser = (InternalParser) f; } if (printer != null || parser != null) { return new DateTimeFormatter(printer, parser); } throw new UnsupportedOperationException("Both printing and parsing not supported"); } /** * Internal method to create a DateTimePrinter instance using all the * appended elements. * <p> * Most applications will not use this method. * If you want a printer in an application, call {@link #toFormatter()} * and just use the printing API. * <p> * Subsequent changes to this builder do not affect the returned printer. * * @throws UnsupportedOperationException if printing is not supported */ public DateTimePrinter toPrinter() { Object f = getFormatter(); if (isPrinter(f)) { InternalPrinter ip = (InternalPrinter) f; return InternalPrinterDateTimePrinter.of(ip); } throw new UnsupportedOperationException("Printing is not supported"); } /** * Internal method to create a DateTimeParser instance using all the * appended elements. * <p> * Most applications will not use this method. * If you want a parser in an application, call {@link #toFormatter()} * and just use the parsing API. * <p> * Subsequent changes to this builder do not affect the returned parser. * * @throws UnsupportedOperationException if parsing is not supported */ public DateTimeParser toParser() { Object f = getFormatter(); if (isParser(f)) { InternalParser ip = (InternalParser) f; return InternalParserDateTimeParser.of(ip); } throw new UnsupportedOperationException("Parsing is not supported"); } //----------------------------------------------------------------------- /** * Returns true if toFormatter can be called without throwing an * UnsupportedOperationException. * * @return true if a formatter can be built */ public boolean canBuildFormatter() { return isFormatter(getFormatter()); } /** * Returns true if toPrinter can be called without throwing an * UnsupportedOperationException. * * @return true if a printer can be built */ public boolean canBuildPrinter() { return isPrinter(getFormatter()); } /** * Returns true if toParser can be called without throwing an * UnsupportedOperationException. * * @return true if a parser can be built */ public boolean canBuildParser() { return isParser(getFormatter()); } //----------------------------------------------------------------------- /** * Clears out all the appended elements, allowing this builder to be * reused. */ public void clear() { iFormatter = null; iElementPairs.clear(); } //----------------------------------------------------------------------- /** * Appends another formatter. * <p> * This extracts the underlying printer and parser and appends them * The printer and parser interfaces are the low-level part of the formatting API. * Normally, instances are extracted from another formatter. * Note however that any formatter specific information, such as the locale, * time-zone, chronology, offset parsing or pivot/default year, will not be * extracted by this method. * * @param formatter the formatter to add * @return this DateTimeFormatterBuilder, for chaining * @throws IllegalArgumentException if formatter is null or of an invalid type */ public DateTimeFormatterBuilder append(DateTimeFormatter formatter) { if (formatter == null) { throw new IllegalArgumentException("No formatter supplied"); } return append0(formatter.getPrinter0(), formatter.getParser0()); } /** * Appends just a printer. With no matching parser, a parser cannot be * built from this DateTimeFormatterBuilder. * <p> * The printer interface is part of the low-level part of the formatting API. * Normally, instances are extracted from another formatter. * Note however that any formatter specific information, such as the locale, * time-zone, chronology, offset parsing or pivot/default year, will not be * extracted by this method. * * @param printer the printer to add, not null * @return this DateTimeFormatterBuilder, for chaining * @throws IllegalArgumentException if printer is null or of an invalid type */ public DateTimeFormatterBuilder append(DateTimePrinter printer) { checkPrinter(printer); return append0(DateTimePrinterInternalPrinter.of(printer), null); } /** * Appends just a parser. With no matching printer, a printer cannot be * built from this builder. * <p> * The parser interface is part of the low-level part of the formatting API. * Normally, instances are extracted from another formatter. * Note however that any formatter specific information, such as the locale, * time-zone, chronology, offset parsing or pivot/default year, will not be * extracted by this method. * * @param parser the parser to add, not null * @return this DateTimeFormatterBuilder, for chaining * @throws IllegalArgumentException if parser is null or of an invalid type */ public DateTimeFormatterBuilder append(DateTimeParser parser) { checkParser(parser); return append0(null, DateTimeParserInternalParser.of(parser)); } /** * Appends a printer/parser pair. * <p> * The printer and parser interfaces are the low-level part of the formatting API. * Normally, instances are extracted from another formatter. * Note however that any formatter specific information, such as the locale, * time-zone, chronology, offset parsing or pivot/default year, will not be * extracted by this method. * * @param printer the printer to add, not null * @param parser the parser to add, not null * @return this DateTimeFormatterBuilder, for chaining * @throws IllegalArgumentException if printer or parser is null or of an invalid type */ public DateTimeFormatterBuilder append(DateTimePrinter printer, DateTimeParser parser) { checkPrinter(printer); checkParser(parser); return append0(DateTimePrinterInternalPrinter.of(printer), DateTimeParserInternalParser.of(parser)); } /** * Appends a printer and a set of matching parsers. When parsing, the first * parser in the list is selected for parsing. If it fails, the next is * chosen, and so on. If none of these parsers succeeds, then the failed * position of the parser that made the greatest progress is returned. * <p> * Only the printer is optional. In addition, it is illegal for any but the * last of the parser array elements to be null. If the last element is * null, this represents the empty parser. The presence of an empty parser * indicates that the entire array of parse formats is optional. * <p> * The printer and parser interfaces are the low-level part of the formatting API. * Normally, instances are extracted from another formatter. * Note however that any formatter specific information, such as the locale, * time-zone, chronology, offset parsing or pivot/default year, will not be * extracted by this method. * * @param printer the printer to add * @param parsers the parsers to add * @return this DateTimeFormatterBuilder, for chaining * @throws IllegalArgumentException if any printer or parser is of an invalid type * @throws IllegalArgumentException if any parser element but the last is null */ public DateTimeFormatterBuilder append(DateTimePrinter printer, DateTimeParser[] parsers) { if (printer != null) { checkPrinter(printer); } if (parsers == null) { throw new IllegalArgumentException("No parsers supplied"); } int length = parsers.length; if (length == 1) { if (parsers[0] == null) { throw new IllegalArgumentException("No parser supplied"); } return append0(DateTimePrinterInternalPrinter.of(printer), DateTimeParserInternalParser.of(parsers[0])); } InternalParser[] copyOfParsers = new InternalParser[length]; int i; for (i = 0; i < length - 1; i++) { if ((copyOfParsers[i] = DateTimeParserInternalParser.of(parsers[i])) == null) { throw new IllegalArgumentException("Incomplete parser array"); } } copyOfParsers[i] = DateTimeParserInternalParser.of(parsers[i]); return append0(DateTimePrinterInternalPrinter.of(printer), new MatchingParser(copyOfParsers)); } /** * Appends just a parser element which is optional. With no matching * printer, a printer cannot be built from this DateTimeFormatterBuilder. * <p> * The parser interface is part of the low-level part of the formatting API. * Normally, instances are extracted from another formatter. * Note however that any formatter specific information, such as the locale, * time-zone, chronology, offset parsing or pivot/default year, will not be * extracted by this method. * * @return this DateTimeFormatterBuilder, for chaining * @throws IllegalArgumentException if parser is null or of an invalid type */ public DateTimeFormatterBuilder appendOptional(DateTimeParser parser) { checkParser(parser); InternalParser[] parsers = new InternalParser[] {DateTimeParserInternalParser.of(parser), null}; return append0(null, new MatchingParser(parsers)); } //----------------------------------------------------------------------- /** * Checks if the parser is non null and a provider. * * @param parser the parser to check */ private void checkParser(DateTimeParser parser) { if (parser == null) { throw new IllegalArgumentException("No parser supplied"); } } /** * Checks if the printer is non null and a provider. * * @param printer the printer to check */ private void checkPrinter(DateTimePrinter printer) { if (printer == null) { throw new IllegalArgumentException("No printer supplied"); } } private DateTimeFormatterBuilder append0(Object element) { iFormatter = null; // Add the element as both a printer and parser. iElementPairs.add(element); iElementPairs.add(element); return this; } private DateTimeFormatterBuilder append0( InternalPrinter printer, InternalParser parser) { iFormatter = null; iElementPairs.add(printer); iElementPairs.add(parser); return this; } //----------------------------------------------------------------------- /** * Instructs the printer to emit a specific character, and the parser to * expect it. The parser is case-insensitive. * * @return this DateTimeFormatterBuilder, for chaining */ public DateTimeFormatterBuilder appendLiteral(char c) { return append0(new CharacterLiteral(c)); } /** * Instructs the printer to emit specific text, and the parser to expect * it. The parser is case-insensitive. * * @return this DateTimeFormatterBuilder, for chaining * @throws IllegalArgumentException if text is null */ public DateTimeFormatterBuilder appendLiteral(String text) { if (text == null) { throw new IllegalArgumentException("Literal must not be null"); } switch (text.length()) { case 0: return this; case 1: return append0(new CharacterLiteral(text.charAt(0))); default: return append0(new StringLiteral(text)); } } /** * Instructs the printer to emit a field value as a decimal number, and the * parser to expect an unsigned decimal number. * * @param fieldType type of field to append * @param minDigits minimum number of digits to <i>print * @param maxDigits maximum number of digits to <i>parse, or the estimated * maximum number of digits to print * @return this DateTimeFormatterBuilder, for chaining * @throws IllegalArgumentException if field type is null */ public DateTimeFormatterBuilder appendDecimal( DateTimeFieldType fieldType, int minDigits, int maxDigits) { if (fieldType == null) { throw new IllegalArgumentException("Field type must not be null"); } if (maxDigits < minDigits) { maxDigits = minDigits; } if (minDigits < 0 || maxDigits <= 0) { throw new IllegalArgumentException(); } if (minDigits <= 1) { return append0(new UnpaddedNumber(fieldType, maxDigits, false)); } else { return append0(new PaddedNumber(fieldType, maxDigits, false, minDigits)); } } /** * Instructs the printer to emit a field value as a fixed-width decimal * number (smaller numbers will be left-padded with zeros), and the parser * to expect an unsigned decimal number with the same fixed width. * * @param fieldType type of field to append * @param numDigits the exact number of digits to parse or print, except if * printed value requires more digits * @return this DateTimeFormatterBuilder, for chaining * @throws IllegalArgumentException if field type is null or if <code>numDigits <= 0 * @since 1.5 */ public DateTimeFormatterBuilder appendFixedDecimal( DateTimeFieldType fieldType, int numDigits) { if (fieldType == null) { throw new IllegalArgumentException("Field type must not be null"); } if (numDigits <= 0) { throw new IllegalArgumentException("Illegal number of digits: " + numDigits); } return append0(new FixedNumber(fieldType, numDigits, false)); } /** * Instructs the printer to emit a field value as a decimal number, and the * parser to expect a signed decimal number. * * @param fieldType type of field to append * @param minDigits minimum number of digits to <i>print * @param maxDigits maximum number of digits to <i>parse, or the estimated * maximum number of digits to print * @return this DateTimeFormatterBuilder, for chaining * @throws IllegalArgumentException if field type is null */ public DateTimeFormatterBuilder appendSignedDecimal( DateTimeFieldType fieldType, int minDigits, int maxDigits) { if (fieldType == null) { throw new IllegalArgumentException("Field type must not be null"); } if (maxDigits < minDigits) { maxDigits = minDigits; } if (minDigits < 0 || maxDigits <= 0) { throw new IllegalArgumentException(); } if (minDigits <= 1) { return append0(new UnpaddedNumber(fieldType, maxDigits, true)); } else { return append0(new PaddedNumber(fieldType, maxDigits, true, minDigits)); } } /** * Instructs the printer to emit a field value as a fixed-width decimal * number (smaller numbers will be left-padded with zeros), and the parser * to expect an signed decimal number with the same fixed width. * * @param fieldType type of field to append * @param numDigits the exact number of digits to parse or print, except if * printed value requires more digits * @return this DateTimeFormatterBuilder, for chaining * @throws IllegalArgumentException if field type is null or if <code>numDigits <= 0 * @since 1.5 */ public DateTimeFormatterBuilder appendFixedSignedDecimal( DateTimeFieldType fieldType, int numDigits) { if (fieldType == null) { throw new IllegalArgumentException("Field type must not be null"); } if (numDigits <= 0) { throw new IllegalArgumentException("Illegal number of digits: " + numDigits); } return append0(new FixedNumber(fieldType, numDigits, true)); } /** * Instructs the printer to emit a field value as text, and the * parser to expect text. * * @param fieldType type of field to append * @return this DateTimeFormatterBuilder, for chaining * @throws IllegalArgumentException if field type is null */ public DateTimeFormatterBuilder appendText(DateTimeFieldType fieldType) { if (fieldType == null) { throw new IllegalArgumentException("Field type must not be null"); } return append0(new TextField(fieldType, false)); } /** * Instructs the printer to emit a field value as short text, and the * parser to expect text. * * @param fieldType type of field to append * @return this DateTimeFormatterBuilder, for chaining * @throws IllegalArgumentException if field type is null */ public DateTimeFormatterBuilder appendShortText(DateTimeFieldType fieldType) { if (fieldType == null) { throw new IllegalArgumentException("Field type must not be null"); } return append0(new TextField(fieldType, true)); } /** * Instructs the printer to emit a remainder of time as a decimal fraction, * without decimal point. For example, if the field is specified as * minuteOfHour and the time is 12:30:45, the value printed is 75. A * decimal point is implied, so the fraction is 0.75, or three-quarters of * a minute. * * @param fieldType type of field to append * @param minDigits minimum number of digits to print. * @param maxDigits maximum number of digits to print or parse. * @return this DateTimeFormatterBuilder, for chaining * @throws IllegalArgumentException if field type is null */ public DateTimeFormatterBuilder appendFraction( DateTimeFieldType fieldType, int minDigits, int maxDigits) { if (fieldType == null) { throw new IllegalArgumentException("Field type must not be null"); } if (maxDigits < minDigits) { maxDigits = minDigits; } if (minDigits < 0 || maxDigits <= 0) { throw new IllegalArgumentException(); } return append0(new Fraction(fieldType, minDigits, maxDigits)); } /** * Appends the print/parse of a fractional second. * <p> * This reliably handles the case where fractional digits are being handled * beyond a visible decimal point. The digits parsed will always be treated * as the most significant (numerically largest) digits. * Thus '23' will be parsed as 230 milliseconds. * Contrast this behaviour to {@link #appendMillisOfSecond}. * This method does not print or parse the decimal point itself. * * @param minDigits minimum number of digits to print * @param maxDigits maximum number of digits to print or parse * @return this DateTimeFormatterBuilder, for chaining */ public DateTimeFormatterBuilder appendFractionOfSecond(int minDigits, int maxDigits) { return appendFraction(DateTimeFieldType.secondOfDay(), minDigits, maxDigits); } /** * Appends the print/parse of a fractional minute. * <p> * This reliably handles the case where fractional digits are being handled * beyond a visible decimal point. The digits parsed will always be treated * as the most significant (numerically largest) digits. * Thus '23' will be parsed as 0.23 minutes (converted to milliseconds). * This method does not print or parse the decimal point itself. * * @param minDigits minimum number of digits to print * @param maxDigits maximum number of digits to print or parse * @return this DateTimeFormatterBuilder, for chaining */ public DateTimeFormatterBuilder appendFractionOfMinute(int minDigits, int maxDigits) { return appendFraction(DateTimeFieldType.minuteOfDay(), minDigits, maxDigits); } /** * Appends the print/parse of a fractional hour. * <p> * This reliably handles the case where fractional digits are being handled * beyond a visible decimal point. The digits parsed will always be treated * as the most significant (numerically largest) digits. * Thus '23' will be parsed as 0.23 hours (converted to milliseconds). * This method does not print or parse the decimal point itself. * * @param minDigits minimum number of digits to print * @param maxDigits maximum number of digits to print or parse * @return this DateTimeFormatterBuilder, for chaining */ public DateTimeFormatterBuilder appendFractionOfHour(int minDigits, int maxDigits) { return appendFraction(DateTimeFieldType.hourOfDay(), minDigits, maxDigits); } /** * Appends the print/parse of a fractional day. * <p> * This reliably handles the case where fractional digits are being handled * beyond a visible decimal point. The digits parsed will always be treated * as the most significant (numerically largest) digits. * Thus '23' will be parsed as 0.23 days (converted to milliseconds). * This method does not print or parse the decimal point itself. * * @param minDigits minimum number of digits to print * @param maxDigits maximum number of digits to print or parse * @return this DateTimeFormatterBuilder, for chaining */ public DateTimeFormatterBuilder appendFractionOfDay(int minDigits, int maxDigits) { return appendFraction(DateTimeFieldType.dayOfYear(), minDigits, maxDigits); } /** * Instructs the printer to emit a numeric millisOfSecond field. * <p> * This method will append a field that prints a three digit value. * During parsing the value that is parsed is assumed to be three digits. * If less than three digits are present then they will be counted as the * smallest parts of the millisecond. This is probably not what you want * if you are using the field as a fraction. Instead, a fractional * millisecond should be produced using {@link #appendFractionOfSecond}. * * @param minDigits minimum number of digits to print * @return this DateTimeFormatterBuilder, for chaining */ public DateTimeFormatterBuilder appendMillisOfSecond(int minDigits) { return appendDecimal(DateTimeFieldType.millisOfSecond(), minDigits, 3); } /** * Instructs the printer to emit a numeric millisOfDay field. * * @param minDigits minimum number of digits to print * @return this DateTimeFormatterBuilder, for chaining */ public DateTimeFormatterBuilder appendMillisOfDay(int minDigits) { return appendDecimal(DateTimeFieldType.millisOfDay(), minDigits, 8); } /** * Instructs the printer to emit a numeric secondOfMinute field. * * @param minDigits minimum number of digits to print * @return this DateTimeFormatterBuilder, for chaining */ public DateTimeFormatterBuilder appendSecondOfMinute(int minDigits) { return appendDecimal(DateTimeFieldType.secondOfMinute(), minDigits, 2); } /** * Instructs the printer to emit a numeric secondOfDay field. * * @param minDigits minimum number of digits to print * @return this DateTimeFormatterBuilder, for chaining */ public DateTimeFormatterBuilder appendSecondOfDay(int minDigits) { return appendDecimal(DateTimeFieldType.secondOfDay(), minDigits, 5); } /** * Instructs the printer to emit a numeric minuteOfHour field. * * @param minDigits minimum number of digits to print * @return this DateTimeFormatterBuilder, for chaining */ public DateTimeFormatterBuilder appendMinuteOfHour(int minDigits) { return appendDecimal(DateTimeFieldType.minuteOfHour(), minDigits, 2); } /** * Instructs the printer to emit a numeric minuteOfDay field. * * @param minDigits minimum number of digits to print * @return this DateTimeFormatterBuilder, for chaining */ public DateTimeFormatterBuilder appendMinuteOfDay(int minDigits) { return appendDecimal(DateTimeFieldType.minuteOfDay(), minDigits, 4); } /** * Instructs the printer to emit a numeric hourOfDay field. * * @param minDigits minimum number of digits to print * @return this DateTimeFormatterBuilder, for chaining */ public DateTimeFormatterBuilder appendHourOfDay(int minDigits) { return appendDecimal(DateTimeFieldType.hourOfDay(), minDigits, 2); } /** * Instructs the printer to emit a numeric clockhourOfDay field. * * @param minDigits minimum number of digits to print * @return this DateTimeFormatterBuilder, for chaining */ public DateTimeFormatterBuilder appendClockhourOfDay(int minDigits) { return appendDecimal(DateTimeFieldType.clockhourOfDay(), minDigits, 2); } /** * Instructs the printer to emit a numeric hourOfHalfday field. * * @param minDigits minimum number of digits to print * @return this DateTimeFormatterBuilder, for chaining */ public DateTimeFormatterBuilder appendHourOfHalfday(int minDigits) { return appendDecimal(DateTimeFieldType.hourOfHalfday(), minDigits, 2); } /** * Instructs the printer to emit a numeric clockhourOfHalfday field. * * @param minDigits minimum number of digits to print * @return this DateTimeFormatterBuilder, for chaining */ public DateTimeFormatterBuilder appendClockhourOfHalfday(int minDigits) { return appendDecimal(DateTimeFieldType.clockhourOfHalfday(), minDigits, 2); } /** * Instructs the printer to emit a numeric dayOfWeek field. * * @param minDigits minimum number of digits to print * @return this DateTimeFormatterBuilder, for chaining */ public DateTimeFormatterBuilder appendDayOfWeek(int minDigits) { return appendDecimal(DateTimeFieldType.dayOfWeek(), minDigits, 1); } /** * Instructs the printer to emit a numeric dayOfMonth field. * * @param minDigits minimum number of digits to print * @return this DateTimeFormatterBuilder, for chaining */ public DateTimeFormatterBuilder appendDayOfMonth(int minDigits) { return appendDecimal(DateTimeFieldType.dayOfMonth(), minDigits, 2); } /** * Instructs the printer to emit a numeric dayOfYear field. * * @param minDigits minimum number of digits to print * @return this DateTimeFormatterBuilder, for chaining */ public DateTimeFormatterBuilder appendDayOfYear(int minDigits) { return appendDecimal(DateTimeFieldType.dayOfYear(), minDigits, 3); } /** * Instructs the printer to emit a numeric weekOfWeekyear field. * * @param minDigits minimum number of digits to print * @return this DateTimeFormatterBuilder, for chaining */ public DateTimeFormatterBuilder appendWeekOfWeekyear(int minDigits) { return appendDecimal(DateTimeFieldType.weekOfWeekyear(), minDigits, 2); } /** * Instructs the printer to emit a numeric weekyear field. * * @param minDigits minimum number of digits to <i>print * @param maxDigits maximum number of digits to <i>parse, or the estimated * maximum number of digits to print * @return this DateTimeFormatterBuilder, for chaining */ public DateTimeFormatterBuilder appendWeekyear(int minDigits, int maxDigits) { return appendSignedDecimal(DateTimeFieldType.weekyear(), minDigits, maxDigits); } /** * Instructs the printer to emit a numeric monthOfYear field. * * @param minDigits minimum number of digits to print * @return this DateTimeFormatterBuilder, for chaining */ public DateTimeFormatterBuilder appendMonthOfYear(int minDigits) { return appendDecimal(DateTimeFieldType.monthOfYear(), minDigits, 2); } /** * Instructs the printer to emit a numeric year field. * * @param minDigits minimum number of digits to <i>print * @param maxDigits maximum number of digits to <i>parse, or the estimated * maximum number of digits to print * @return this DateTimeFormatterBuilder, for chaining */ public DateTimeFormatterBuilder appendYear(int minDigits, int maxDigits) { return appendSignedDecimal(DateTimeFieldType.year(), minDigits, maxDigits); } /** * Instructs the printer to emit a numeric year field which always prints * and parses two digits. A pivot year is used during parsing to determine * the range of supported years as <code>(pivot - 50) .. (pivot + 49). * * <pre> * pivot supported range 00 is 20 is 40 is 60 is 80 is * --------------------------------------------------------------- * 1950 1900..1999 1900 1920 1940 1960 1980 * 1975 1925..2024 2000 2020 1940 1960 1980 * 2000 1950..2049 2000 2020 2040 1960 1980 * 2025 1975..2074 2000 2020 2040 2060 1980 * 2050 2000..2099 2000 2020 2040 2060 2080 * </pre> * * @param pivot pivot year to use when parsing * @return this DateTimeFormatterBuilder, for chaining */ public DateTimeFormatterBuilder appendTwoDigitYear(int pivot) { return appendTwoDigitYear(pivot, false); } /** * Instructs the printer to emit a numeric year field which always prints * two digits. A pivot year is used during parsing to determine the range * of supported years as <code>(pivot - 50) .. (pivot + 49). If * parse is instructed to be lenient and the digit count is not two, it is * treated as an absolute year. With lenient parsing, specifying a positive * or negative sign before the year also makes it absolute. * * @param pivot pivot year to use when parsing * @param lenientParse when true, if digit count is not two, it is treated * as an absolute year * @return this DateTimeFormatterBuilder, for chaining * @since 1.1 */ public DateTimeFormatterBuilder appendTwoDigitYear(int pivot, boolean lenientParse) { return append0(new TwoDigitYear(DateTimeFieldType.year(), pivot, lenientParse)); } /** * Instructs the printer to emit a numeric weekyear field which always prints * and parses two digits. A pivot year is used during parsing to determine * the range of supported years as <code>(pivot - 50) .. (pivot + 49). * * <pre> * pivot supported range 00 is 20 is 40 is 60 is 80 is * --------------------------------------------------------------- * 1950 1900..1999 1900 1920 1940 1960 1980 * 1975 1925..2024 2000 2020 1940 1960 1980 * 2000 1950..2049 2000 2020 2040 1960 1980 * 2025 1975..2074 2000 2020 2040 2060 1980 * 2050 2000..2099 2000 2020 2040 2060 2080 * </pre> * * @param pivot pivot weekyear to use when parsing * @return this DateTimeFormatterBuilder, for chaining */ public DateTimeFormatterBuilder appendTwoDigitWeekyear(int pivot) { return appendTwoDigitWeekyear(pivot, false); } /** * Instructs the printer to emit a numeric weekyear field which always prints * two digits. A pivot year is used during parsing to determine the range * of supported years as <code>(pivot - 50) .. (pivot + 49). If * parse is instructed to be lenient and the digit count is not two, it is * treated as an absolute weekyear. With lenient parsing, specifying a positive * or negative sign before the weekyear also makes it absolute. * * @param pivot pivot weekyear to use when parsing * @param lenientParse when true, if digit count is not two, it is treated * as an absolute weekyear * @return this DateTimeFormatterBuilder, for chaining * @since 1.1 */ public DateTimeFormatterBuilder appendTwoDigitWeekyear(int pivot, boolean lenientParse) { return append0(new TwoDigitYear(DateTimeFieldType.weekyear(), pivot, lenientParse)); } /** * Instructs the printer to emit a numeric yearOfEra field. * * @param minDigits minimum number of digits to <i>print * @param maxDigits maximum number of digits to <i>parse, or the estimated * maximum number of digits to print * @return this DateTimeFormatterBuilder, for chaining */ public DateTimeFormatterBuilder appendYearOfEra(int minDigits, int maxDigits) { return appendDecimal(DateTimeFieldType.yearOfEra(), minDigits, maxDigits); } /** * Instructs the printer to emit a numeric year of century field. * * @param minDigits minimum number of digits to print * @param maxDigits maximum number of digits to <i>parse, or the estimated * maximum number of digits to print * @return this DateTimeFormatterBuilder, for chaining */ public DateTimeFormatterBuilder appendYearOfCentury(int minDigits, int maxDigits) { return appendDecimal(DateTimeFieldType.yearOfCentury(), minDigits, maxDigits); } /** * Instructs the printer to emit a numeric century of era field. * * @param minDigits minimum number of digits to print * @param maxDigits maximum number of digits to <i>parse, or the estimated * maximum number of digits to print * @return this DateTimeFormatterBuilder, for chaining */ public DateTimeFormatterBuilder appendCenturyOfEra(int minDigits, int maxDigits) { return appendSignedDecimal(DateTimeFieldType.centuryOfEra(), minDigits, maxDigits); } /** * Instructs the printer to emit a locale-specific AM/PM text, and the * parser to expect it. The parser is case-insensitive. * * @return this DateTimeFormatterBuilder, for chaining */ public DateTimeFormatterBuilder appendHalfdayOfDayText() { return appendText(DateTimeFieldType.halfdayOfDay()); } /** * Instructs the printer to emit a locale-specific dayOfWeek text. The * parser will accept a long or short dayOfWeek text, case-insensitive. * * @return this DateTimeFormatterBuilder, for chaining */ public DateTimeFormatterBuilder appendDayOfWeekText() { return appendText(DateTimeFieldType.dayOfWeek()); } /** * Instructs the printer to emit a short locale-specific dayOfWeek * text. The parser will accept a long or short dayOfWeek text, * case-insensitive. * * @return this DateTimeFormatterBuilder, for chaining */ public DateTimeFormatterBuilder appendDayOfWeekShortText() { return appendShortText(DateTimeFieldType.dayOfWeek()); } /** * Instructs the printer to emit a short locale-specific monthOfYear * text. The parser will accept a long or short monthOfYear text, * case-insensitive. * * @return this DateTimeFormatterBuilder, for chaining */ public DateTimeFormatterBuilder appendMonthOfYearText() { return appendText(DateTimeFieldType.monthOfYear()); } /** * Instructs the printer to emit a locale-specific monthOfYear text. The * parser will accept a long or short monthOfYear text, case-insensitive. * * @return this DateTimeFormatterBuilder, for chaining */ public DateTimeFormatterBuilder appendMonthOfYearShortText() { return appendShortText(DateTimeFieldType.monthOfYear()); } /** * Instructs the printer to emit a locale-specific era text (BC/AD), and * the parser to expect it. The parser is case-insensitive. * * @return this DateTimeFormatterBuilder, for chaining */ public DateTimeFormatterBuilder appendEraText() { return appendText(DateTimeFieldType.era()); } /** * Instructs the printer to emit a locale-specific time zone name. * Using this method prevents parsing, because time zone names are not unique. * See {@link #appendTimeZoneName(Map)}. * * @return this DateTimeFormatterBuilder, for chaining */ public DateTimeFormatterBuilder appendTimeZoneName() { return append0(new TimeZoneName(TimeZoneName.LONG_NAME, null), null); } /** * Instructs the printer to emit a locale-specific time zone name, providing a lookup for parsing. * Time zone names are not unique, thus the API forces you to supply the lookup. * The names are searched in the order of the map, thus it is strongly recommended * to use a {@code LinkedHashMap} or similar. * * @param parseLookup the table of names, not null * @return this DateTimeFormatterBuilder, for chaining */ public DateTimeFormatterBuilder appendTimeZoneName(Map<String, DateTimeZone> parseLookup) { TimeZoneName pp = new TimeZoneName(TimeZoneName.LONG_NAME, parseLookup); return append0(pp, pp); } /** * Instructs the printer to emit a short locale-specific time zone name. * Using this method prevents parsing, because time zone names are not unique. * See {@link #appendTimeZoneShortName(Map)}. * * @return this DateTimeFormatterBuilder, for chaining */ public DateTimeFormatterBuilder appendTimeZoneShortName() { return append0(new TimeZoneName(TimeZoneName.SHORT_NAME, null), null); } /** * Instructs the printer to emit a short locale-specific time zone * name, providing a lookup for parsing. * Time zone names are not unique, thus the API forces you to supply the lookup. * The names are searched in the order of the map, thus it is strongly recommended * to use a {@code LinkedHashMap} or similar. * * @param parseLookup the table of names, null to use the {@link DateTimeUtils#getDefaultTimeZoneNames() default names} * @return this DateTimeFormatterBuilder, for chaining */ public DateTimeFormatterBuilder appendTimeZoneShortName(Map<String, DateTimeZone> parseLookup) { TimeZoneName pp = new TimeZoneName(TimeZoneName.SHORT_NAME, parseLookup); return append0(pp, pp); } /** * Instructs the printer to emit the identifier of the time zone. * From version 2.0, this field can be parsed. * * @return this DateTimeFormatterBuilder, for chaining */ public DateTimeFormatterBuilder appendTimeZoneId() { return append0(TimeZoneId.INSTANCE, TimeZoneId.INSTANCE); } /** * Instructs the printer to emit text and numbers to display time zone * offset from UTC. A parser will use the parsed time zone offset to adjust * the datetime. * <p> * If zero offset text is supplied, then it will be printed when the zone is zero. * During parsing, either the zero offset text, or the offset will be parsed. * * @param zeroOffsetText the text to use if time zone offset is zero. If * null, offset is always shown. * @param showSeparators if true, prints ':' separator before minute and * second field and prints '.' separator before fraction field. * @param minFields minimum number of fields to print, stopping when no * more precision is required. 1=hours, 2=minutes, 3=seconds, 4=fraction * @param maxFields maximum number of fields to print * @return this DateTimeFormatterBuilder, for chaining */ public DateTimeFormatterBuilder appendTimeZoneOffset( String zeroOffsetText, boolean showSeparators, int minFields, int maxFields) { return append0(new TimeZoneOffset (zeroOffsetText, zeroOffsetText, showSeparators, minFields, maxFields)); } /** * Instructs the printer to emit text and numbers to display time zone * offset from UTC. A parser will use the parsed time zone offset to adjust * the datetime. * <p> * If zero offset print text is supplied, then it will be printed when the zone is zero. * If zero offset parse text is supplied, then either it or the offset will be parsed. * * @param zeroOffsetPrintText the text to print if time zone offset is zero. If * null, offset is always shown. * @param zeroOffsetParseText the text to optionally parse to indicate that the time * zone offset is zero. If null, then always use the offset. * @param showSeparators if true, prints ':' separator before minute and * second field and prints '.' separator before fraction field. * @param minFields minimum number of fields to print, stopping when no * more precision is required. 1=hours, 2=minutes, 3=seconds, 4=fraction * @param maxFields maximum number of fields to print * @return this DateTimeFormatterBuilder, for chaining * @since 2.0 */ public DateTimeFormatterBuilder appendTimeZoneOffset( String zeroOffsetPrintText, String zeroOffsetParseText, boolean showSeparators, int minFields, int maxFields) { return append0(new TimeZoneOffset (zeroOffsetPrintText, zeroOffsetParseText, showSeparators, minFields, maxFields)); } //----------------------------------------------------------------------- /** * Calls upon {@link DateTimeFormat} to parse the pattern and append the * results into this builder. * * @param pattern pattern specification * @throws IllegalArgumentException if the pattern is invalid * @see DateTimeFormat */ public DateTimeFormatterBuilder appendPattern(String pattern) { DateTimeFormat.appendPatternTo(this, pattern); return this; } //----------------------------------------------------------------------- private Object getFormatter() { Object f = iFormatter; if (f == null) { if (iElementPairs.size() == 2) { Object printer = iElementPairs.get(0); Object parser = iElementPairs.get(1); if (printer != null) { if (printer == parser || parser == null) { f = printer; } } else { f = parser; } } if (f == null) { f = new Composite(iElementPairs); } iFormatter = f; } return f; } private boolean isPrinter(Object f) { if (f instanceof InternalPrinter) { if (f instanceof Composite) { return ((Composite)f).isPrinter(); } return true; } return false; } private boolean isParser(Object f) { if (f instanceof InternalParser) { if (f instanceof Composite) { return ((Composite)f).isParser(); } return true; } return false; } private boolean isFormatter(Object f) { return (isPrinter(f) || isParser(f)); } static void appendUnknownString(Appendable appendable, int len) throws IOException { for (int i = len; --i >= 0;) { appendable.append('\ufffd'); } } //----------------------------------------------------------------------- static class CharacterLiteral implements InternalPrinter, InternalParser { private final char iValue; CharacterLiteral(char value) { super(); iValue = value; } public int estimatePrintedLength() { return 1; } public void printTo( Appendable appendable, long instant, Chronology chrono, int displayOffset, DateTimeZone displayZone, Locale locale) throws IOException { appendable.append(iValue); } public void printTo(Appendable appendable, ReadablePartial partial, Locale locale) throws IOException { appendable.append(iValue); } public int estimateParsedLength() { return 1; } public int parseInto(DateTimeParserBucket bucket, CharSequence text, int position) { if (position >= text.length()) { return ~position; } char a = text.charAt(position); char b = iValue; if (a != b) { a = Character.toUpperCase(a); b = Character.toUpperCase(b); if (a != b) { a = Character.toLowerCase(a); b = Character.toLowerCase(b); if (a != b) { return ~position; } } } return position + 1; } } //----------------------------------------------------------------------- static class StringLiteral implements InternalPrinter, InternalParser { private final String iValue; StringLiteral(String value) { super(); iValue = value; } public int estimatePrintedLength() { return iValue.length(); } public void printTo( Appendable appendable, long instant, Chronology chrono, int displayOffset, DateTimeZone displayZone, Locale locale) throws IOException { appendable.append(iValue); } public void printTo(Appendable appendable, ReadablePartial partial, Locale locale) throws IOException { appendable.append(iValue); } public int estimateParsedLength() { return iValue.length(); } public int parseInto(DateTimeParserBucket bucket, CharSequence text, int position) { if (csStartsWithIgnoreCase(text, position, iValue)) { return position + iValue.length(); } return ~position; } } //----------------------------------------------------------------------- static abstract class NumberFormatter implements InternalPrinter, InternalParser { protected final DateTimeFieldType iFieldType; protected final int iMaxParsedDigits; protected final boolean iSigned; NumberFormatter(DateTimeFieldType fieldType, int maxParsedDigits, boolean signed) { super(); iFieldType = fieldType; iMaxParsedDigits = maxParsedDigits; iSigned = signed; } public int estimateParsedLength() { return iMaxParsedDigits; } public int parseInto(DateTimeParserBucket bucket, CharSequence text, int position) { int limit = Math.min(iMaxParsedDigits, text.length() - position); boolean negative = false; boolean positive = false; int length = 0; while (length < limit) { char c = text.charAt(position + length); if (length == 0 && (c == '-' || c == '+') && iSigned) { negative = c == '-'; positive = c == '+'; // Next character must be a digit. if (length + 1 >= limit || (c = text.charAt(position + length + 1)) < '0' || c > '9') { break; } length++; // Expand the limit to disregard the sign character. limit = Math.min(limit + 1, text.length() - position); continue; } if (c < '0' || c > '9') { break; } length++; } if (length == 0) { return ~position; } int value; if (length >= 9) { // Since value may exceed integer limits, use stock parser // which checks for this. if (positive) { value = Integer.parseInt(text.subSequence(position + 1, position += length).toString()); } else { value = Integer.parseInt(text.subSequence(position, position += length).toString()); } // value = Integer.parseInt(text.subSequence(position, position += length).toString()); } else { int i = position; if (negative || positive) { i++; } try { value = text.charAt(i++) - '0'; } catch (StringIndexOutOfBoundsException e) { return ~position; } position += length; while (i < position) { value = ((value << 3) + (value << 1)) + text.charAt(i++) - '0'; } if (negative) { value = -value; } } bucket.saveField(iFieldType, value); return position; } } //----------------------------------------------------------------------- static class UnpaddedNumber extends NumberFormatter { protected UnpaddedNumber(DateTimeFieldType fieldType, int maxParsedDigits, boolean signed) { super(fieldType, maxParsedDigits, signed); } public int estimatePrintedLength() { return iMaxParsedDigits; } public void printTo( Appendable appendable, long instant, Chronology chrono, int displayOffset, DateTimeZone displayZone, Locale locale) throws IOException { try { DateTimeField field = iFieldType.getField(chrono); FormatUtils.appendUnpaddedInteger(appendable, field.get(instant)); } catch (RuntimeException e) { appendable.append('\ufffd'); } } public void printTo(Appendable appendable, ReadablePartial partial, Locale locale) throws IOException { if (partial.isSupported(iFieldType)) { try { FormatUtils.appendUnpaddedInteger(appendable, partial.get(iFieldType)); } catch (RuntimeException e) { appendable.append('\ufffd'); } } else { appendable.append('\ufffd'); } } } //----------------------------------------------------------------------- static class PaddedNumber extends NumberFormatter { protected final int iMinPrintedDigits; protected PaddedNumber(DateTimeFieldType fieldType, int maxParsedDigits, boolean signed, int minPrintedDigits) { super(fieldType, maxParsedDigits, signed); iMinPrintedDigits = minPrintedDigits; } public int estimatePrintedLength() { return iMaxParsedDigits; } public void printTo( Appendable appendable, long instant, Chronology chrono, int displayOffset, DateTimeZone displayZone, Locale locale) throws IOException { try { DateTimeField field = iFieldType.getField(chrono); FormatUtils.appendPaddedInteger(appendable, field.get(instant), iMinPrintedDigits); } catch (RuntimeException e) { appendUnknownString(appendable, iMinPrintedDigits); } } public void printTo(Appendable appendable, ReadablePartial partial, Locale locale) throws IOException { if (partial.isSupported(iFieldType)) { try { FormatUtils.appendPaddedInteger(appendable, partial.get(iFieldType), iMinPrintedDigits); } catch (RuntimeException e) { appendUnknownString(appendable, iMinPrintedDigits); } } else { appendUnknownString(appendable, iMinPrintedDigits); } } } //----------------------------------------------------------------------- static class FixedNumber extends PaddedNumber { protected FixedNumber(DateTimeFieldType fieldType, int numDigits, boolean signed) { super(fieldType, numDigits, signed, numDigits); } @Override public int parseInto(DateTimeParserBucket bucket, CharSequence text, int position) { int newPos = super.parseInto(bucket, text, position); if (newPos < 0) { return newPos; } int expectedPos = position + iMaxParsedDigits; if (newPos != expectedPos) { if (iSigned) { char c = text.charAt(position); if (c == '-' || c == '+') { expectedPos++; } } if (newPos > expectedPos) { // The failure is at the position of the first extra digit. return ~(expectedPos + 1); } else if (newPos < expectedPos) { // The failure is at the position where the next digit should be. return ~newPos; } } return newPos; } } //----------------------------------------------------------------------- static class TwoDigitYear implements InternalPrinter, InternalParser { /** The field to print/parse. */ private final DateTimeFieldType iType; /** The pivot year. */ private final int iPivot; private final boolean iLenientParse; TwoDigitYear(DateTimeFieldType type, int pivot, boolean lenientParse) { super(); iType = type; iPivot = pivot; iLenientParse = lenientParse; } public int estimateParsedLength() { return iLenientParse ? 4 : 2; } public int parseInto(DateTimeParserBucket bucket, CharSequence text, int position) { int limit = text.length() - position; if (!iLenientParse) { limit = Math.min(2, limit); if (limit < 2) { return ~position; } } else { boolean hasSignChar = false; boolean negative = false; int length = 0; while (length < limit) { char c = text.charAt(position + length); if (length == 0 && (c == '-' || c == '+')) { hasSignChar = true; negative = c == '-'; if (negative) { length++; } else { // Skip the '+' for parseInt to succeed. position++; limit--; } continue; } if (c < '0' || c > '9') { break; } length++; } if (length == 0) { return ~position; } if (hasSignChar || length != 2) { int value; if (length >= 9) { // Since value may exceed integer limits, use stock // parser which checks for this. value = Integer.parseInt(text.subSequence(position, position += length).toString()); } else { int i = position; if (negative) { i++; } try { value = text.charAt(i++) - '0'; } catch (StringIndexOutOfBoundsException e) { return ~position; } position += length; while (i < position) { value = ((value << 3) + (value << 1)) + text.charAt(i++) - '0'; } if (negative) { value = -value; } } bucket.saveField(iType, value); return position; } } int year; char c = text.charAt(position); if (c < '0' || c > '9') { return ~position; } year = c - '0'; c = text.charAt(position + 1); if (c < '0' || c > '9') { return ~position; } year = ((year << 3) + (year << 1)) + c - '0'; int pivot = iPivot; // If the bucket pivot year is non-null, use that when parsing if (bucket.getPivotYear() != null) { pivot = bucket.getPivotYear().intValue(); } int low = pivot - 50; int t; if (low >= 0) { t = low % 100; } else { t = 99 + ((low + 1) % 100); } year += low + ((year < t) ? 100 : 0) - t; bucket.saveField(iType, year); return position + 2; } public int estimatePrintedLength() { return 2; } public void printTo( Appendable appendable, long instant, Chronology chrono, int displayOffset, DateTimeZone displayZone, Locale locale) throws IOException { int year = getTwoDigitYear(instant, chrono); if (year < 0) { appendable.append('\ufffd'); appendable.append('\ufffd'); } else { FormatUtils.appendPaddedInteger(appendable, year, 2); } } private int getTwoDigitYear(long instant, Chronology chrono) { try { int year = iType.getField(chrono).get(instant); if (year < 0) { year = -year; } return year % 100; } catch (RuntimeException e) { return -1; } } public void printTo(Appendable appendable, ReadablePartial partial, Locale locale) throws IOException { int year = getTwoDigitYear(partial); if (year < 0) { appendable.append('\ufffd'); appendable.append('\ufffd'); } else { FormatUtils.appendPaddedInteger(appendable, year, 2); } } private int getTwoDigitYear(ReadablePartial partial) { if (partial.isSupported(iType)) { try { int year = partial.get(iType); if (year < 0) { year = -year; } return year % 100; } catch (RuntimeException e) {} } return -1; } } //----------------------------------------------------------------------- static class TextField implements InternalPrinter, InternalParser { private static Map<Locale, Map |
... this post is sponsored by my books ... | |
#1 New Release! |
FP Best Seller |
Copyright 1998-2021 Alvin Alexander, alvinalexander.com
All Rights Reserved.
A percentage of advertising revenue from
pages under the /java/jwarehouse
URI on this website is
paid back to open source projects.