All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.threeten.bp.format.DateTimeFormatterBuilder.scala Maven / Gradle / Ivy

/*
 * Copyright (c) 2007-present, Stephen Colebourne & Michael Nascimento Santos
 *
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *  * Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 *
 *  * Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 *
 *  * Neither the name of JSR-310 nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package org.threeten.bp.format

import java.text.DateFormat
import java.text.SimpleDateFormat
import java.util.{ Comparator, Locale, Objects }
import java.lang.Long

import org.threeten.bp.ZoneId
import org.threeten.bp.ZoneOffset
import org.threeten.bp.chrono.ChronoLocalDate
import org.threeten.bp.chrono.Chronology
import org.threeten.bp.temporal.ChronoField
import org.threeten.bp.temporal.IsoFields
import org.threeten.bp.temporal.TemporalAccessor
import org.threeten.bp.temporal.TemporalField
import org.threeten.bp.temporal.TemporalQueries
import org.threeten.bp.temporal.TemporalQuery
import org.threeten.bp.format.internal.TTBPDateTimeTextProvider
import org.threeten.bp.format.internal.TTBPSimpleDateTimeTextProvider
import org.threeten.bp.format.internal.TTBPDateTimeFormatterBuilder
import org.threeten.bp.format.internal.DateTimePrinterParser

object DateTimeFormatterBuilder {

  /** Query for a time-zone that is region-only. */
  private lazy val QUERY_REGION_ONLY: TemporalQuery[ZoneId] =
    new TemporalQuery[ZoneId] {
      override def queryFrom(temporal: TemporalAccessor): ZoneId = {
        val zone: ZoneId = temporal.query(TemporalQueries.zoneId)
        if (zone != null && !zone.isInstanceOf[ZoneOffset]) zone else null
      }
    }

  /**
   * Gets the formatting pattern for date and time styles for a locale and chronology. The locale
   * and chronology are used to lookup the locale specific format for the requested dateStyle and/or
   * timeStyle.
   *
   * @param dateStyle
   *   the FormatStyle for the date
   * @param timeStyle
   *   the FormatStyle for the time
   * @param chrono
   *   the Chronology, non-null
   * @param locale
   *   the locale, non-null
   * @return
   *   the locale and Chronology specific formatting pattern
   * @throws IllegalArgumentException
   *   if both dateStyle and timeStyle are null
   */
  def getLocalizedDateTimePattern(
    dateStyle: FormatStyle,
    timeStyle: FormatStyle,
    chrono:    Chronology,
    locale:    Locale
  ): String = {
    Objects.requireNonNull(locale, "locale")
    Objects.requireNonNull(chrono, "chrono")
    if (dateStyle == null && timeStyle == null)
      throw new IllegalArgumentException("Either dateStyle or timeStyle must be non-null")
    val dateFormat: DateFormat =
      if (dateStyle != null)
        if (timeStyle != null)
          DateFormat.getDateTimeInstance(dateStyle.ordinal, timeStyle.ordinal, locale)
        else
          DateFormat.getDateInstance(dateStyle.ordinal, locale)
      else
        DateFormat.getTimeInstance(timeStyle.ordinal, locale)
    if (dateFormat.isInstanceOf[SimpleDateFormat])
      dateFormat.asInstanceOf[SimpleDateFormat].toPattern
    else
      throw new IllegalArgumentException("Unable to determine pattern")
  }

  /** Map of letters to fields. */
  private lazy val FIELD_MAP: java.util.Map[Character, TemporalField] = {
    // Size checked
    val map = new java.util.HashMap[Character, TemporalField]
    map.put('G', ChronoField.ERA)
    map.put('y', ChronoField.YEAR_OF_ERA)
    map.put('u', ChronoField.YEAR)
    map.put('Q', IsoFields.QUARTER_OF_YEAR)
    map.put('q', IsoFields.QUARTER_OF_YEAR)
    map.put('M', ChronoField.MONTH_OF_YEAR)
    map.put('L', ChronoField.MONTH_OF_YEAR)
    map.put('D', ChronoField.DAY_OF_YEAR)
    map.put('d', ChronoField.DAY_OF_MONTH)
    map.put('F', ChronoField.ALIGNED_DAY_OF_WEEK_IN_MONTH)
    map.put('E', ChronoField.DAY_OF_WEEK)
    map.put('c', ChronoField.DAY_OF_WEEK)
    map.put('e', ChronoField.DAY_OF_WEEK)
    map.put('a', ChronoField.AMPM_OF_DAY)
    map.put('H', ChronoField.HOUR_OF_DAY)
    map.put('k', ChronoField.CLOCK_HOUR_OF_DAY)
    map.put('K', ChronoField.HOUR_OF_AMPM)
    map.put('h', ChronoField.CLOCK_HOUR_OF_AMPM)
    map.put('m', ChronoField.MINUTE_OF_HOUR)
    map.put('s', ChronoField.SECOND_OF_MINUTE)
    map.put('S', ChronoField.NANO_OF_SECOND)
    map.put('A', ChronoField.MILLI_OF_DAY)
    map.put('n', ChronoField.NANO_OF_SECOND)
    map.put('N', ChronoField.NANO_OF_DAY)
    map
  }

  /** Length comparator. */
  private[format] lazy val LENGTH_SORT: Comparator[String] =
    new Comparator[String] {
      override def compare(str1: String, str2: String): Int =
        if (str1.length == str2.length) str1.compareTo(str2) else str1.length - str2.length
    }
}

/**
 * Builder to create date-time formatters.
 *
 * This allows a {@code DateTimeFormatter} to be created. All date-time formatters are created
 * ultimately using this builder.
 *
 * The basic elements of date-time can all be added: 
  • Value - a numeric value
  • *
  • Fraction - a fractional value including the decimal place. Always use this when outputting * fractions to ensure that the fraction is parsed correctly
  • Text - the textual equivalent * for the value
  • OffsetId/Offset - the {@linkplain ZoneOffset zone offset}
  • ZoneId * - the {@linkplain ZoneId time-zone} id
  • ZoneText - the name of the time-zone
  • *
  • Literal - a text literal
  • Nested and Optional - formats can be nested or made * optional
  • Other - the printer and parser interfaces can be used to add user supplied * formatting

In addition, any of the elements may be decorated by padding, either * with spaces or any other character. * * Finally, a shorthand pattern, mostly compatible with {@code java.text.SimpleDateFormat * SimpleDateFormat} can be used, see {@link #appendPattern(String)}. In practice, this simply * parses the pattern and calls other methods on the builder. * *

Specification for implementors

This class is a mutable builder intended for use from a * single thread. * * @constructor * Constructs a new instance of the builder. * * @param parent * the parent builder, not null * @param optional * whether the formatter is optional, not null */ final class DateTimeFormatterBuilder private ( private val parent: DateTimeFormatterBuilder, private val optional: Boolean ) { /** Constructs a new instance of the builder. */ def this() = this(null, false) /** The currently active builder, used by the outermost builder. */ private var active: DateTimeFormatterBuilder = this /** The list of printers that will be used. */ private val printerParsers: java.util.List[DateTimePrinterParser] = new java.util.ArrayList[DateTimePrinterParser] /** The width to pad the next field to. */ private var padNextWidth: Int = 0 /** The character to pad the next field with. */ private var padNextChar: Char = 0 /** The index of the last variable width value parser. */ private var valueParserIndex: Int = -1 /** * Changes the parse style to be case sensitive for the remainder of the formatter. * * Parsing can be case sensitive or insensitive - by default it is case sensitive. This method * allows the case sensitivity setting of parsing to be changed. * * Calling this method changes the state of the builder such that all subsequent builder method * calls will parse text in case sensitive mode. See {@link #parseCaseInsensitive} for the * opposite setting. The parse case sensitive/insensitive methods may be called at any point in * the builder, thus the parser can swap between case parsing modes multiple times during the * parse. * * Since the default is case sensitive, this method should only be used after a previous call to * {@code #parseCaseInsensitive}. * * @return * this, for chaining, not null */ def parseCaseSensitive: DateTimeFormatterBuilder = { appendInternal(TTBPDateTimeFormatterBuilder.SettingsParser.SENSITIVE) this } /** * Changes the parse style to be case insensitive for the remainder of the formatter. * * Parsing can be case sensitive or insensitive - by default it is case sensitive. This method * allows the case sensitivity setting of parsing to be changed. * * Calling this method changes the state of the builder such that all subsequent builder method * calls will parse text in case sensitive mode. See {@link #parseCaseSensitive()} for the * opposite setting. The parse case sensitive/insensitive methods may be called at any point in * the builder, thus the parser can swap between case parsing modes multiple times during the * parse. * * @return * this, for chaining, not null */ def parseCaseInsensitive: DateTimeFormatterBuilder = { appendInternal(TTBPDateTimeFormatterBuilder.SettingsParser.INSENSITIVE) this } /** * Changes the parse style to be strict for the remainder of the formatter. * * Parsing can be strict or lenient - by default its strict. This controls the degree of * flexibility in matching the text and sign styles. * * When used, this method changes the parsing to be strict from this point onwards. As strict is * the default, this is normally only needed after calling {@link #parseLenient()}. The change * will remain in force until the end of the formatter that is eventually constructed or until * {@code parseLenient} is called. * * @return * this, for chaining, not null */ def parseStrict: DateTimeFormatterBuilder = { appendInternal(TTBPDateTimeFormatterBuilder.SettingsParser.STRICT) this } /** * Changes the parse style to be lenient for the remainder of the formatter. Note that case * sensitivity is set separately to this method. * * Parsing can be strict or lenient - by default its strict. This controls the degree of * flexibility in matching the text and sign styles. Applications calling this method should * typically also call {@link #parseCaseInsensitive()}. * * When used, this method changes the parsing to be strict from this point onwards. The change * will remain in force until the end of the formatter that is eventually constructed or until * {@code parseStrict} is called. * * @return * this, for chaining, not null */ def parseLenient: DateTimeFormatterBuilder = { appendInternal(TTBPDateTimeFormatterBuilder.SettingsParser.LENIENT) this } /** * Appends a default value for a field to the formatter for use in parsing. * * This appends an instruction to the builder to inject a default value into the parsed result. * This is especially useful in conjunction with optional parts of the formatter. * * For example, consider a formatter that parses the year, followed by an optional month, with a * further optional day-of-month. Using such a formatter would require the calling code to check * whether a full date, year-month or just a year had been parsed. This method can be used to * default the month and day-of-month to a sensible value, such as the first of the month, * allowing the calling code to always get a date. * * During formatting, this method has no effect. * * During parsing, the current state of the parse is inspected. If the specified field has no * associated value, because it has not been parsed successfully at that point, then the specified * value is injected into the parse result. Injection is immediate, thus the field-value pair will * be visible to any subsequent elements in the formatter. As such, this method is normally called * at the end of the builder. * * @param field * the field to default the value of, not null * @param value * the value to default the field to * @return * this, for chaining, not null */ def parseDefaulting(field: TemporalField, value: Long): DateTimeFormatterBuilder = { Objects.requireNonNull(field, "field") appendInternal(new TTBPDateTimeFormatterBuilder.DefaultingParser(field, value)) this } /** * Appends the value of a date-time field to the formatter using a normal output style. * * The value of the field will be output during a print. If the value cannot be obtained then an * exception will be thrown. * * The value will be printed as per the normal print of an integer value. Only negative numbers * will be signed. No padding will be added. * * The parser for a variable width value such as this normally behaves greedily, requiring one * digit, but accepting as many digits as possible. This behavior can be affected by 'adjacent * value parsing'. See {@link #appendValue(TemporalField, int)} for full details. * * @param field * the field to append, not null * @return * this, for chaining, not null */ def appendValue(field: TemporalField): DateTimeFormatterBuilder = { Objects.requireNonNull(field, "field") appendValue( new TTBPDateTimeFormatterBuilder.NumberPrinterParser(field, 1, 19, SignStyle.NORMAL) ) this } /** * Appends the value of a date-time field to the formatter using a fixed width, zero-padded * approach. * * The value of the field will be output during a print. If the value cannot be obtained then an * exception will be thrown. * * The value will be zero-padded on the left. If the size of the value means that it cannot be * printed within the width then an exception is thrown. If the value of the field is negative * then an exception is thrown during printing. * * This method supports a special technique of parsing known as 'adjacent value parsing'. This * technique solves the problem where a variable length value is followed by one or more fixed * length values. The standard parser is greedy, and thus it would normally steal the digits that * are needed by the fixed width value parsers that follow the variable width one. * * No action is required to initiate 'adjacent value parsing'. When a call to {@code appendValue} * with a variable width is made, the builder enters adjacent value parsing setup mode. If the * immediately subsequent method call or calls on the same builder are to this method, then the * parser will reserve space so that the fixed width values can be parsed. * * For example, consider {@code builder.appendValue(YEAR).appendValue(MONTH_OF_YEAR, 2);} The year * is a variable width parse of between 1 and 19 digits. The month is a fixed width parse of 2 * digits. Because these were appended to the same builder immediately after one another, the year * parser will reserve two digits for the month to parse. Thus, the text '201106' will correctly * parse to a year of 2011 and a month of 6. Without adjacent value parsing, the year would * greedily parse all six digits and leave nothing for the month. * * Adjacent value parsing applies to each set of fixed width not-negative values in the parser * that immediately follow any kind of variable width value. Calling any other append method will * end the setup of adjacent value parsing. Thus, in the unlikely event that you need to avoid * adjacent value parsing behavior, simply add the {@code appendValue} to another {@code * DateTimeFormatterBuilder. and add that to this builder. * * If adjacent parsing is active, then parsing must match exactly the specified number of digits * in both strict and lenient modes. In addition, no positive or negative sign is permitted. * * @param field * the field to append, not null * @param width * the width of the printed field, from 1 to 19 * @return * this, for chaining, not null * @throws IllegalArgumentException * if the width is invalid */ def appendValue(field: TemporalField, width: Int): DateTimeFormatterBuilder = { Objects.requireNonNull(field, "field") if (width < 1 || width > 19) throw new IllegalArgumentException(s"The width must be from 1 to 19 inclusive but was $width") appendValue( new TTBPDateTimeFormatterBuilder.NumberPrinterParser(field, width, width, SignStyle.NOT_NEGATIVE ) ) this } /** * Appends the value of a date-time field to the formatter providing full control over printing. * * The value of the field will be output during a print. If the value cannot be obtained then an * exception will be thrown. * * This method provides full control of the numeric formatting, including zero-padding and the * positive/negative sign. * * The parser for a variable width value such as this normally behaves greedily, accepting as many * digits as possible. This behavior can be affected by 'adjacent value parsing'. See {@link * #appendValue(TemporalField, int)} for full details. * * In strict parsing mode, the minimum number of parsed digits is {@code minWidth}. In lenient * parsing mode, the minimum number of parsed digits is one. * * If this method is invoked with equal minimum and maximum widths and a sign style of {@code * NOT_NEGATIVE} then it delegates to {@code appendValue(TemporalField,int)}. In this scenario, * the printing and parsing behavior described there occur. * * @param field * the field to append, not null * @param minWidth * the minimum field width of the printed field, from 1 to 19 * @param maxWidth * the maximum field width of the printed field, from 1 to 19 * @param signStyle * the positive/negative output style, not null * @return * this, for chaining, not null * @throws IllegalArgumentException * if the widths are invalid */ def appendValue( field: TemporalField, minWidth: Int, maxWidth: Int, signStyle: SignStyle ): DateTimeFormatterBuilder = { if (minWidth == maxWidth && (signStyle eq SignStyle.NOT_NEGATIVE)) return appendValue(field, maxWidth) Objects.requireNonNull(field, "field") Objects.requireNonNull(signStyle, "signStyle") if (minWidth < 1 || minWidth > 19) throw new IllegalArgumentException( s"The minimum width must be from 1 to 19 inclusive but was $minWidth" ) if (maxWidth < 1 || maxWidth > 19) throw new IllegalArgumentException( s"The maximum width must be from 1 to 19 inclusive but was $maxWidth" ) if (maxWidth < minWidth) throw new IllegalArgumentException( s"The maximum width must exceed or equal the minimum width but $maxWidth < $minWidth" ) appendValue( new TTBPDateTimeFormatterBuilder.NumberPrinterParser(field, minWidth, maxWidth, signStyle) ) this } /** * Appends the reduced value of a date-time field to the formatter. * * Since fields such as year vary by chronology, it is recommended to use the {@link * #appendValueReduced(TemporalField, int, int, ChronoLocalDate)} date} variant of this method in * most cases. This variant is suitable for simple fields or working with only the ISO chronology. * * For formatting, the {@code width} and {@code maxWidth} are used to determine the number of * characters to format. If they are equal then the format is fixed width. If the value of the * field is within the range of the {@code baseValue} using {@code width} characters then the * reduced value is formatted otherwise the value is truncated to fit {@code maxWidth}. The * rightmost characters are output to match the width, left padding with zero. * * For strict parsing, the number of characters allowed by {@code width} to {@code maxWidth} are * parsed. For lenient parsing, the number of characters must be at least 1 and less than 10. If * the number of digits parsed is equal to {@code width} and the value is positive, the value of * the field is computed to be the first number greater than or equal to the {@code baseValue} * with the same least significant characters, otherwise the value parsed is the field value. This * allows a reduced value to be entered for values in range of the baseValue and width and * absolute values can be entered for values outside the range. * * For example, a base value of {@code 1980} and a width of {@code 2} will have valid values from * {@code 1980} to {@code 2079}. During parsing, the text {@code "12"} will result in the value * {@code 2012} as that is the value within the range where the last two characters are "12". By * contrast, parsing the text {@code "1915"} will result in the value {@code 1915}. * * @param field * the field to append, not null * @param width * the field width of the printed and parsed field, from 1 to 10 * @param maxWidth * the maximum field width of the printed field, from 1 to 10 * @param baseValue * the base value of the range of valid values * @return * this, for chaining, not null * @throws IllegalArgumentException * if the width or base value is invalid */ def appendValueReduced( field: TemporalField, width: Int, maxWidth: Int, baseValue: Int ): DateTimeFormatterBuilder = { Objects.requireNonNull(field, "field") appendValue( new TTBPDateTimeFormatterBuilder.ReducedPrinterParser(field, width, maxWidth, baseValue, null) ) this } /** * Appends the reduced value of a date-time field to the formatter. * * This is typically used for formatting and parsing a two digit year. * * The base date is used to calculate the full value during parsing. For example, if the base date * is 1950-01-01 then parsed values for a two digit year parse will be in the range 1950-01-01 to * 2049-12-31. Only the year would be extracted from the date, thus a base date of 1950-08-25 * would also parse to the range 1950-01-01 to 2049-12-31. This behavior is necessary to support * fields such as week-based-year or other calendar systems where the parsed value does not align * with standard ISO years. * * The exact behavior is as follows. Parse the full set of fields and determine the effective * chronology using the last chronology if it appears more than once. Then convert the base date * to the effective chronology. Then extract the specified field from the chronology-specific base * date and use it to determine the {@code baseValue} used below. * * For formatting, the {@code width} and {@code maxWidth} are used to determine the number of * characters to format. If they are equal then the format is fixed width. If the value of the * field is within the range of the {@code baseValue} using {@code width} characters then the * reduced value is formatted otherwise the value is truncated to fit {@code maxWidth}. The * rightmost characters are output to match the width, left padding with zero. * * For strict parsing, the number of characters allowed by {@code width} to {@code maxWidth} are * parsed. For lenient parsing, the number of characters must be at least 1 and less than 10. If * the number of digits parsed is equal to {@code width} and the value is positive, the value of * the field is computed to be the first number greater than or equal to the {@code baseValue} * with the same least significant characters, otherwise the value parsed is the field value. This * allows a reduced value to be entered for values in range of the baseValue and width and * absolute values can be entered for values outside the range. * * For example, a base value of {@code 1980} and a width of {@code 2} will have valid values from * {@code 1980} to {@code 2079}. During parsing, the text {@code "12"} will result in the value * {@code 2012} as that is the value within the range where the last two characters are "12". By * contrast, parsing the text {@code "1915"} will result in the value {@code 1915}. * * @param field * the field to append, not null * @param width * the field width of the printed and parsed field, from 1 to 10 * @param maxWidth * the maximum field width of the printed field, from 1 to 10 * @param baseDate * the base date used to calculate the base value for the range of valid values in the parsed * chronology, not null * @return * this, for chaining, not null * @throws IllegalArgumentException * if the width or base value is invalid */ def appendValueReduced( field: TemporalField, width: Int, maxWidth: Int, baseDate: ChronoLocalDate ): DateTimeFormatterBuilder = { Objects.requireNonNull(field, "field") Objects.requireNonNull(baseDate, "baseDate") appendValue( new TTBPDateTimeFormatterBuilder.ReducedPrinterParser(field, width, maxWidth, 0, baseDate) ) this } /** * Appends a fixed width printer-parser. * * @param pp * the printer-parser, not null * @return * this, for chaining, not null */ private def appendValue( pp: TTBPDateTimeFormatterBuilder.NumberPrinterParser ): DateTimeFormatterBuilder = { // Size checked if ( active.valueParserIndex >= 0 && active.printerParsers .get(active.valueParserIndex) .isInstanceOf[TTBPDateTimeFormatterBuilder.NumberPrinterParser] ) { val activeValueParser: Int = active.valueParserIndex var basePP: TTBPDateTimeFormatterBuilder.NumberPrinterParser = active.printerParsers .get(activeValueParser) .asInstanceOf[TTBPDateTimeFormatterBuilder.NumberPrinterParser] if (pp.minWidth == pp.maxWidth && (pp.signStyle eq SignStyle.NOT_NEGATIVE)) { basePP = basePP.withSubsequentWidth(pp.maxWidth) appendInternal(pp.withFixedWidth) active.valueParserIndex = activeValueParser } else { basePP = basePP.withFixedWidth active.valueParserIndex = appendInternal(pp) } active.printerParsers.set(activeValueParser, basePP) } else active.valueParserIndex = appendInternal(pp) this } /** * Appends the fractional value of a date-time field to the formatter. * * The fractional value of the field will be output including the preceding decimal point. The * preceding value is not output. For example, the second-of-minute value of 15 would be output as * {@code .25}. * * The width of the printed fraction can be controlled. Setting the minimum width to zero will * cause no output to be generated. The printed fraction will have the minimum width necessary * between the minimum and maximum widths - trailing zeroes are omitted. No rounding occurs due to * the maximum width - digits are simply dropped. * * When parsing in strict mode, the number of parsed digits must be between the minimum and * maximum width. When parsing in lenient mode, the minimum width is considered to be zero and the * maximum is nine. * * If the value cannot be obtained then an exception will be thrown. If the value is negative an * exception will be thrown. If the field does not have a fixed set of valid values then an * exception will be thrown. If the field value in the date-time to be printed is invalid it * cannot be printed and an exception will be thrown. * * @param field * the field to append, not null * @param minWidth * the minimum width of the field excluding the decimal point, from 0 to 9 * @param maxWidth * the maximum width of the field excluding the decimal point, from 1 to 9 * @param decimalPoint * whether to output the localized decimal point symbol * @return * this, for chaining, not null * @throws IllegalArgumentException * if the field has a variable set of valid values or either width is invalid */ def appendFraction( field: TemporalField, minWidth: Int, maxWidth: Int, decimalPoint: Boolean ): DateTimeFormatterBuilder = { appendInternal( new TTBPDateTimeFormatterBuilder.FractionPrinterParser(field, minWidth, maxWidth, decimalPoint ) ) this } /** * Appends the text of a date-time field to the formatter using the full text style. * * The text of the field will be output during a print. The value must be within the valid range * of the field. If the value cannot be obtained then an exception will be thrown. If the field * has no textual representation, then the numeric value will be used. * * The value will be printed as per the normal print of an integer value. Only negative numbers * will be signed. No padding will be added. * * @param field * the field to append, not null * @return * this, for chaining, not null */ def appendText(field: TemporalField): DateTimeFormatterBuilder = appendText(field, TextStyle.FULL) /** * Appends the text of a date-time field to the formatter. * * The text of the field will be output during a print. The value must be within the valid range * of the field. If the value cannot be obtained then an exception will be thrown. If the field * has no textual representation, then the numeric value will be used. * * The value will be printed as per the normal print of an integer value. Only negative numbers * will be signed. No padding will be added. * * @param field * the field to append, not null * @param textStyle * the text style to use, not null * @return * this, for chaining, not null */ def appendText(field: TemporalField, textStyle: TextStyle): DateTimeFormatterBuilder = { Objects.requireNonNull(field, "field") Objects.requireNonNull(textStyle, "textStyle") appendInternal( new TTBPDateTimeFormatterBuilder.TextPrinterParser(field, textStyle, TTBPDateTimeTextProvider.Provider ) ) this } /** * Appends the text of a date-time field to the formatter using the specified map to supply the * text. * * The standard text outputting methods use the localized text in the JDK. This method allows that * text to be specified directly. The supplied map is not validated by the builder to ensure that * printing or parsing is possible, thus an invalid map may throw an error during later use. * * Supplying the map of text provides considerable flexibility in printing and parsing. For * example, a legacy application might require or supply the months of the year as "JNY", "FBY", * "MCH" etc. These do not match the standard set of text for localized month names. Using this * method, a map can be created which defines the connection between each value and the text: *
 Map<Long, String> map = new HashMap<>(); map.put(1, "JNY"); map.put(2,
   * "FBY"); map.put(3, "MCH"); ... builder.appendText(MONTH_OF_YEAR, map); 
* * Other uses might be to output the value with a suffix, such as "1st", "2nd", "3rd", or as Roman * numerals "I", "II", "III", "IV". * * During printing, the value is obtained and checked that it is in the valid range. If text is * not available for the value then it is output as a number. During parsing, the parser will * match against the map of text and numeric values. * * @param field * the field to append, not null * @param textLookup * the map from the value to the text * @return * this, for chaining, not null */ def appendText( field: TemporalField, textLookup: java.util.Map[Long, String] ): DateTimeFormatterBuilder = { Objects.requireNonNull(field, "field") Objects.requireNonNull(textLookup, "textLookup") import scala.collection.JavaConverters._ val copy: Map[scala.Long, String] = textLookup.asScala.toMap.map { case (x, i) => (x.toLong, i) } val map: Map[TextStyle, Map[scala.Long, String]] = Map(TextStyle.FULL -> copy) val store: TTBPSimpleDateTimeTextProvider.LocaleStore = new TTBPSimpleDateTimeTextProvider.LocaleStore(map) val provider: TTBPDateTimeTextProvider = new TTBPDateTimeTextProvider() { override def getText( field: TemporalField, value: scala.Long, style: TextStyle, locale: Locale ): String = store.getText(value, style) override def getTextIterator( field: TemporalField, style: TextStyle, locale: Locale ): Iterator[(String, scala.Long)] = store.getTextIterator(style) } appendInternal( new TTBPDateTimeFormatterBuilder.TextPrinterParser(field, TextStyle.FULL, provider) ) this } /** * Appends an instant using ISO-8601 to the formatter, formatting fractional digits in groups of * three. * * Instants have a fixed output format. They are converted to a date-time with a zone-offset of * UTC and formatted using the standard ISO-8601 format. With this method, formatting * nano-of-second outputs zero, three, six or nine digits digits as necessary. The localized * decimal style is not used. * * The instant is obtained using {@link ChronoField#INSTANT_SECONDS INSTANT_SECONDS} and * optionally (@code NANO_OF_SECOND). The value of {@code INSTANT_SECONDS} may be outside the * maximum range of {@code LocalDateTime}. * * The {@linkplain ResolverStyle resolver style} has no effect on instant parsing. The end-of-day * time of '24:00' is handled as midnight at the start of the following day. The leap-second time * of '23:59:59' is handled to some degree, see {@link DateTimeFormatter#parsedLeapSecond()} for * full details. * * An alternative to this method is to format/parse the instant as a single epoch-seconds value. * That is achieved using {@code appendValue(INSTANT_SECONDS)}. * * @return * this, for chaining, not null */ def appendInstant: DateTimeFormatterBuilder = { appendInternal(new TTBPDateTimeFormatterBuilder.InstantPrinterParser(-2)) this } /** * Appends an instant using ISO-8601 to the formatter with control over the number of fractional * digits. * * Instants have a fixed output format, although this method provides some control over the * fractional digits. They are converted to a date-time with a zone-offset of UTC and printed * using the standard ISO-8601 format. The localized decimal style is not used. * * The {@code fractionalDigits} parameter allows the output of the fractional second to be * controlled. Specifying zero will cause no fractional digits to be output. From 1 to 9 will * output an increasing number of digits, using zero right-padding if necessary. The special value * -1 is used to output as many digits as necessary to avoid any trailing zeroes. * * When parsing in strict mode, the number of parsed digits must match the fractional digits. When * parsing in lenient mode, any number of fractional digits from zero to nine are accepted. * * The instant is obtained using {@link ChronoField#INSTANT_SECONDS INSTANT_SECONDS} and * optionally (@code NANO_OF_SECOND). The value of {@code INSTANT_SECONDS} may be outside the * maximum range of {@code LocalDateTime}. * * The {@linkplain ResolverStyle resolver style} has no effect on instant parsing. The end-of-day * time of '24:00' is handled as midnight at the start of the following day. The leap-second time * of '23:59:59' is handled to some degree, see {@link DateTimeFormatter#parsedLeapSecond()} for * full details. * * An alternative to this method is to format/parse the instant as a single epoch-seconds value. * That is achieved using {@code appendValue(INSTANT_SECONDS)}. * * @param fractionalDigits * the number of fractional second digits to format with, from 0 to 9, or -1 to use as many * digits as necessary * @return * this, for chaining, not null */ def appendInstant(fractionalDigits: Int): DateTimeFormatterBuilder = if (fractionalDigits < -1 || fractionalDigits > 9) throw new IllegalArgumentException(s"Invalid fractional digits: $fractionalDigits") else { appendInternal(new TTBPDateTimeFormatterBuilder.InstantPrinterParser(fractionalDigits)) this } /** * Appends the zone offset, such as '+01:00', to the formatter. * * This appends an instruction to print/parse the offset ID to the builder. This is equivalent to * calling {@code appendOffset("HH:MM:ss", "Z")}. * * @return * this, for chaining, not null */ def appendOffsetId: DateTimeFormatterBuilder = { appendInternal(TTBPDateTimeFormatterBuilder.OffsetIdPrinterParser.INSTANCE_ID) this } /** * Appends the zone offset, such as '+01:00', to the formatter. * * This appends an instruction to print/parse the offset ID to the builder. * * During printing, the offset is obtained using a mechanism equivalent to querying the temporal * with {@link TemporalQueries#offset()}. It will be printed using the format defined below. If * the offset cannot be obtained then an exception is thrown unless the section of the formatter * is optional. * * During parsing, the offset is parsed using the format defined below. If the offset cannot be * parsed then an exception is thrown unless the section of the formatter is optional. * * The format of the offset is controlled by a pattern which must be one of the following:
    *
  • {@code +HH} - hour only, ignoring minute and second
  • {@code +HHmm} - hour, with minute * if non-zero, ignoring second, no colon
  • {@code +HH:mm} - hour, with minute if non-zero, * ignoring second, with colon
  • {@code +HHMM} - hour and minute, ignoring second, no colon *
  • {@code +HH:MM} - hour and minute, ignoring second, with colon
  • {@code +HHMMss} - hour * and minute, with second if non-zero, no colon
  • {@code +HH:MM:ss} - hour and minute, with * second if non-zero, with colon
  • {@code +HHMMSS} - hour, minute and second, no colon *
  • {@code +HH:MM:SS} - hour, minute and second, with colon

The "no offset" text * controls what text is printed when the total amount of the offset fields to be output is zero. * Example values would be 'Z', '+00:00', 'UTC' or 'GMT'. Three formats are accepted for parsing * UTC - the "no offset" text, and the plus and minus versions of zero defined by the pattern. * * @param pattern * the pattern to use, not null * @param noOffsetText * the text to use when the offset is zero, not null * @return * this, for chaining, not null */ def appendOffset(pattern: String, noOffsetText: String): DateTimeFormatterBuilder = { appendInternal(new TTBPDateTimeFormatterBuilder.OffsetIdPrinterParser(noOffsetText, pattern)) this } /** * Appends the localized zone offset, such as 'GMT+01:00', to the formatter. * * This appends a localized zone offset to the builder, the format of the localized offset is * controlled by the specified {@link FormatStyle style} to this method:

  • {@link * TextStyle#FULL full} - formats with localized offset text, such as 'GMT, 2-digit hour and * minute field, optional second field if non-zero, and colon.
  • {@link TextStyle#SHORT short} - * formats with localized offset text, such as 'GMT, hour without leading zero, optional 2-digit * minute and second if non-zero, and colon.
* * During formatting, the offset is obtained using a mechanism equivalent to querying the temporal * with {@link TemporalQueries#offset()}. If the offset cannot be obtained then an exception is * thrown unless the section of the formatter is optional. * * During parsing, the offset is parsed using the format defined above. If the offset cannot be * parsed then an exception is thrown unless the section of the formatter is optional. * * @param style * the format style to use, not null * @return * this, for chaining, not null * @throws IllegalArgumentException * if style is neither { @link TextStyle#FULL full} nor { @link TextStyle#SHORT short} */ def appendLocalizedOffset(style: TextStyle): DateTimeFormatterBuilder = { Objects.requireNonNull(style, "style") if ((style ne TextStyle.FULL) && (style ne TextStyle.SHORT)) throw new IllegalArgumentException("Style must be either full or short") appendInternal(new TTBPDateTimeFormatterBuilder.LocalizedOffsetPrinterParser(style)) this } /** * Appends the time-zone ID, such as 'Europe/Paris' or '+02:00', to the formatter. * * This appends an instruction to print/parse the zone ID to the builder. The zone ID is obtained * in a strict manner suitable for {@code ZonedDateTime}. By contrast, {@code OffsetDateTime} does * not have a zone ID suitable for use with this method, see {@link #appendZoneOrOffsetId()}. * * During printing, the zone is obtained using a mechanism equivalent to querying the temporal * with {@link TemporalQueries#zoneId()}. It will be printed using the result of {@link * ZoneId#getId()}. If the zone cannot be obtained then an exception is thrown unless the section * of the formatter is optional. * * During parsing, the zone is parsed and must match a known zone or offset. If the zone cannot be * parsed then an exception is thrown unless the section of the formatter is optional. * * @return * this, for chaining, not null * @see * #appendZoneRegionId() */ def appendZoneId: DateTimeFormatterBuilder = { appendInternal( new TTBPDateTimeFormatterBuilder.ZoneIdPrinterParser(TemporalQueries.zoneId, "ZoneId()") ) this } /** * Appends the time-zone region ID, such as 'Europe/Paris', to the formatter, rejecting the zone * ID if it is a {@code ZoneOffset}. * * This appends an instruction to print/parse the zone ID to the builder only if it is a * region-based ID. * * During printing, the zone is obtained using a mechanism equivalent to querying the temporal * with {@link TemporalQueries#zoneId()}. If the zone is a {@code ZoneOffset} or it cannot be * obtained then an exception is thrown unless the section of the formatter is optional. If the * zone is not an offset, then the zone will be printed using the zone ID from {@link * ZoneId#getId()}. * * During parsing, the zone is parsed and must match a known zone or offset. If the zone cannot be * parsed then an exception is thrown unless the section of the formatter is optional. Note that * parsing accepts offsets, whereas printing will never produce one, thus parsing is equivalent to * {@code appendZoneId}. * * @return * this, for chaining, not null * @see * #appendZoneId() */ def appendZoneRegionId: DateTimeFormatterBuilder = { appendInternal( new TTBPDateTimeFormatterBuilder.ZoneIdPrinterParser( DateTimeFormatterBuilder.QUERY_REGION_ONLY, "ZoneRegionId()" ) ) this } /** * Appends the time-zone ID, such as 'Europe/Paris' or '+02:00', to the formatter, using the best * available zone ID. * * This appends an instruction to print/parse the best available zone or offset ID to the builder. * The zone ID is obtained in a lenient manner that first attempts to find a true zone ID, such as * that on {@code ZonedDateTime}, and then attempts to find an offset, such as that on {@code * OffsetDateTime}. * * During printing, the zone is obtained using a mechanism equivalent to querying the temporal * with {@link TemporalQueries#zone()}. It will be printed using the result of {@link * ZoneId#getId()}. If the zone cannot be obtained then an exception is thrown unless the section * of the formatter is optional. * * During parsing, the zone is parsed and must match a known zone or offset. If the zone cannot be * parsed then an exception is thrown unless the section of the formatter is optional. * * This method is is identical to {@code appendZoneId()} except in the mechanism used to obtain * the zone. * * @return * this, for chaining, not null * @see * #appendZoneId() */ def appendZoneOrOffsetId: DateTimeFormatterBuilder = { appendInternal( new TTBPDateTimeFormatterBuilder.ZoneIdPrinterParser(TemporalQueries.zone, "ZoneOrOffsetId()") ) this } /** * Appends the time-zone name, such as 'British Summer Time', to the formatter. * * This appends an instruction to print the textual name of the zone to the builder. * * During printing, the zone is obtained using a mechanism equivalent to querying the temporal * with {@link TemporalQueries#zoneId()}. If the zone is a {@code ZoneOffset} it will be printed * using the result of {@link ZoneOffset#getId()}. If the zone is not an offset, the textual name * will be looked up for the locale set in the {@link DateTimeFormatter}. If the temporal object * being printed represents an instant, then the text will be the summer or winter time text as * appropriate. If the lookup for text does not find any suitable reuslt, then the {@link * ZoneId#getId() ID} will be printed instead. If the zone cannot be obtained then an exception is * thrown unless the section of the formatter is optional. * * Parsing is not currently supported. * * @param textStyle * the text style to use, not null * @return * this, for chaining, not null */ def appendZoneText(textStyle: TextStyle): DateTimeFormatterBuilder = { appendInternal(new TTBPDateTimeFormatterBuilder.ZoneTextPrinterParser(textStyle)) this } /** * Appends the time-zone name, such as 'British Summer Time', to the formatter. * * This appends an instruction to format/parse the textual name of the zone to the builder. * * During formatting, the zone is obtained using a mechanism equivalent to querying the temporal * with {@link TemporalQueries#zoneId()}. If the zone is a {@code ZoneOffset} it will be printed * using the result of {@link ZoneOffset#getId()}. If the zone is not an offset, the textual name * will be looked up for the locale set in the {@link DateTimeFormatter}. If the temporal object * being printed represents an instant, then the text will be the summer or winter time text as * appropriate. If the lookup for text does not find any suitable result, then the {@link * ZoneId#getId() ID} will be printed instead. If the zone cannot be obtained then an exception is * thrown unless the section of the formatter is optional. * * During parsing, either the textual zone name, the zone ID or the offset is accepted. Many * textual zone names are not unique, such as CST can be for both "Central Standard Time" and * "China Standard Time". In this situation, the zone id will be determined by the region * information from formatter's {@link DateTimeFormatter#getLocale() locale} and the standard zone * id for that area, for example, America/New_York for the America Eastern zone. This method also * allows a set of preferred {@link ZoneId} to be specified for parsing. The matched preferred * zone id will be used if the textual zone name being parsed is not unique. * * If the zone cannot be parsed then an exception is thrown unless the section of the formatter is * optional. * * @param textStyle * the text style to use, not null * @param preferredZones * the set of preferred zone ids, not null * @return * this, for chaining, not null */ def appendZoneText( textStyle: TextStyle, preferredZones: java.util.Set[ZoneId] ): DateTimeFormatterBuilder = { Objects.requireNonNull(preferredZones, "preferredZones") appendInternal(new TTBPDateTimeFormatterBuilder.ZoneTextPrinterParser(textStyle)) this } /** * Appends the chronology ID to the formatter. * * The chronology ID will be output during a print. If the chronology cannot be obtained then an * exception will be thrown. * * @return * this, for chaining, not null */ def appendChronologyId: DateTimeFormatterBuilder = { appendInternal(new TTBPDateTimeFormatterBuilder.ChronoPrinterParser(null)) this } /** * Appends the chronology ID, such as 'ISO' or 'ThaiBuddhist', to the formatter. * * This appends an instruction to format/parse the chronology ID to the builder. * * During printing, the chronology is obtained using a mechanism equivalent to querying the * temporal with {@link TemporalQueries#chronology()}. It will be printed using the result of * {@link Chronology#getId()}. If the chronology cannot be obtained then an exception is thrown * unless the section of the formatter is optional. * * During parsing, the chronology is parsed and must match one of the chronologies in {@link * Chronology#getAvailableChronologies()}. If the chronology cannot be parsed then an exception is * thrown unless the section of the formatter is optional. The parser uses the {@linkplain * #parseCaseInsensitive() case sensitive} setting. * * @return * this, for chaining, not null */ def appendChronologyText(textStyle: TextStyle): DateTimeFormatterBuilder = { Objects.requireNonNull(textStyle, "textStyle") appendInternal(new TTBPDateTimeFormatterBuilder.ChronoPrinterParser(textStyle)) this } /** * Appends a localized date-time pattern to the formatter. * * This appends a localized section to the builder, suitable for outputting a date, time or * date-time combination. The format of the localized section is lazily looked up based on four * items:
  • the {@code dateStyle} specified to this method
  • the {@code timeStyle} * specified to this method
  • the {@code Locale} of the {@code DateTimeFormatter}
  • the {@code * Chronology}, selecting the best available

During formatting, the chronology is * obtained from the temporal object being formatted, which may have been overridden by {@link * DateTimeFormatter#withChronology(Chronology)}. * * During parsing, if a chronology has already been parsed, then it is used. Otherwise the default * from {@code DateTimeFormatter.withChronology(Chronology)} is used, with {@code IsoChronology} * as the fallback. * * Note that this method provides similar functionality to methods on {@code DateFormat} such as * {@link DateFormat#getDateTimeInstance(int, int)}. * * @param dateStyle * the date style to use, null means no date required * @param timeStyle * the time style to use, null means no time required * @return * this, for chaining, not null * @throws IllegalArgumentException * if both the date and time styles are null */ def appendLocalized(dateStyle: FormatStyle, timeStyle: FormatStyle): DateTimeFormatterBuilder = { if (dateStyle == null && timeStyle == null) throw new IllegalArgumentException("Either the date or time style must be non-null") appendInternal(new TTBPDateTimeFormatterBuilder.LocalizedPrinterParser(dateStyle, timeStyle)) this } /** * Appends a character literal to the formatter. * * This character will be output during a print. * * @param literal * the literal to append, not null * @return * this, for chaining, not null */ def appendLiteral(literal: Char): DateTimeFormatterBuilder = { appendInternal(new TTBPDateTimeFormatterBuilder.CharLiteralPrinterParser(literal)) this } /** * Appends a string literal to the formatter. * * This string will be output during a print. * * If the literal is empty, nothing is added to the formatter. * * @param literal * the literal to append, not null * @return * this, for chaining, not null */ def appendLiteral(literal: String): DateTimeFormatterBuilder = { Objects.requireNonNull(literal, "literal") if (literal.length > 0) if (literal.length == 1) appendInternal(new TTBPDateTimeFormatterBuilder.CharLiteralPrinterParser(literal.charAt(0))) else appendInternal(new TTBPDateTimeFormatterBuilder.StringLiteralPrinterParser(literal)) this } /** * Appends all the elements of a formatter to the builder. * * This method has the same effect as appending each of the constituent parts of the formatter * directly to this builder. * * @param formatter * the formatter to add, not null * @return * this, for chaining, not null */ def append(formatter: DateTimeFormatter): DateTimeFormatterBuilder = { Objects.requireNonNull(formatter, "formatter") appendInternal(formatter.toPrinterParser(false)) this } /** * Appends a formatter to the builder which will optionally print/parse. * * This method has the same effect as appending each of the constituent parts directly to this * builder surrounded by an {@link #optionalStart()} and {@link #optionalEnd()}. * * The formatter will print if data is available for all the fields contained within it. The * formatter will parse if the string matches, otherwise no error is returned. * * @param formatter * the formatter to add, not null * @return * this, for chaining, not null */ def appendOptional(formatter: DateTimeFormatter): DateTimeFormatterBuilder = { Objects.requireNonNull(formatter, "formatter") appendInternal(formatter.toPrinterParser(true)) this } /** * Appends the elements defined by the specified pattern to the builder. * * All letters 'A' to 'Z' and 'a' to 'z' are reserved as pattern letters. The characters '{' and * '}' are reserved for future use. The characters '[' and ']' indicate optional patterns. The * following pattern letters are defined:

 Symbol Meaning Presentation Examples
   * ------ ------- ------------ ------- G era number/text 1; 01; AD; Anno Domini y year year 2004;
   * 04 D day-of-year number 189 M month-of-year number/text 7; 07; Jul; July; J d day-of-month
   * number 10
   *
   * Q quarter-of-year number/text 3; 03; Q3 Y week-based-year year 1996; 96 w week-of-year number
   * 27 W week-of-month number 27 e localized day-of-week number 2; Tue; Tuesday; T E day-of-week
   * number/text 2; Tue; Tuesday; T F week-of-month number 3
   *
   * a am-pm-of-day text PM h clock-hour-of-am-pm (1-12) number 12 K hour-of-am-pm (0-11) number 0 k
   * clock-hour-of-am-pm (1-24) number 0
   *
   * H hour-of-day (0-23) number 0 m minute-of-hour number 30 s second-of-minute number 55 S
   * fraction-of-second fraction 978 A milli-of-day number 1234 n nano-of-second number 987654321 N
   * nano-of-day number 1234000000
   *
   * V time-zone ID zone-id America/Los_Angeles; Z; -08:30 z time-zone name zone-name Pacific
   * Standard Time; PST X zone-offset 'Z' for zero offset-X Z; -08; -0830; -08:30; -083015;
   * -08:30:15; x zone-offset offset-x +0000; -08; -0830; -08:30; -083015; -08:30:15; Z zone-offset
   * offset-Z +0000; -0800; -08:00;
   *
   * p pad next pad modifier 1
   *
   * ' escape for text delimiter '' single quote literal ' [ optional section start ] optional
   * section end {} reserved for future use 
* * The count of pattern letters determine the format. * * Text: The text style is determined based on the number of pattern letters used. Less * than 4 pattern letters will use the {@link TextStyle#SHORT short form}. Exactly 4 pattern * letters will use the {@link TextStyle#FULL full form}. Exactly 5 pattern letters will use the * {@link TextStyle#NARROW narrow form}. * * Number: If the count of letters is one, then the value is printed using the minimum * number of digits and without padding as per {@link #appendValue(TemporalField)}. Otherwise, the * count of digits is used as the width of the output field as per {@link * #appendValue(TemporalField, int)}. * * Number/Text: If the count of pattern letters is 3 or greater, use the Text rules above. * Otherwise use the Number rules above. * * Fraction: Outputs the nano-of-second field as a fraction-of-second. The nano-of-second * value has nine digits, thus the count of pattern letters is from 1 to 9. If it is less than 9, * then the nano-of-second value is truncated, with only the most significant digits being output. * When parsing in strict mode, the number of parsed digits must match the count of pattern * letters. When parsing in lenient mode, the number of parsed digits must be at least the count * of pattern letters, up to 9 digits. * * Year: The count of letters determines the minimum field width below which padding is * used. If the count of letters is two, then a {@link #appendValueReduced reduced} two digit form * is used. For printing, this outputs the rightmost two digits. For parsing, this will parse * using the base value of 2000, resulting in a year within the range 2000 to 2099 inclusive. If * the count of letters is less than four (but not two), then the sign is only output for negative * years as per {@link SignStyle#NORMAL}. Otherwise, the sign is output if the pad width is * exceeded, as per {@link SignStyle#EXCEEDS_PAD} * * ZoneId: This outputs the time-zone ID, such as 'Europe/Paris'. If the count of letters * is two, then the time-zone ID is output. Any other count of letters throws {@code * IllegalArgumentException}.
 Pattern Equivalent builder methods VV appendZoneId() 
* * Zone names: This outputs the display name of the time-zone ID. If the count of letters * is one, two or three, then the short name is output. If the count of letters is four, then the * full name is output. Five or more letters throws {@code IllegalArgumentException}.
   * Pattern Equivalent builder methods z appendZoneText(TextStyle.SHORT) zz
   * appendZoneText(TextStyle.SHORT) zzz appendZoneText(TextStyle.SHORT) zzzz
   * appendZoneText(TextStyle.FULL) 
* * Offset X and x: This formats the offset based on the number of pattern letters. One * letter outputs just the hour', such as '+01', unless the minute is non-zero in which case the * minute is also output, such as '+0130'. Two letters outputs the hour and minute, without a * colon, such as '+0130'. Three letters outputs the hour and minute, with a colon, such as * '+01:30'. Four letters outputs the hour and minute and optional second, without a colon, such * as '+013015'. Five letters outputs the hour and minute and optional second, with a colon, such * as '+01:30:15'. Six or more letters throws {@code IllegalArgumentException}. Pattern letter 'X' * (upper case) will output 'Z' when the offset to be output would be zero, whereas pattern letter * 'x' (lower case) will output '+00', '+0000', or '+00:00'.
 Pattern Equivalent builder
   * methods X appendOffset("+HHmm","Z") XX appendOffset("+HHMM","Z") XXX appendOffset("+HH:MM","Z")
   * XXXX appendOffset("+HHMMss","Z") XXXXX appendOffset("+HH:MM:ss","Z") x
   * appendOffset("+HHmm","+00") xx appendOffset("+HHMM","+0000") xxx
   * appendOffset("+HH:MM","+00:00") xxxx appendOffset("+HHMMss","+0000") xxxxx
   * appendOffset("+HH:MM:ss","+00:00") 
* * Offset Z: This formats the offset based on the number of pattern letters. One, two or * three letters outputs the hour and minute, without a colon, such as '+0130'. Four or more * letters throws {@code IllegalArgumentException}. The output will be '+0000' when the offset is * zero.
 Pattern Equivalent builder methods Z appendOffset("+HHMM","+0000") ZZ
   * appendOffset("+HHMM","+0000") ZZZ appendOffset("+HHMM","+0000") 
* * Optional section: The optional section markers work exactly like calling {@link * #optionalStart()} and {@link #optionalEnd()}. * * Pad modifier: Modifies the pattern that immediately follows to be padded with spaces. * The pad width is determined by the number of pattern letters. This is the same as calling * {@link #padNext(int)}. * * For example, 'ppH' outputs the hour-of-day padded on the left with spaces to a width of 2. * * Any unrecognized letter is an error. Any non-letter character, other than '[', ']', '{', '}' * and the single quote will be output directly. Despite this, it is recommended to use single * quotes around all characters that you want to output directly to ensure that future changes do * not break your application. * * Note that the pattern string is similar, but not identical, to {@link * java.text.SimpleDateFormat SimpleDateFormat}. The pattern string is also similar, but not * identical, to that defined by the Unicode Common Locale Data Repository (CLDR/LDML). Pattern * letters 'E' and 'u' are merged, which changes the meaning of "E" and "EE" to be numeric. * Pattern letters 'X' is aligned with Unicode CLDR/LDML, which affects pattern 'X'. Pattern * letter 'y' and 'Y' parse years of two digits and more than 4 digits differently. Pattern * letters 'n', 'A', 'N', 'I' and 'p' are added. Number types will reject large numbers. * * @param pattern * the pattern to add, not null * @return * this, for chaining, not null * @throws IllegalArgumentException * if the pattern is invalid */ def appendPattern(pattern: String): DateTimeFormatterBuilder = { Objects.requireNonNull(pattern, "pattern") parsePattern(pattern) this } private def parsePattern(pattern: String): Unit = { var pos: Int = 0 while (pos < pattern.length) { var cur: Char = pattern.charAt(pos) if ((cur >= 'A' && cur <= 'Z') || (cur >= 'a' && cur <= 'z')) { var start: Int = pos pos += 1 while (pos < pattern.length && pattern.charAt(pos) == cur) pos += 1 var count: Int = pos - start if (cur == 'p') { var pad: Int = 0 if (pos < pattern.length) { cur = pattern.charAt(pos) if ((cur >= 'A' && cur <= 'Z') || (cur >= 'a' && cur <= 'z')) { pad = count start = pos pos += 1 while (pos < pattern.length && pattern.charAt(pos) == cur) pos += 1 count = pos - start } } if (pad == 0) throw new IllegalArgumentException( s"Pad letter 'p' must be followed by valid pad pattern: $pattern" ) padNext(pad) } val field: TemporalField = DateTimeFormatterBuilder.FIELD_MAP.get(cur) if (field != null) parseField(cur, count, field) else if (cur == 'z') if (count > 4) throw new IllegalArgumentException(s"Too many pattern letters: $cur") else if (count == 4) appendZoneText(TextStyle.FULL) else appendZoneText(TextStyle.SHORT) else if (cur == 'V') { if (count != 2) throw new IllegalArgumentException(s"Pattern letter count must be 2: $cur") appendZoneId } else if (cur == 'Z') if (count < 4) appendOffset("+HHMM", "+0000") else if (count == 4) appendLocalizedOffset(TextStyle.FULL) else if (count == 5) appendOffset("+HH:MM:ss", "Z") else throw new IllegalArgumentException(s"Too many pattern letters: $cur") else if (cur == 'O') if (count == 1) appendLocalizedOffset(TextStyle.SHORT) else if (count == 4) appendLocalizedOffset(TextStyle.FULL) else throw new IllegalArgumentException(s"Pattern letter count must be 1 or 4: $cur") else if (cur == 'X') { if (count > 5) throw new IllegalArgumentException(s"Too many pattern letters: $cur") appendOffset(TTBPDateTimeFormatterBuilder.OffsetIdPrinterParser .PATTERNS(count + (if (count == 1) 0 else 1)), "Z" ) } else if (cur == 'x') { if (count > 5) throw new IllegalArgumentException(s"Too many pattern letters: $cur") val zero: String = if (count == 1) "+00" else if (count % 2 == 0) "+0000" else "+00:00" appendOffset(TTBPDateTimeFormatterBuilder.OffsetIdPrinterParser .PATTERNS(count + (if (count == 1) 0 else 1)), zero ) } else if (cur == 'W') { if (count > 1) throw new IllegalArgumentException(s"Too many pattern letters: $cur") appendInternal(new TTBPDateTimeFormatterBuilder.WeekFieldsPrinterParser('W', count)) } else if (cur == 'w') { if (count > 2) throw new IllegalArgumentException(s"Too many pattern letters: $cur") appendInternal(new TTBPDateTimeFormatterBuilder.WeekFieldsPrinterParser('w', count)) } else if (cur == 'Y') appendInternal(new TTBPDateTimeFormatterBuilder.WeekFieldsPrinterParser('Y', count)) else throw new IllegalArgumentException(s"Unknown pattern letter: $cur") pos -= 1 } else if (cur == '\'') { val start: Int = pos pos += 1 // Size improved var break = false while (!break && pos < pattern.length) { if (pattern.charAt(pos) == '\'') if (pos + 1 < pattern.length && pattern.charAt(pos + 1) == '\'') pos += 1 else break = true if (!break) pos += 1 } if (pos >= pattern.length) throw new IllegalArgumentException( s"Pattern ends with an incomplete string literal: $pattern" ) val str: String = pattern.substring(start + 1, pos) if (str.length == 0) appendLiteral('\'') else appendLiteral(str.replace("''", "'")) } else if (cur == '[') optionalStart() else if (cur == ']') { if (active.parent == null) throw new IllegalArgumentException("Pattern invalid as it contains ] without previous [") optionalEnd() } else if (cur == '{' || cur == '}' || cur == '#') throw new IllegalArgumentException(s"Pattern includes reserved character: '$cur'") else appendLiteral(cur) pos += 1 } } private def parseField(cur: Char, count: Int, field: TemporalField): Unit = { val exc = new IllegalArgumentException(s"Too many pattern letters: $cur") cur match { case 'u' | 'y' => if (count == 2) appendValueReduced(field, 2, 2, TTBPDateTimeFormatterBuilder.ReducedPrinterParser.BASE_DATE ) else if (count < 4) appendValue(field, count, 19, SignStyle.NORMAL) else appendValue(field, count, 19, SignStyle.EXCEEDS_PAD) case 'M' | 'Q' => count match { case 1 => appendValue(field) case 2 => appendValue(field, 2) case 3 => appendText(field, TextStyle.SHORT) case 4 => appendText(field, TextStyle.FULL) case 5 => appendText(field, TextStyle.NARROW) case _ => throw exc } case 'L' | 'q' => count match { case 1 => appendValue(field) case 2 => appendValue(field, 2) case 3 => appendText(field, TextStyle.SHORT_STANDALONE) case 4 => appendText(field, TextStyle.FULL_STANDALONE) case 5 => appendText(field, TextStyle.NARROW_STANDALONE) case _ => throw exc } case 'e' => count match { case 1 | 2 => appendInternal(new TTBPDateTimeFormatterBuilder.WeekFieldsPrinterParser('e', count)) case 3 => appendText(field, TextStyle.SHORT) case 4 => appendText(field, TextStyle.FULL) case 5 => appendText(field, TextStyle.NARROW) case _ => throw exc } case 'c' => count match { case 1 => appendInternal(new TTBPDateTimeFormatterBuilder.WeekFieldsPrinterParser('c', count)) case 2 => throw new IllegalArgumentException(s"Invalid number of pattern letters: $cur") case 3 => appendText(field, TextStyle.SHORT_STANDALONE) case 4 => appendText(field, TextStyle.FULL_STANDALONE) case 5 => appendText(field, TextStyle.NARROW_STANDALONE) case _ => throw exc } case 'a' => if (count == 1) appendText(field, TextStyle.SHORT) else throw exc case 'E' | 'G' => count match { case 1 | 2 | 3 => appendText(field, TextStyle.SHORT) case 4 => appendText(field, TextStyle.FULL) case 5 => appendText(field, TextStyle.NARROW) case _ => throw exc } case 'S' => appendFraction(ChronoField.NANO_OF_SECOND, count, count, false) case 'F' => if (count == 1) appendValue(field) else throw exc case 'd' | 'h' | 'H' | 'k' | 'K' | 'm' | 's' => if (count == 1) appendValue(field) else if (count == 2) appendValue(field, count) else throw exc case 'D' => if (count == 1) appendValue(field) else if (count <= 3) appendValue(field, count) else throw exc case _ => if (count == 1) appendValue(field) else appendValue(field, count) } () } /** * Causes the next added printer/parser to pad to a fixed width using a space. * * This padding will pad to a fixed width using spaces. * * During formatting, the decorated element will be output and then padded to the specified width. * An exception will be thrown during printing if the pad width is exceeded. * * During parsing, the padding and decorated element are parsed. If parsing is lenient, then the * pad width is treated as a maximum. If parsing is case insensitive, then the pad character is * matched ignoring case. The padding is parsed greedily. Thus, if the decorated element starts * with the pad character, it will not be parsed. * * @param padWidth * the pad width, 1 or greater * @return * this, for chaining, not null * @throws IllegalArgumentException * if pad width is too small */ def padNext(padWidth: Int): DateTimeFormatterBuilder = padNext(padWidth, ' ') /** * Causes the next added printer/parser to pad to a fixed width. * * This padding is intended for padding other than zero-padding. Zero-padding should be achieved * using the appendValue methods. * * During formatting, the decorated element will be output and then padded to the specified width. * An exception will be thrown during printing if the pad width is exceeded. * * During parsing, the padding and decorated element are parsed. If parsing is lenient, then the * pad width is treated as a maximum. If parsing is case insensitive, then the pad character is * matched ignoring case. The padding is parsed greedily. Thus, if the decorated element starts * with the pad character, it will not be parsed. * * @param padWidth * the pad width, 1 or greater * @param padChar * the pad character * @return * this, for chaining, not null * @throws IllegalArgumentException * if pad width is too small */ def padNext(padWidth: Int, padChar: Char): DateTimeFormatterBuilder = { if (padWidth < 1) throw new IllegalArgumentException(s"The pad width must be at least one but was $padWidth") active.padNextWidth = padWidth active.padNextChar = padChar active.valueParserIndex = -1 this } /** * Mark the start of an optional section. * * The output of printing can include optional sections, which may be nested. An optional section * is started by calling this method and ended by calling {@link #optionalEnd()} or by ending the * build process. * * All elements in the optional section are treated as optional. During printing, the section is * only output if data is available in the {@code TemporalAccessor} for all the elements in the * section. During parsing, the whole section may be missing from the parsed string. * * For example, consider a builder setup as {@code * builder.appendValue(HOUR_OF_DAY,2).optionalStart().appendValue(MINUTE_OF_HOUR,2)}. The optional * section ends automatically at the end of the builder. During printing, the minute will only be * output if its value can be obtained from the date-time. During parsing, the input will be * successfully parsed whether the minute is present or not. * * @return * this, for chaining, not null */ def optionalStart(): DateTimeFormatterBuilder = { active.valueParserIndex = -1 active = new DateTimeFormatterBuilder(active, true) this } /** * Ends an optional section. * * The output of printing can include optional sections, which may be nested. An optional section * is started by calling {@link #optionalStart()} and ended using this method (or at the end of * the builder). * * Calling this method without having previously called {@code optionalStart} will throw an * exception. Calling this method immediately after calling {@code optionalStart} has no effect on * the formatter other than ending the (empty) optional section. * * All elements in the optional section are treated as optional. During printing, the section is * only output if data is available in the {@code TemporalAccessor} for all the elements in the * section. During parsing, the whole section may be missing from the parsed string. * * For example, consider a builder setup as {@code * builder.appendValue(HOUR_OF_DAY,2).optionalStart().appendValue(MINUTE_OF_HOUR,2).optionalEnd()}. * During printing, the minute will only be output if its value can be obtained from the * date-time. During parsing, the input will be successfully parsed whether the minute is present * or not. * * @return * this, for chaining, not null * @throws IllegalStateException * if there was no previous call to { @code optionalStart} */ def optionalEnd(): DateTimeFormatterBuilder = { if (active.parent == null) throw new IllegalStateException( "Cannot call optionalEnd() as there was no previous call to optionalStart()" ) if (active.printerParsers.size > 0) { val cpp: TTBPDateTimeFormatterBuilder.CompositePrinterParser = new TTBPDateTimeFormatterBuilder.CompositePrinterParser(active.printerParsers, active.optional ) active = active.parent appendInternal(cpp) } else active = active.parent this } /** * Appends a printer and/or parser to the internal list handling padding. * * @param pp * the printer-parser to add, not null * @return * the index into the active parsers list */ private def appendInternal(pp: DateTimePrinterParser): Int = { var _pp = pp Objects.requireNonNull(_pp, "pp") if (active.padNextWidth > 0) { if (_pp != null) _pp = new TTBPDateTimeFormatterBuilder.PadPrinterParserDecorator(_pp, active.padNextWidth, active.padNextChar ) active.padNextWidth = 0 active.padNextChar = 0 } active.printerParsers.add(_pp) active.valueParserIndex = -1 active.printerParsers.size - 1 } /** * Completes this builder by creating the DateTimeFormatter using the default locale. * * This will create a formatter with the default locale. Numbers will be printed and parsed using * the standard non-localized set of symbols. * * Calling this method will end any open optional sections by repeatedly calling {@link * #optionalEnd()} before creating the formatter. * * This builder can still be used after creating the formatter if desired, although the state may * have been changed by calls to {@code optionalEnd}. * * @return * the created formatter, not null */ def toFormatter: DateTimeFormatter = toFormatter(Locale.getDefault) /** * Completes this builder by creating the DateTimeFormatter using the specified locale. * * This will create a formatter with the specified locale. Numbers will be printed and parsed * using the standard non-localized set of symbols. * * Calling this method will end any open optional sections by repeatedly calling {@link * #optionalEnd()} before creating the formatter. * * This builder can still be used after creating the formatter if desired, although the state may * have been changed by calls to {@code optionalEnd}. * * @param locale * the locale to use for formatting, not null * @return * the created formatter, not null */ def toFormatter(locale: Locale): DateTimeFormatter = { Objects.requireNonNull(locale, "locale") while (active.parent != null) optionalEnd() val pp: TTBPDateTimeFormatterBuilder.CompositePrinterParser = new TTBPDateTimeFormatterBuilder.CompositePrinterParser(printerParsers, false) new DateTimeFormatter(pp, locale, DecimalStyle.STANDARD, ResolverStyle.SMART, null, null, null) } private[format] def toFormatter(style: ResolverStyle): DateTimeFormatter = toFormatter.withResolverStyle(style) }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy