/* * ----------------------------------------------------------------------- * Copyright © 2013-2016 Meno Hochschild, <http://www.menodata.de/> * ----------------------------------------------------------------------- * This file (ChronoFormatter.java) is part of project Time4J. * * Time4J is free software: You can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation, either version 2.1 of the License, or * (at your option) any later version. * * Time4J is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with Time4J. If not, see <http://www.gnu.org/licenses/>. * ----------------------------------------------------------------------- */ package net.time4j.format.expert; import net.time4j.CalendarUnit; import net.time4j.DayPeriod; import net.time4j.GeneralTimestamp; import net.time4j.Moment; import net.time4j.PlainDate; import net.time4j.PlainTime; import net.time4j.PlainTimestamp; import net.time4j.base.TimeSource; import net.time4j.base.UnixTime; import net.time4j.engine.AttributeKey; import net.time4j.engine.AttributeQuery; import net.time4j.engine.BridgeChronology; import net.time4j.engine.CalendarFamily; import net.time4j.engine.CalendarVariant; import net.time4j.engine.Calendrical; import net.time4j.engine.ChronoCondition; import net.time4j.engine.ChronoDisplay; import net.time4j.engine.ChronoElement; import net.time4j.engine.ChronoEntity; import net.time4j.engine.ChronoException; import net.time4j.engine.ChronoExtension; import net.time4j.engine.ChronoFunction; import net.time4j.engine.ChronoMerger; import net.time4j.engine.Chronology; import net.time4j.engine.DisplayStyle; import net.time4j.engine.StartOfDay; import net.time4j.engine.TimeAxis; import net.time4j.engine.ValidationElement; import net.time4j.engine.VariantSource; import net.time4j.format.Attributes; import net.time4j.format.CalendarText; import net.time4j.format.DisplayMode; import net.time4j.format.Leniency; import net.time4j.format.LocalizedPatternSupport; import net.time4j.format.NumericalElement; import net.time4j.format.OutputContext; import net.time4j.format.PluralCategory; import net.time4j.format.RawValues; import net.time4j.format.TemporalFormatter; import net.time4j.format.TextElement; import net.time4j.format.TextWidth; import net.time4j.history.ChronoHistory; import net.time4j.history.internal.HistoricAttribute; import net.time4j.history.internal.HistorizedElement; import net.time4j.tz.TZID; import net.time4j.tz.Timezone; import net.time4j.tz.TransitionStrategy; import java.io.IOException; import java.math.BigDecimal; import java.text.AttributedCharacterIterator; import java.text.AttributedString; import java.text.DateFormat; import java.text.FieldPosition; import java.text.Format; import java.text.ParseException; import java.text.ParsePosition; import java.time.ZoneId; import java.time.temporal.TemporalAccessor; import java.time.temporal.TemporalQueries; import java.util.ArrayList; import java.util.Collections; import java.util.Deque; import java.util.HashMap; import java.util.LinkedHashSet; import java.util.LinkedList; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.NoSuchElementException; import java.util.Set; import java.util.function.IntPredicate; import java.util.function.Supplier; import static net.time4j.format.CalendarText.ISO_CALENDAR_TYPE; /** * <p>Represents a chronological format for the conversion between a * chronological text and the chronological value of type T. </p> * * <p>An instance can either be created via a {@code Builder} obtainable * by {@link #setUp(Class, Locale)} or by some static factory methods. * The class {@link Iso8601Format} provides additional formatters * adapted for the ISO-8601-standard. </p> * * <p><strong>Interoperability note:</strong> </p> * * <p>The static methods {@link #setUp(Chronology, Locale)} and * {@link #ofPattern(String, PatternType, Locale, Chronology)} also * allow the usage of foreign types by specifying a suitable bridge * chronology. Even the old type {@code java.util.Date} can be * used in conjunction with this formatter. Example: </p> * * <pre> * ChronoFormatter<java.util.Date> f = * ChronoFormatter.ofPattern( * "MM/dd/yyyy", * PatternType.CLDR, * Locale.US, * Moment.axis(TemporalType.JAVA_UTIL_DATE)) * .withDefault(PlainTime.COMPONENT, PlainTime.midnightAtStartOfDay()) * .withStdTimezone(); * System.out.println(f.format(new java.util.Date())); * java.util.Date d = f.parse("10/26/2016"); * </pre> * * @param <T> generic type of chronological entity * @author Meno Hochschild * @since 3.0 * @doctags.concurrency {immutable} */ /*[deutsch] * <p>Repräsentiert ein Zeitformat zur Konversion zwischen einem * chronologischen Text und einem chronologischen Wert des Typs T. </p> * * <p>Eine Instanz kann entweder über einen {@code Builder} via * {@link #setUp(Class, Locale)} oder über statische Fabrikmethoden * erzeugt werden. Die Klasse {@link Iso8601Format} liefert dazu * speziell für die ISO-8601-Norm vordefinierte Formate. </p> * * <p><strong>Interoperabilitätsnotiz:</strong> </p> * * <p>Die statischen Methoden {@link #setUp(Chronology, Locale)} und * {@link #ofPattern(String, PatternType, Locale, Chronology)} erlauben * auch die Verwendung von externen Typen, indem eine geeignete Chronologie * angegegeben wird. Sogar der alte Typ {@code java.util.Date} kann in * Verbindung mit diesem Formatierer verwendet werden. Beispiel: </p> * * <pre> * ChronoFormatter<java.util.Date> f = * ChronoFormatter.ofPattern( * "MM/dd/yyyy", * PatternType.CLDR, * Locale.US, * Moment.axis(TemporalType.JAVA_UTIL_DATE)) * .withDefault(PlainTime.COMPONENT, PlainTime.midnightAtStartOfDay()) * .withStdTimezone(); * System.out.println(f.format(new java.util.Date())); * java.util.Date d = f.parse("10/26/2016"); * </pre> * * @param <T> generic type of chronological entity * @author Meno Hochschild * @since 3.0 * @doctags.concurrency {immutable} */ public final class ChronoFormatter<T> implements ChronoPrinter<T>, ChronoParser<T>, TemporalFormatter<T> { //~ Instanzvariablen -------------------------------------------------- private final Chronology<T> chronology; private final OverrideHandler<?> overrideHandler; private final AttributeSet globalAttributes; private final List<FormatStep> steps; private final Map<ChronoElement<?>, Object> defaults; private final FractionProcessor fracproc; // serves for optimization private final boolean hasOptionals; private final boolean needsHistorization; private final boolean needsExtensions; private final int countOfElements; private final Leniency leniency; private final boolean indexable; private final boolean trailing; private final boolean noPreparser; private final Chronology<?> deepestParser; private final int stepCount; private final boolean singleStepMode; //~ Konstruktoren ----------------------------------------------------- // Aufruf durch Builder private ChronoFormatter( Chronology<T> chronology, Chronology<?> override, Locale locale, List<FormatStep> steps, Map<ChronoElement<?>, Object> defaults, Attributes globals, Chronology<?> deepestParser ) { super(); if (chronology == null) { throw new NullPointerException("Missing chronology."); } else if (steps.isEmpty()) { throw new IllegalStateException("No format processors defined."); } this.chronology = chronology; this.overrideHandler = OverrideHandler.of(override); this.deepestParser = deepestParser; this.globalAttributes = AttributeSet.createDefaults((override == null) ? chronology : override, globals, locale); this.leniency = this.globalAttributes.get(Attributes.LENIENCY, Leniency.SMART); this.defaults = Collections.unmodifiableMap(defaults); FractionProcessor fp = null; boolean ho = false; boolean nh = false; boolean dp = false; int co = 0; boolean ix = true; for (FormatStep step : steps) { if ((fp == null) && step.getProcessor() instanceof FractionProcessor) { fp = FractionProcessor.class.cast(step.getProcessor()); } if (!ho && (step.getLevel() > 0)) { ho = true; } ChronoElement<?> element = step.getProcessor().getElement(); if (element != null) { co++; if (ix && !ParsedValues.isIndexed(element)) { ix = false; } if (element instanceof HistorizedElement) { nh = true; } else if (!dp && element.name().endsWith("_DAY_PERIOD")) { dp = true; } } } this.fracproc = fp; this.hasOptionals = ho; this.needsHistorization = nh; this.countOfElements = co; this.indexable = ix; Class<?> chronoType = chronology.getChronoType(); if (PlainDate.class.isAssignableFrom(chronoType)) { this.needsExtensions = (nh || (chronology.getExtensions().size() > 2)); } else if (PlainTime.class.isAssignableFrom(chronoType)) { this.needsExtensions = (dp || (chronology.getExtensions().size() > 1)); } else if (PlainTimestamp.class.isAssignableFrom(chronoType)) { this.needsExtensions = (dp || nh || (chronology.getExtensions().size() > 3)); } else { this.needsExtensions = true; } this.trailing = this.globalAttributes.get(Attributes.TRAILING_CHARACTERS, Boolean.FALSE).booleanValue(); this.noPreparser = this.hasNoPreparser(); this.stepCount = steps.size(); this.steps = this.freeze(steps); this.singleStepMode = this.getSingleStepMode(); } // Aufruf durch withAttribute-Methoden private ChronoFormatter( ChronoFormatter<T> old, Attributes attrs ) { this(old, old.globalAttributes.withAttributes(attrs), null); } // Aufruf durch with(Locale)-Methode private ChronoFormatter( ChronoFormatter<T> old, AttributeSet globalAttributes ) { this(old, globalAttributes, null); } // Aufruf durch withGregorianCutOver private ChronoFormatter( ChronoFormatter<T> old, AttributeSet globalAttributes, ChronoHistory history ) { super(); if (globalAttributes == null) { throw new NullPointerException("Missing global format attributes."); } this.chronology = old.chronology; this.overrideHandler = old.overrideHandler; this.deepestParser = old.deepestParser; this.globalAttributes = globalAttributes; this.leniency = this.globalAttributes.get(Attributes.LENIENCY, Leniency.SMART); this.defaults = Collections.unmodifiableMap(new NonAmbivalentMap(old.defaults)); this.fracproc = old.fracproc; this.hasOptionals = old.hasOptionals; this.needsHistorization = (old.needsHistorization || (history != null)); this.needsExtensions = (old.needsExtensions || this.needsHistorization); this.countOfElements = old.countOfElements; // update extension elements and historizable elements int len = old.steps.size(); List<FormatStep> copy = new ArrayList<>(old.steps); boolean ix = old.indexable; for (int i = 0; i < len; i++) { FormatStep step = copy.get(i); ChronoElement<?> element = step.getProcessor().getElement(); Chronology<?> c = this.chronology; if (c instanceof BridgeChronology) { c = c.preparser(); } if (c == Moment.axis()) { c = c.preparser(); } // update extension elements if ( (element != null) // no literal steps etc. && !c.isRegistered(element) ) { // example: week-of-year dependent on LOCALE for (ChronoExtension ext : c.getExtensions()) { if (ext.getElements(old.getLocale(), old.globalAttributes).contains(element)) { Set<ChronoElement<?>> elements = ext.getElements(globalAttributes.getLocale(), globalAttributes); for (ChronoElement<?> e : elements) { if (e.name().equals(element.name())) { if (e != element) { copy.set(i, step.updateElement(e)); ix = false; } break; } } break; } } } // update historizable elements if (history != null) { ix = false; ChronoElement<?> replacement = null; if (element == PlainDate.YEAR) { replacement = history.yearOfEra(); } else if ( (element == PlainDate.MONTH_OF_YEAR) || (element == PlainDate.MONTH_AS_NUMBER) ) { replacement = history.month(); } else if (element == PlainDate.DAY_OF_MONTH) { replacement = history.dayOfMonth(); } else if (element == PlainDate.DAY_OF_YEAR) { replacement = history.dayOfYear(); } if (replacement != null) { copy.set(i, step.updateElement(replacement)); } } } this.indexable = ix; this.trailing = this.globalAttributes.get(Attributes.TRAILING_CHARACTERS, Boolean.FALSE).booleanValue(); this.noPreparser = this.hasNoPreparser(); this.stepCount = copy.size(); this.steps = this.freeze(copy); this.singleStepMode = this.getSingleStepMode(); } // Aufruf durch withDefault private ChronoFormatter( ChronoFormatter<T> formatter, Map<ChronoElement<?>, Object> defaultMap ) { super(); Chronology<?> overrideCalendar = ( (formatter.overrideHandler == null) ? null : formatter.overrideHandler.getCalendarOverride()); for (ChronoElement<?> element : defaultMap.keySet()) { checkElement(formatter.chronology, overrideCalendar, element); } this.chronology = formatter.chronology; this.overrideHandler = formatter.overrideHandler; this.deepestParser = formatter.deepestParser; this.globalAttributes = formatter.globalAttributes; this.leniency = formatter.leniency; this.fracproc = formatter.fracproc; this.hasOptionals = formatter.hasOptionals; this.needsHistorization = formatter.needsHistorization; this.needsExtensions = formatter.needsExtensions; this.countOfElements = formatter.countOfElements; this.trailing = formatter.trailing; Map<ChronoElement<?>, Object> map = new HashMap<>(formatter.defaults); boolean ix = formatter.indexable; for (ChronoElement<?> element : defaultMap.keySet()) { Object replacement = defaultMap.get(element); if (replacement == null) { map.remove(element); } else { map.put(element, replacement); ix = ix && ParsedValues.isIndexed(element); } } this.defaults = Collections.unmodifiableMap(map); this.indexable = ix; this.noPreparser = this.hasNoPreparser(); this.stepCount = formatter.stepCount; this.steps = this.freeze(formatter.steps); this.singleStepMode = this.getSingleStepMode(); } //~ Methoden ---------------------------------------------------------- /** * <p>Returns the associated chronology. </p> * * @return chronology to be used for formatting associated objects */ /*[deutsch] * <p>Ermittelt die zugehörige Chronologie. </p> * * @return chronology to be used for formatting associated objects */ public Chronology<T> getChronology() { return this.chronology; } /** * <p>Returns the locale setting. </p> * * <p>If there is just a reference to ISO-8601 without any concrete * language then this method will yield {@code Locale.ROOT}. </p> * * @return Locale (empty if related to ISO-8601, never {@code null}) */ /*[deutsch] * <p>Ermittelt die Sprach- und Ländereinstellung. </p> * * <p>Falls ein Bezug zu ISO-8601 ohne eine konkrete Sprache vorliegt, * liefert die Methode {@code Locale.ROOT}. </p> * * @return Locale (empty if related to ISO-8601, never {@code null}) */ public Locale getLocale() { return this.globalAttributes.getLocale(); } /** * <p>Returns the global format attributes which are active if they are not * overridden by sectional attributes. </p> * * <p>The global attributes can be adjusted by a suitable * {@code with()}-method. Following attributes are predefined: </p> * * <table border="1" style="margin-top:5px;"> * <caption>Legend</caption> * <tr> * <td>{@link Attributes#CALENDAR_TYPE}</td> * <td>read-only, dependent on associated chronology</td> * </tr> * <tr> * <td>{@link Attributes#LANGUAGE}</td> * <td>dependent on associated locale</td> * </tr> * <tr> * <td>{@link Attributes#DECIMAL_SEPARATOR}</td> * <td>dependent on associated locale</td> * </tr> * <tr> * <td>{@link Attributes#ZERO_DIGIT}</td> * <td>dependent on associated locale</td> * </tr> * <tr> * <td>{@link Attributes#LENIENCY}</td> * <td>{@link Leniency#SMART}</td> * </tr> * <tr> * <td>{@link Attributes#PARSE_CASE_INSENSITIVE}</td> * <td>{@code true}</td> * </tr> * <tr> * <td>{@link Attributes#PARSE_PARTIAL_COMPARE}</td> * <td>{@code false}</td> * </tr> * <tr> * <td>{@link Attributes#TEXT_WIDTH}</td> * <td>{@link TextWidth#WIDE}</td> * </tr> * <tr> * <td>{@link Attributes#OUTPUT_CONTEXT}</td> * <td>{@link OutputContext#FORMAT}</td> * </tr> * <tr> * <td>{@link Attributes#PAD_CHAR}</td> * <td>(SPACE)</td> * </tr> * </table> * * @return global control attributes valid for the whole formatter * (can be overridden by sectional attributes however) * @see #getChronology() * @see #getLocale() */ /*[deutsch] * <p>Ermittelt die globalen Standardattribute, welche genau dann wirksam sind, * wenn sie nicht durch sektionale Attribute überschrieben werden. </p> * * <p>Die Standard-Attribute können über eine geeignete * {@code with()}-Methode geändert werden. Folgende Attribute * werden vordefiniert: </p> * * <table border="1" style="margin-top:5px;"> * <caption>Legende</caption> * <tr> * <td>{@link Attributes#CALENDAR_TYPE}</td> * <td>schreibgeschützt, abhängig von der Chronologie</td> * </tr> * <tr> * <td>{@link Attributes#LANGUAGE}</td> * <td>abhängig von der Sprache</td> * </tr> * <tr> * <td>{@link Attributes#DECIMAL_SEPARATOR}</td> * <td>abhängig von der Sprache</td> * </tr> * <tr> * <td>{@link Attributes#ZERO_DIGIT}</td> * <td>abhängig von der Sprache</td> * </tr> * <tr> * <td>{@link Attributes#LENIENCY}</td> * <td>{@link Leniency#SMART}</td> * </tr> * <tr> * <td>{@link Attributes#PARSE_CASE_INSENSITIVE}</td> * <td>{@code true}</td> * </tr> * <tr> * <td>{@link Attributes#PARSE_PARTIAL_COMPARE}</td> * <td>{@code false}</td> * </tr> * <tr> * <td>{@link Attributes#TEXT_WIDTH}</td> * <td>{@link TextWidth#WIDE}</td> * </tr> * <tr> * <td>{@link Attributes#OUTPUT_CONTEXT}</td> * <td>{@link OutputContext#FORMAT}</td> * </tr> * <tr> * <td>{@link Attributes#PAD_CHAR}</td> * <td>Leerzeichen (SPACE)</td> * </tr> * </table> * * @return global control attributes valid for the whole formatter * (can be overridden by sectional attributes however) * @see #getChronology() * @see #getLocale() */ @Override public AttributeQuery getAttributes() { return this.globalAttributes; } @Override public String format(T formattable) { ChronoDisplay display = this.display(formattable, this.globalAttributes); return this.format0(display); } /** * <p>Prints given general timestamp. </p> * * @param tsp general timestamp as combination of a date and a time * @return formatted string * @throws IllegalArgumentException if the timestamp is not formattable with this formatter * @since 3.11/4.8 */ /*[deutsch] * <p>Formatiert den angegebenen allgemeinen Zeitstempel. </p> * * @param tsp general timestamp as combination of a date and a time * @return formatted string * @throws IllegalArgumentException if the timestamp is not formattable with this formatter * @since 3.11/4.8 */ public String format(GeneralTimestamp<?> tsp) { return this.format0(tsp); } /** * <p>Prints the given Threeten-object. </p> * * @param formattable temporal accessor * @return formatted text * @throws IllegalArgumentException if given argument cannot be formatted * @since 4.0 * @deprecated Use alternative approach based on static factory methods and bridge chronology */ /*[deutsch] * <p>Formatiert das angegebene Threeten-Objekt. </p> * * @param formattable temporal accessor * @return formatted text * @throws IllegalArgumentException if given argument cannot be formatted * @since 4.0 * @deprecated Use alternative approach based on static factory methods and bridge chronology */ @Deprecated public String formatThreeten(TemporalAccessor formattable) { T entity = this.toEntity(formattable); return this.toZonedFormatter(formattable).format(entity); } @Override public void formatToBuffer( T formattable, Appendable buffer ) throws IOException { this.print(formattable, buffer, this.globalAttributes); } @Override public Set<ElementPosition> print( T formattable, StringBuilder buffer, AttributeQuery attributes ) { ChronoDisplay display = this.display(formattable, attributes); try { return this.print(display, buffer, attributes, true); } catch (IOException ioe) { throw new AssertionError(ioe); } } /** * <p>Prints given Threeten-object as formatted text and writes * the text into given buffer. </p> * * <p>Similar to {@code print(formattable, buffer, getAttributes())}. </p> * * @param formattable object to be formatted * @param buffer text output buffer * @return unmodifiable set of element positions in formatted text * @throws IllegalArgumentException if given object is not formattable * @throws IOException if writing to buffer fails * @since 4.0 * @deprecated Use alternative approach based on static factory methods and bridge chronology */ /*[deutsch] * <p>Formatiert das angegebene Threeten-Objekt als Text und schreibt ihn in * den Puffer. </p> * * <p>Ähnlich zu {@code print(formattable, buffer, getAttributes())}. </p> * * @param formattable object to be formatted * @param buffer text output buffer * @return unmodifiable set of element positions in formatted text * @throws IllegalArgumentException if given object is not formattable * @throws IOException if writing to buffer fails * @since 4.0 * @deprecated Use alternative approach based on static factory methods and bridge chronology */ @Deprecated public Set<ElementPosition> printThreeten( TemporalAccessor formattable, Appendable buffer ) throws IOException { T entity = this.toEntity(formattable); return this.toZonedFormatter(formattable).print(entity, buffer, this.globalAttributes); } /** * <p>Prints given chronological entity as formatted text and writes * the text into given buffer. </p> * * <p>The given attributes cannot change the inner format structure * (for example not change a localized weekmodel), but can override some * format properties like language or certain text attributes for this * run only. </p> * * @param formattable object to be formatted * @param buffer text output buffer * @param attributes attributes for limited formatting control * @return unmodifiable set of element positions in formatted text * @throws IllegalArgumentException if given object is not formattable * @throws IOException if writing to buffer fails */ /*[deutsch] * <p>Erzeugt eine Textausgabe und speichert sie im angegebenen Puffer. </p> * * <p>Die mitgegebenen Steuerattribute können nicht die innere * Formatstruktur ändern (zum Beispiel nicht ein lokalisiertes * Wochenmodell wechseln), aber bestimmte Formateigenschaften wie * die Sprachausgabe oder Textattribute individuell nur für diesen * Lauf setzen. </p> * * @param formattable object to be formatted * @param buffer text output buffer * @param attributes attributes for limited formatting control * @return unmodifiable set of element positions in formatted text * @throws IllegalArgumentException if given object is not formattable * @throws IOException if writing to buffer fails */ public Set<ElementPosition> print( T formattable, Appendable buffer, AttributeQuery attributes ) throws IOException { ChronoDisplay display = this.display(formattable, attributes); return this.print(display, buffer, attributes, true); } @Deprecated @Override public <R> R print( T formattable, Appendable buffer, AttributeQuery attributes, ChronoFunction<ChronoDisplay, R> query ) throws IOException { ChronoDisplay display = this.display(formattable, attributes); this.print(display, buffer, attributes, false); return query.apply(display); } @Deprecated private T toEntity(TemporalAccessor formattable) { T entity = this.chronology.createFrom(formattable, this.globalAttributes); if (entity == null) { throw new IllegalArgumentException("Insufficient data to convert temporal accessor: " + formattable); } return entity; } private ChronoFormatter<T> toZonedFormatter(TemporalAccessor formattable) { if (!this.getAttributes().contains(Attributes.TIMEZONE_ID)) { ZoneId zoneId = formattable.query(TemporalQueries.zone()); if (zoneId != null) { return this.withTimezone(zoneId.getId()); } } return this; } // also directly called by CustomizedProcessor and StyleProcessor Set<ElementPosition> print( ChronoDisplay formattable, Appendable buffer, AttributeQuery attributes, boolean withPositions ) throws IOException { if (buffer == null) { throw new NullPointerException("Missing text result buffer."); } Set<ElementPosition> positions = null; boolean quickPath = (attributes == this.globalAttributes); if (withPositions) { positions = new LinkedHashSet<>(this.steps.size()); } try { int index = 0; int len = this.steps.size(); while (index < len) { FormatStep step = this.steps.get(index); step.print(formattable, buffer, attributes, positions, quickPath); if (step.isNewOrBlockStarted()) { index = step.skipTrailingOrBlocks(); } index++; } } catch (ChronoException ex) { throw new IllegalArgumentException( "Not formattable: " + formattable, ex); } if (withPositions) { return Collections.unmodifiableSet(positions); } else { return Collections.emptySet(); } } @Override public T parse(CharSequence text) throws ParseException { ParseLog status = new ParseLog(); T result = this.parse(text, status); if (result == null) { throw new ParseException( status.getErrorMessage(), status.getErrorIndex() ); } int index = status.getPosition(); if (!this.trailing && (index < text.length())) { throw new ParseException( "Unparsed trailing characters: " + sub(index, text), index ); } return result; } @Override public T parse( CharSequence text, ParseLog status ) { if (this.noPreparser) { return parse( this, this.chronology, this.chronology.getExtensions(), text, status, this.globalAttributes, this.leniency, false, true ); } return this.parse(text, status, this.globalAttributes); } /** * <p>For maximum information use {@link #parse(CharSequence, ParseLog)} instead. </p> * * @param text text to be parsed * @param position parse position (always as new instance) * @return result or {@code null} if parsing does not work * @throws IndexOutOfBoundsException if the start position is at end of text or even behind */ /*[deutsch] * <p>Für maximale Information stattdessen {@link #parse(CharSequence, ParseLog)} nutzen. </p> * * @param text text to be parsed * @param position parse position (always as new instance) * @return result or {@code null} if parsing does not work * @throws IndexOutOfBoundsException if the start position is at end of text or even behind */ @Override public T parse( CharSequence text, ParsePosition position ) { return this.parse(text, new ParseLog(position)); } @Override public T parse( CharSequence text, ParsePosition position, RawValues rawValues ) { ParseLog plog = new ParseLog(position); T result = this.parse(text, plog); rawValues.accept(plog.getRawValues()); return result; } /** * <p>Interpretes given text as chronological entity starting * at the specified position in parse log. </p> * * <p>The given attributes cannot change the inner format structure * (for example not change a localized weekmodel), but can override some * format properties like expected language or certain text attributes * for this run only. </p> * * @param text text to be parsed * @param status parser information (always as new instance) * @param attributes attributes for limited parsing control * @return result or {@code null} if parsing does not work * @throws IndexOutOfBoundsException if the start position is at end of text or even behind */ /*[deutsch] * <p>Interpretiert den angegebenen Text ab der angegebenen Position * im Log. </p> * * <p>Die mitgegebenen Steuerattribute können nicht die innere * Formatstruktur ändern (zum Beispiel nicht ein lokalisiertes * Wochenmodell wechseln), aber bestimmte Formateigenschaften wie * die erwartetete Sprache oder Textattribute individuell nur für * diesen Lauf setzen. </p> * * @param text text to be parsed * @param status parser information (always as new instance) * @param attributes attributes for limited parsing control * @return result or {@code null} if parsing does not work * @throws IndexOutOfBoundsException if the start position is at end of text or even behind */ @Override public T parse( CharSequence text, ParseLog status, AttributeQuery attributes ) { AttributeQuery attrs = attributes; Leniency leniency = this.leniency; boolean quickPath = true; if (attributes != this.globalAttributes) { attrs = new MergedAttributes(attributes, this.globalAttributes); leniency = attrs.get(Attributes.LENIENCY, Leniency.SMART); quickPath = false; } if (this.overrideHandler != null) { // use calendar override List<ChronoExtension> extensions = this.overrideHandler.getExtensions(); ChronoMerger<? extends GeneralTimestamp<?>> merger = this.overrideHandler; GeneralTimestamp<?> tsp = parse(this, merger, extensions, text, status, attrs, leniency, true, quickPath); if (status.isError()) { return null; } ChronoEntity<?> parsed = status.getRawValues0(); TZID tzid = null; Moment moment = null; if (parsed.hasTimezone()) { tzid = parsed.getTimezone(); } else if (attrs.contains(Attributes.TIMEZONE_ID)) { tzid = attrs.get(Attributes.TIMEZONE_ID); // Ersatzwert } if (tzid != null) { StartOfDay startOfDay = attributes.get(Attributes.START_OF_DAY, merger.getDefaultStartOfDay()); if (attrs.contains(Attributes.TRANSITION_STRATEGY)) { TransitionStrategy strategy = attrs.get(Attributes.TRANSITION_STRATEGY); moment = tsp.in(Timezone.of(tzid).with(strategy), startOfDay); } else { moment = tsp.in(Timezone.of(tzid), startOfDay); } } if (moment == null) { status.setError(text.length(), "Missing timezone or offset."); return null; } else { parsed.with(Moment.axis().element(), moment); T result = cast(moment); if (leniency.isStrict()) { checkConsistency(parsed, result, text, status); } return result; } } else { // standard parsing mode return parse( this, this.chronology, 0, text, status, attrs, leniency, quickPath); } } /** * <p>Translates given text as raw chronological entity without * converting to the target type of the underlying chronology. </p> * * @param text text to be parsed * @return new map-like mutable entity (empty if parsing does not work) * @since 2.0 */ /*[deutsch] * <p>Interpretiert den angegebenen Text zu Rohdaten, ohne eine * Typkonversion vorzunehmen. </p> * * @param text text to be parsed * @return new map-like mutable entity (empty if parsing does not work) * @since 2.0 */ public ChronoEntity<?> parseRaw(String text) { return this.parseRaw(text, 0); } /** * <p>Translates given text as raw chronological entity without * converting to the target type of the underlying chronology. </p> * * @param text text to be parsed * @param offset start position * @return new map-like mutable entity (empty if parsing does not work) * @since 2.0 */ /*[deutsch] * <p>Interpretiert den angegebenen Text zu Rohdaten, ohne eine * Typkonversion vorzunehmen. </p> * * @param text text to be parsed * @param offset start position * @return new map-like mutable entity (empty if parsing does not work) * @since 2.0 */ public ChronoEntity<?> parseRaw( CharSequence text, int offset ) { if (offset >= text.length()) { return new ParsedValues(0, false); } // Phase 1: elementweise Interpretation und Sammeln der Elementwerte ParseLog status = new ParseLog(offset); ChronoEntity<?> parsed = null; try { parsed = this.parseElements(text, status, this.globalAttributes, true, this.countOfElements); status.setRawValues(parsed); } catch (AmbivalentValueException ex) { if (!status.isError()) { status.setError(status.getPosition(), ex.getMessage()); } } if ((parsed == null) || status.isError()) { return new ParsedValues(0, false); } // Phase 2: Anreicherung mit Default-Werten for (ChronoElement<?> e : this.defaults.keySet()) { if (!parsed.contains(e)) { setValue(parsed, e, this.getDefaultValue(e, parsed)); } } return parsed; } /** * <p>Creates a copy of this formatter with given locale. </p> * * <p>Note: Sectional attributes will never be overridden. Is the * locale not changed then the method will simply return this instance. * Otherwise the copy will contain given locale and also adjusts * the associated numerical symbols: </p> * * <ul> * <li>{@link Attributes#LANGUAGE}</li> * <li>{@link Attributes#ZERO_DIGIT}</li> * <li>{@link Attributes#DECIMAL_SEPARATOR}</li> * </ul> * * <p>If necessary all inner format elements which are locale-dependent * will also be adjusted. Some country-specific extensions like * {@link net.time4j.Weekmodel#weekOfYear()} or {@link ChronoHistory#era()} * will only be adjusted if the country-part of given locale is not empty. * However, fixed literals will remain unchanged hence this method might * not be suitable for all languages. Example: </p> * * <p>{@code new Locale("sv")} will only change the language to swedish * with no side effects, but {@code new Locale("sv", "SE")} will also * set the week model and the historical swedish calendar which can be * relevant for the years 1700-1712. </p> * * <p><strong>Important: </strong> This method cannot adjust localized format * patterns which were maybe generated by applying a format style like * {@code DisplayMode.FULL}. Users have to call the style factory * method again with different locale parameter in this case. </p> * * @param locale new language and country configuration * @return changed copy with given language and localized symbols while * this instance remains unaffected */ /*[deutsch] * <p>Erzeugt eine Kopie mit der alternativ angegebenen * Sprach- und Ländereinstellung. </p> * * <p>Hinweise: Sektionale Attribute werden grundsätzlich nicht * übersteuert. Ist die Einstellung gleich, wird keine Kopie, sondern * diese Instanz zurückgegeben, andernfalls werden neben der Sprache * automatisch die numerischen Symbole mit angepasst: </p> * * <ul> * <li>{@link Attributes#LANGUAGE}</li> * <li>{@link Attributes#ZERO_DIGIT}</li> * <li>{@link Attributes#DECIMAL_SEPARATOR}</li> * </ul> * * <p>Angepasst werden bei Bedarf auch innere Formatelemente, die * Bestandteil landesabhängiger chronologischer Erweiterungen wie * zum Beispiel {@link net.time4j.Weekmodel#weekOfYear()} oder * {@link ChronoHistory#era()} sind, aber nur dann, wenn die landesspezifische * Komponente des angegebenen Arguments nicht leer ist. Jedoch bleiben * feste Literale unverändert, so daß diese Methode nicht für * alle Sprachen geeignet sein mag. Beispiel: </p> * * <p>{@code new Locale("sv")} wird nur die Sprache zu Schwedisch abändern, * während {@code new Locale("sv", "SE")} auch das Wochenmodell und den * schwedischen Kalender setzt, welcher für die Jahre 1700-1712 relevant * sein kann. </p> * * <p><strong>Wichtig: </strong> Diese Methode kann nicht lokalisierte Formatmuster * anpassen, die zum Beispiel mit Fabrikmethoden unter Verwendung von Formatstilen * wie {@code DisplayMode.FULL} erzeugt wurden. Anwender haben in solchen Fällen * die Fabrikmethode mit einem anderen {@code Locale}-Parameter aufzurufen. </p> * * @param locale new language and country configuration * @return changed copy with given language and localized symbols while * this instance remains unaffected */ @Override public ChronoFormatter<T> with(Locale locale) { if (locale.equals(this.globalAttributes.getLocale())) { return this; } return new ChronoFormatter<>(this, this.globalAttributes.withLocale(locale)); } @Override public ChronoFormatter<T> with(Leniency leniency) { return this.with(Attributes.LENIENCY, leniency); } /** * <p>Creates a copy of this formatter with alternative era names. </p> * * <p>Note: Sectional attributes cannot be overridden. </p> * * @return changed copy with alternative era names while this instance remains unaffected * @see net.time4j.history.HistoricEra#getAlternativeName(Locale, TextWidth) * @since 3.0 */ /*[deutsch] * <p>Erzeugt eine Kopie, die statt der Standard-Ära-Bezeichnungen * alternative Namen verwendet. </p> * * <p>Hinweis: Sektionale Attribute werden nicht übersteuert. </p> * * @return changed copy with alternative era names while this instance remains unaffected * @see net.time4j.history.HistoricEra#getAlternativeName(Locale, TextWidth) * @since 3.0 */ public ChronoFormatter<T> withAlternativeEraNames() { Attributes attrs = new Attributes.Builder() .setAll(this.globalAttributes.getAttributes()) .set(HistoricAttribute.COMMON_ERA, true) .set(HistoricAttribute.LATIN_ERA, false) .build(); return new ChronoFormatter<>(this, attrs); } /** * <p>Creates a copy of this formatter with latin era names. </p> * * <p>Note: Sectional attributes cannot be overridden. </p> * * @return changed copy with latin era names while this instance remains unaffected * @since 3.1 */ /*[deutsch] * <p>Erzeugt eine Kopie, die statt der Standard-Ära-Bezeichnungen * lateinische Namen verwendet. </p> * * <p>Hinweis: Sektionale Attribute werden nicht übersteuert. </p> * * @return changed copy with latin era names while this instance remains unaffected * @since 3.1 */ public ChronoFormatter<T> withLatinEraNames() { Attributes attrs = new Attributes.Builder() .setAll(this.globalAttributes.getAttributes()) .set(HistoricAttribute.COMMON_ERA, false) .set(HistoricAttribute.LATIN_ERA, true) .build(); return new ChronoFormatter<>(this, attrs); } /** * <p>Short-cut for {@code with(ChronoHistory.ofGregorianReform(date))}. </p> * * @param date first gregorian date after gregorian calendar reform takes effect * @return changed copy with given date of gregorian calendar reform while this instance remains unaffected * @throws IllegalArgumentException if given date is before first introduction of gregorian calendar * on 1582-10-15 and not the minimum on the date axis * @see ChronoHistory#ofGregorianReform(PlainDate) * @see #with(ChronoHistory) * @since 3.0 */ /*[deutsch] * <p>Abkürzung für {@code with(ChronoHistory.ofGregorianReform(date))}. </p> * * @param date first gregorian date after gregorian calendar reform takes effect * @return changed copy with given date of gregorian calendar reform while this instance remains unaffected * @throws IllegalArgumentException if given date is before first introduction of gregorian calendar * on 1582-10-15 and not the minimum on the date axis * @see ChronoHistory#ofGregorianReform(PlainDate) * @see #with(ChronoHistory) * @since 3.0 */ public ChronoFormatter<T> withGregorianCutOver(PlainDate date) { return this.with(ChronoHistory.ofGregorianReform(date)); } /** * <p>Creates a copy of this formatter with the given chronological history of gregorian calendar reforms. </p> * * <p>Note that this configuration will override any gregorian cutover date which might be inferred * from current locale. </p> * * @param history chronological history describing historical calendar reforms * @return changed copy with given history while this instance remains unaffected * @since 3.1 */ /*[deutsch] * <p>Erzeugt eine Kopie, die die angegebene Kalenderreform verwendet. </p> * * <p>Zu beachten: Diese Methode wird jedes gregorianische Umstellungsdatum überschreiben, das * von der Ländereinstellung dieses Formatierers abgeleitet werden mag. </p> * * @param history chronological history describing historical calendar reforms * @return changed copy with given history while this instance remains unaffected * @since 3.1 */ public ChronoFormatter<T> with(ChronoHistory history) { if (history == null) { throw new NullPointerException("Missing calendar history."); } AttributeSet as = this.globalAttributes.withInternal(HistoricAttribute.CALENDAR_HISTORY, history); return new ChronoFormatter<>(this, as, history); } /** * <p>Creates a copy of this formatter with given timezone which * shall be used in formatting or parsing. </p> * * <p>The timezone is in most cases only relevant for the type * {@link net.time4j.Moment}. When formatting the timezone helps * to convert the UTC value into a zonal representation. When * parsing the timezone serves as replacement value if the formatted * text does not contain any timezone. </p> * * @param tz timezone * @return changed copy with the new or changed timezone while this instance remains unaffected * @see Attributes#TIMEZONE_ID * @see Attributes#TRANSITION_STRATEGY * @since 3.11/4.8 * */ /*[deutsch] * <p>Erzeugt eine Kopie mit der angegebenen Zeitzone, die beim * Formatieren oder Parsen verwendet werden soll. </p> * * <p>Die Zeitzone ist nur für den Typ {@link net.time4j.Moment} * von Bedeutung. Beim Formatieren wandelt sie die UTC-Darstellung in * eine zonale Repräsentation um. Beim Parsen dient sie als * Ersatzwert, wenn im zu interpretierenden Text keine Zeitzone * gefunden werden konnte. </p> * * @param tz timezone * @return changed copy with the new or changed timezone while this instance remains unaffected * @see Attributes#TIMEZONE_ID * @see Attributes#TRANSITION_STRATEGY * @since 3.11/4.8 */ public ChronoFormatter<T> with(Timezone tz) { if (tz == null) { throw new NullPointerException("Missing timezone id."); } Attributes attrs = new Attributes.Builder() .setAll(this.globalAttributes.getAttributes()) .setTimezone(tz.getID()) .build(); AttributeSet as = this.globalAttributes.withAttributes(attrs); as = as.withInternal(Attributes.TRANSITION_STRATEGY, tz.getStrategy()); return new ChronoFormatter<>(this, as); } /** * <p>Equivalent to {@link #with(Timezone) with(Timezone.of(tzid))}. </p> * * @param tzid timezone id * @return changed copy with the new or changed attribute while this instance remains unaffected */ /*[deutsch] * <p>Entspricht {@link #with(Timezone) with(Timezone.of(tzid))}. </p> * * @param tzid timezone id * @return changed copy with the new or changed attribute while this instance remains unaffected */ @Override public ChronoFormatter<T> withTimezone(TZID tzid) { return this.with(Timezone.of(tzid)); } /** * <p>Equivalent to {@link #with(Timezone) with(Timezone.of(tzid))}. </p> * * @param tzid timezone id * @return changed copy with the new or changed attribute while this instance remains unaffected * @throws IllegalArgumentException if given timezone cannot be loaded * @since 1.1 */ /*[deutsch] * <p>Entspricht {@link #with(Timezone) with(Timezone.of(tzid))}. </p> * * @param tzid timezone id * @return changed copy with the new or changed attribute while this instance remains unaffected * @throws IllegalArgumentException if given timezone cannot be loaded * @since 1.1 */ @Override public ChronoFormatter<T> withTimezone(String tzid) { return this.with(Timezone.of(tzid)); } /** * <p>Equivalent to {@link #with(Timezone) with(Timezone.ofSystem())}. </p> * * @return changed copy with the system timezone while this instance remains unaffected */ /*[deutsch] * <p>Entspricht {@link #with(Timezone) with(Timezone.ofSystem())}. </p> * * @return changed copy with the system timezone while this instance remains unaffected */ public ChronoFormatter<T> withStdTimezone() { return this.with(Timezone.ofSystem()); } /** * <p>Sets the calendar variant. </p> * * <p>Some calendars like {@code HijriCalendar} require the variant otherwise they cannot be * successfully parsed. </p> * * @param variant name of new calendar variant * @return changed copy with the calendar variant while this instance remains unaffected * @see Attributes#CALENDAR_VARIANT * @since 3.5/4.3 */ /*[deutsch] * <p>Setzt die Kalendervariante. </p> * * <p>Einige Kalender wie {@code HijriCalendar} erfordern die Angabe einer Variante, sonst können * sie nicht erfolgreich beim Parsen konstruiert werden. </p> * * @param variant name of new calendar variant * @return changed copy with the calendar variant while this instance remains unaffected * @see Attributes#CALENDAR_VARIANT * @since 3.5/4.3 */ public ChronoFormatter<T> withCalendarVariant(String variant) { Attributes attrs = new Attributes.Builder() .setAll(this.globalAttributes.getAttributes()) .setCalendarVariant(variant) .build(); return new ChronoFormatter<>(this, attrs); } /** * <p>Sets the calendar variant. </p> * * <p>Some calendars like {@code HijriCalendar} require the variant otherwise they cannot be * successfully parsed. </p> * * @param variantSource source of new calendar variant * @return changed copy with the given calendar variant while this instance remains unaffected * @see Attributes#CALENDAR_VARIANT * @since 3.6/4.4 */ /*[deutsch] * <p>Setzt die Kalendervariante. </p> * * <p>Einige Kalender wie {@code HijriCalendar} erfordern die Angabe einer Variante, sonst können * sie nicht erfolgreich beim Parsen konstruiert werden. </p> * * @param variantSource source of new calendar variant * @return changed copy with the given calendar variant while this instance remains unaffected * @see Attributes#CALENDAR_VARIANT * @since 3.6/4.4 */ public ChronoFormatter<T> withCalendarVariant(VariantSource variantSource) { return this.withCalendarVariant(variantSource.getVariant()); } /** * <p>Sets the start of calendar day. </p> * * @param startOfDay new start of day * @return changed copy with the given start of day while this instance remains unaffected * @see Attributes#START_OF_DAY * @since 3.11/4.8 */ /*[deutsch] * <p>Setzt den Beginn des Kalendertages. </p> * * @param startOfDay new start of day * @return changed copy with the given start of day while this instance remains unaffected * @see Attributes#START_OF_DAY * @since 3.11/4.8 */ public ChronoFormatter<T> with(StartOfDay startOfDay) { if (startOfDay == null) { throw new NullPointerException("Missing start of day."); } return new ChronoFormatter<>(this, this.globalAttributes.withInternal(Attributes.START_OF_DAY, startOfDay)); } /** * <p>Determines a default replacement value for given element. </p> * * <p>Example: </p> * * <pre> * ChronoFormatter<PlainDate> fmt = * PlainDate.localFormatter("MM-dd", PatternType.CLDR) * .withDefault(PlainDate.YEAR, 2012); * PlainDate date = fmt.parse("05-21"); * System.out.println(date); // 2012-05-21 * </pre> * * <p>Default replacement values will be considered by Time4J if either * the formatter does not contain the element in question at all or if * there are no consumable characters for given element. Latter * situation might sometimes require the use of sectional attribute * {@code PROTECTED_CHARACTERS} in order to simulate an end-of-text * situation. </p> * * @param <V> generic element value type * @param element chronological element to be updated * @param value replacement value or {@code null} * if the default value shall be deregistered * @return changed copy with new replacement value * @throws IllegalArgumentException if given element is not supported * by the underlying chronology * @see Attributes#PROTECTED_CHARACTERS */ /*[deutsch] * <p>Legt einen Standard-Ersatzwert für das angegebene Element * fest, wenn die Interpretation sonst nicht funktioniert. </p> * * <p>Beispiel: </p> * * <pre> * ChronoFormatter<PlainDate> fmt = * PlainDate.localFormatter("MM-dd", PatternType.CLDR) * .withDefault(PlainDate.YEAR, 2012); * PlainDate date = fmt.parse("05-21"); * System.out.println(date); // 2012-05-21 * </pre> * * <p>Standard-Ersatzwerte werden von Time4J herangezogen, wenn entweder * der Formatierer das fragliche Element nicht enthält oder wenn es * keine konsumierbaren Zeichen für das angegebene Element gibt. * Die letzte Situation erfordert manchmal die Verwendung des sektionalen * Attributs {@code PROTECTED_CHARACTERS}, um eine Situation zu simulieren, * in der der Formatierer quasi am Ende eines Texts angekommen ist. </p> * * @param <V> generic element value type * @param element chronological element to be updated * @param value replacement value or {@code null} * if the default value shall be deregistered * @return changed copy with new replacement value * @throws IllegalArgumentException if given element is not supported * by the underlying chronology * @see Attributes#PROTECTED_CHARACTERS */ public <V> ChronoFormatter<T> withDefault( ChronoElement<V> element, V value ) { if (element == null) { throw new NullPointerException("Missing element."); } Map<ChronoElement<?>, Object> defaultMap = new HashMap<>(); defaultMap.put(element, value); return new ChronoFormatter<>(this, defaultMap); } /** * <p>Determines a supplier for a default replacement value of given element. </p> * * <p>Example: </p> * * <pre> * ChronoFormatter<PlainTimestamp> fmt = * ChronoFormatter.ofTimestampPattern("HH:mm", PatternType.CLDR, Locale.ROOT) * .withDefaultSupplier(PlainDate.COMPONENT, () -> SystemClock.inLocalView().today()); * PlainTimestamp tsp = fmt.parse("14:45"); * System.out.println(tsp); // 2012-05-21T14:45 (example for parsed time on today) * </pre> * * <p>Default replacement values will be considered by Time4J if either * the formatter does not contain the element in question at all or if * there are no consumable characters for given element. Latter * situation might sometimes require the use of sectional attribute * {@code PROTECTED_CHARACTERS} in order to simulate an end-of-text * situation. </p> * * @param <V> generic element value type * @param element chronological element to be updated * @param supplier supplier for replacement value or {@code null} * if the default value shall be deregistered * @return changed copy with new replacement value * @throws IllegalArgumentException if given element is not supported * by the underlying chronology * @see Attributes#PROTECTED_CHARACTERS * @since 4.14 */ /*[deutsch] * <p>Legt einen Lieferanten für einen Standard-Ersatzwert des angegebenen Elements * fest, wenn die Interpretation sonst nicht funktioniert. </p> * * <p>Beispiel: </p> * * <pre> * ChronoFormatter<PlainTimestamp> fmt = * ChronoFormatter.ofTimestampPattern("HH:mm", PatternType.CLDR, Locale.ROOT) * .withDefaultSupplier(PlainDate.COMPONENT, () -> SystemClock.inLocalView().today()); * PlainTimestamp tsp = fmt.parse("14:45"); * System.out.println(tsp); // 2012-05-21T14:45 (example for parsed time on today) * </pre> * * <p>Standard-Ersatzwerte werden von Time4J herangezogen, wenn entweder * der Formatierer das fragliche Element nicht enthält oder wenn es * keine konsumierbaren Zeichen für das angegebene Element gibt. * Die letzte Situation erfordert manchmal die Verwendung des sektionalen * Attributs {@code PROTECTED_CHARACTERS}, um eine Situation zu simulieren, * in der der Formatierer quasi am Ende eines Texts angekommen ist. </p> * * @param <V> generic element value type * @param element chronological element to be updated * @param supplier supplier for replacement value or {@code null} * if the default value shall be deregistered * @return changed copy with new replacement value * @throws IllegalArgumentException if given element is not supported * by the underlying chronology * @see Attributes#PROTECTED_CHARACTERS * @since 4.14 */ public <V> ChronoFormatter<T> withDefaultSupplier( ChronoElement<V> element, Supplier<V> supplier ) { if (element == null) { throw new NullPointerException("Missing element."); } Map<ChronoElement<?>, Object> defaultMap = new HashMap<>(); defaultMap.put(element, supplier); return new ChronoFormatter<>(this, defaultMap); } /** * <p>Determines a source reference in parsed data for a default replacement value of given element. </p> * * <p>Default replacement values will be considered by Time4J if either * the formatter does not contain the element in question at all or if * there are no consumable characters for given element. Latter * situation might sometimes require the use of sectional attribute * {@code PROTECTED_CHARACTERS} in order to simulate an end-of-text * situation. </p> * * @param <V> generic element value type * @param element chronological element to be updated * @param source element reference in parsed data whose value serves as replacement value * or {@code null} if the default value shall be deregistered * @return changed copy with new replacement value * @throws IllegalArgumentException if the first argument is not supported by the underlying chronology * or if both arguments are equal * @see Attributes#PROTECTED_CHARACTERS * @since 4.20 */ /*[deutsch] * <p>Legt eine Elementquelle in interpretierten Rohdaten für einen Standard-Ersatzwert * des angegebenen Elements fest, wenn die Interpretation sonst nicht funktioniert. </p> * * <p>Standard-Ersatzwerte werden von Time4J herangezogen, wenn entweder * der Formatierer das fragliche Element nicht enthält oder wenn es * keine konsumierbaren Zeichen für das angegebene Element gibt. * Die letzte Situation erfordert manchmal die Verwendung des sektionalen * Attributs {@code PROTECTED_CHARACTERS}, um eine Situation zu simulieren, * in der der Formatierer quasi am Ende eines Texts angekommen ist. </p> * * @param <V> generic element value type * @param element chronological element to be updated * @param source element reference in parsed data whose value serves as replacement value * or {@code null} if the default value shall be deregistered * @return changed copy with new replacement value * @throws IllegalArgumentException if the first argument is not supported by the underlying chronology * or if both arguments are equal * @see Attributes#PROTECTED_CHARACTERS * @since 4.20 */ public <V> ChronoFormatter<T> withDefaultSource( ChronoElement<V> element, ChronoElement<V> source ) { if (element.equals(source)) { // NPE-check throw new IllegalArgumentException("Source equal to defaulting element."); } Map<ChronoElement<?>, Object> defaultMap = new HashMap<>(); defaultMap.put(element, source); return new ChronoFormatter<>(this, defaultMap); } /** * <p>Creates a copy of this formatter with given boolean-attribute. </p> * * <p>Note: Sectional attributes cannot be overridden. </p> * * @param key attribute key * @param value attribute value * @return changed copy with the new or changed attribute while * this instance remains unaffected */ /*[deutsch] * <p>Erzeugt eine Kopie mit dem angegebenen boolean-Attribut. </p> * * <p>Hinweis: Sektionale Attribute werden nicht übersteuert. </p> * * @param key attribute key * @param value attribute value * @return changed copy with the new or changed attribute while * this instance remains unaffected */ public ChronoFormatter<T> with( AttributeKey<Boolean> key, boolean value ) { Attributes attrs = new Attributes.Builder() .setAll(this.globalAttributes.getAttributes()) .set(key, value) .build(); return new ChronoFormatter<>(this, attrs); } /** * <p>Creates a copy of this formatter with given int-attribute. </p> * * <p>Note: Sectional attributes cannot be overridden. </p> * * @param key attribute key * @param value attribute value * @return changed copy with the new or changed attribute while * this instance remains unaffected */ /*[deutsch] * <p>Erzeugt eine Kopie mit dem angegebenen int-Attribut. </p> * * <p>Hinweis: Sektionale Attribute werden nicht übersteuert. </p> * * @param key attribute key * @param value attribute value * @return changed copy with the new or changed attribute while * this instance remains unaffected */ public ChronoFormatter<T> with( AttributeKey<Integer> key, int value ) { Attributes attrs = new Attributes.Builder() .setAll(this.globalAttributes.getAttributes()) .set(key, value) .build(); return new ChronoFormatter<>(this, attrs); } /** * <p>Creates a copy of this formatter with given char-attribute. </p> * * <p>Note: Sectional attributes cannot be overridden. </p> * * @param key attribute key * @param value attribute value * @return changed copy with the new or changed attribute while * this instance remains unaffected */ /*[deutsch] * <p>Erzeugt eine Kopie mit dem angegebenen char-Attribut. </p> * * <p>Hinweis: Sektionale Attribute werden nicht übersteuert. </p> * * @param key attribute key * @param value attribute value * @return changed copy with the new or changed attribute while * this instance remains unaffected */ public ChronoFormatter<T> with( AttributeKey<Character> key, char value ) { Attributes attrs = new Attributes.Builder() .setAll(this.globalAttributes.getAttributes()) .set(key, value) .build(); return new ChronoFormatter<>(this, attrs); } /** * <p>Creates a copy of this formatter with given enum-attribute. </p> * * <p>Note: Sectional attributes cannot be overridden. </p> * * @param <A> generic attribute value type * @param key attribute key * @param value attribute value * @return changed copy with the new or changed attribute while * this instance remains unaffected */ /*[deutsch] * <p>Erzeugt eine Kopie mit dem angegebenen enum-Attribut. </p> * * <p>Hinweis: Sektionale Attribute werden nicht übersteuert. </p> * * @param <A> generic attribute value type * @param key attribute key * @param value attribute value * @return changed copy with the new or changed attribute while * this instance remains unaffected */ public <A extends Enum<A>> ChronoFormatter<T> with( AttributeKey<A> key, A value ) { Attributes attrs = new Attributes.Builder() .setAll(this.globalAttributes.getAttributes()) .set(key, value) .build(); return new ChronoFormatter<>(this, attrs); } /** * <p>Creates a copy of this formatter with given standard attributes. </p> * * <p>Note: Sectional attributes cannot be overridden. </p> * * @param attributes new default attributes * @return changed copy with the new or changed attributes while * this instance remains unaffected */ /*[deutsch] * <p>Erzeugt eine Kopie mit den angegebenen Standard-Attributen. </p> * * <p>Hinweis: Sektionale Attribute werden nicht übersteuert. </p> * * @param attributes new default attributes * @return changed copy with the new or changed attributes while * this instance remains unaffected */ public ChronoFormatter<T> with(Attributes attributes) { Attributes newAttrs = new Attributes.Builder() .setAll(this.globalAttributes.getAttributes()) .setAll(attributes) .build(); return new ChronoFormatter<>(this, newAttrs); } // used by CustomizedProcessor ChronoFormatter<T> with( Map<ChronoElement<?>, Object> outerDefaults, AttributeSet outerAttrs ) { AttributeSet merged = AttributeSet.merge(outerAttrs, this.globalAttributes); return new ChronoFormatter<>( new ChronoFormatter<>(this, outerDefaults), merged, merged.get(HistoricAttribute.CALENDAR_HISTORY, null)); } // used by CustomizedProcessor Map<ChronoElement<?>, Object> getDefaults() { return this.defaults; } /** * <p>Converts this formatter into a traditional * {@code java.text.Format}-object. </p> * * <p>The returned format object also supports attributed strings such * that all {@code ChronoElement}-structures are associated with field * attributes of type {@link java.text.DateFormat.Field DateFormat.Field}. * In ISO systems following mapping will be applied: </p> * * <ul> * <li>{@link net.time4j.PlainTime#AM_PM_OF_DAY} => * {@link java.text.DateFormat.Field#AM_PM}</li> * <li>{@link net.time4j.PlainTime#CLOCK_HOUR_OF_AMPM} => * {@link java.text.DateFormat.Field#HOUR1}</li> * <li>{@link net.time4j.PlainTime#CLOCK_HOUR_OF_DAY} => * {@link java.text.DateFormat.Field#HOUR_OF_DAY1}</li> * <li>{@link net.time4j.PlainDate#DAY_OF_MONTH} => * {@link java.text.DateFormat.Field#DAY_OF_MONTH}</li> * <li>{@link net.time4j.PlainDate#DAY_OF_WEEK} => * {@link java.text.DateFormat.Field#DAY_OF_WEEK}</li> * <li>{@link net.time4j.PlainDate#DAY_OF_YEAR} => * {@link java.text.DateFormat.Field#DAY_OF_YEAR}</li> * <li>{@link net.time4j.PlainTime#DIGITAL_HOUR_OF_AMPM} => * {@link java.text.DateFormat.Field#HOUR1}</li> * <li>{@link net.time4j.PlainTime#DIGITAL_HOUR_OF_DAY} => * {@link java.text.DateFormat.Field#HOUR0}</li> * <li>{@link net.time4j.PlainTime#MILLI_OF_SECOND} => * {@link java.text.DateFormat.Field#MILLISECOND}</li> * <li>{@link net.time4j.PlainTime#MINUTE_OF_HOUR} => * {@link java.text.DateFormat.Field#MINUTE}</li> * <li>{@link net.time4j.PlainDate#MONTH_AS_NUMBER} => * {@link java.text.DateFormat.Field#MONTH}</li> * <li>{@link net.time4j.PlainDate#MONTH_OF_YEAR} => * {@link java.text.DateFormat.Field#MONTH}</li> * <li>{@link net.time4j.PlainTime#SECOND_OF_MINUTE} => * {@link java.text.DateFormat.Field#SECOND}</li> * <li>{@link net.time4j.PlainDate#WEEKDAY_IN_MONTH} => * {@link java.text.DateFormat.Field#DAY_OF_WEEK_IN_MONTH}</li> * <li>{@link net.time4j.PlainDate#YEAR} => * {@link java.text.DateFormat.Field#YEAR}</li> * <li>{@link net.time4j.PlainDate#YEAR_OF_WEEKDATE} => * {@link java.text.DateFormat.Field#YEAR}</li> * <li>{@link net.time4j.Weekmodel#boundedWeekOfMonth()} => * {@link java.text.DateFormat.Field#WEEK_OF_MONTH}</li> * <li>{@link net.time4j.Weekmodel#boundedWeekOfYear()} => * {@link java.text.DateFormat.Field#WEEK_OF_YEAR}</li> * <li>{@link net.time4j.Weekmodel#weekOfMonth()} => * {@link java.text.DateFormat.Field#WEEK_OF_MONTH}</li> * <li>{@link net.time4j.Weekmodel#weekOfYear()} => * {@link java.text.DateFormat.Field#WEEK_OF_YEAR}</li> * <li>{@link net.time4j.Weekmodel#localDayOfWeek()} => * {@link java.text.DateFormat.Field#DAY_OF_WEEK}</li> * <li>{@link net.time4j.engine.ChronoEntity#getTimezone()} => * {@link java.text.DateFormat.Field#TIME_ZONE}</li> * <li>{@link ChronoHistory#era()} => * {@link java.text.DateFormat.Field#ERA}</li> * <li>{@link ChronoHistory#yearOfEra()} => * {@link java.text.DateFormat.Field#YEAR}</li> * <li>{@link ChronoHistory#month()} => * {@link java.text.DateFormat.Field#MONTH}</li> * <li>{@link ChronoHistory#dayOfMonth()} => * {@link java.text.DateFormat.Field#DAY_OF_MONTH}</li> * </ul> * * <p>Note: The returned {@code Format}-object is not serializable. </p> * * @return new non-serializable {@code java.text.Format}-object which * delegates all formatting and parsing work to this instance */ /*[deutsch] * <p>Wandelt dieses Objekt in ein herkömmliches * {@code java.text.Format}-Objekt um. </p> * * <p>Das erzeugte Format-Objekt unterstützt auch attributierte * Strings, indem versucht wird, allen {@code ChronoElement}-Strukturen * Attribute vom Typ {@link java.text.DateFormat.Field DateFormat.Field} * zuzuweisen. In ISO-Systemen wird folgende Abbildung verwendet: </p> * * <ul> * <li>{@link net.time4j.PlainTime#AM_PM_OF_DAY} => * {@link java.text.DateFormat.Field#AM_PM}</li> * <li>{@link net.time4j.PlainTime#CLOCK_HOUR_OF_AMPM} => * {@link java.text.DateFormat.Field#HOUR1}</li> * <li>{@link net.time4j.PlainTime#CLOCK_HOUR_OF_DAY} => * {@link java.text.DateFormat.Field#HOUR_OF_DAY1}</li> * <li>{@link net.time4j.PlainDate#DAY_OF_MONTH} => * {@link java.text.DateFormat.Field#DAY_OF_MONTH}</li> * <li>{@link net.time4j.PlainDate#DAY_OF_WEEK} => * {@link java.text.DateFormat.Field#DAY_OF_WEEK}</li> * <li>{@link net.time4j.PlainDate#DAY_OF_YEAR} => * {@link java.text.DateFormat.Field#DAY_OF_YEAR}</li> * <li>{@link net.time4j.PlainTime#DIGITAL_HOUR_OF_AMPM} => * {@link java.text.DateFormat.Field#HOUR1}</li> * <li>{@link net.time4j.PlainTime#DIGITAL_HOUR_OF_DAY} => * {@link java.text.DateFormat.Field#HOUR0}</li> * <li>{@link net.time4j.PlainTime#MILLI_OF_SECOND} => * {@link java.text.DateFormat.Field#MILLISECOND}</li> * <li>{@link net.time4j.PlainTime#MINUTE_OF_HOUR} => * {@link java.text.DateFormat.Field#MINUTE}</li> * <li>{@link net.time4j.PlainDate#MONTH_AS_NUMBER} => * {@link java.text.DateFormat.Field#MONTH}</li> * <li>{@link net.time4j.PlainDate#MONTH_OF_YEAR} => * {@link java.text.DateFormat.Field#MONTH}</li> * <li>{@link net.time4j.PlainTime#SECOND_OF_MINUTE} => * {@link java.text.DateFormat.Field#SECOND}</li> * <li>{@link net.time4j.PlainDate#WEEKDAY_IN_MONTH} => * {@link java.text.DateFormat.Field#DAY_OF_WEEK_IN_MONTH}</li> * <li>{@link net.time4j.PlainDate#YEAR} => * {@link java.text.DateFormat.Field#YEAR}</li> * <li>{@link net.time4j.PlainDate#YEAR_OF_WEEKDATE} => * {@link java.text.DateFormat.Field#YEAR}</li> * <li>{@link net.time4j.Weekmodel#boundedWeekOfMonth()} => * {@link java.text.DateFormat.Field#WEEK_OF_MONTH}</li> * <li>{@link net.time4j.Weekmodel#boundedWeekOfYear()} => * {@link java.text.DateFormat.Field#WEEK_OF_YEAR}</li> * <li>{@link net.time4j.Weekmodel#weekOfMonth()} => * {@link java.text.DateFormat.Field#WEEK_OF_MONTH}</li> * <li>{@link net.time4j.Weekmodel#weekOfYear()} => * {@link java.text.DateFormat.Field#WEEK_OF_YEAR}</li> * <li>{@link net.time4j.Weekmodel#localDayOfWeek()} => * {@link java.text.DateFormat.Field#DAY_OF_WEEK}</li> * <li>{@link net.time4j.engine.ChronoEntity#getTimezone()} => * {@link java.text.DateFormat.Field#TIME_ZONE}</li> * <li>{@link ChronoHistory#era()} => * {@link java.text.DateFormat.Field#ERA}</li> * <li>{@link ChronoHistory#yearOfEra()} => * {@link java.text.DateFormat.Field#YEAR}</li> * <li>{@link ChronoHistory#month()} => * {@link java.text.DateFormat.Field#MONTH}</li> * <li>{@link ChronoHistory#dayOfMonth()} => * {@link java.text.DateFormat.Field#DAY_OF_MONTH}</li> * </ul> * * <p>Zu beachten: Das {@code Format}-Objekt ist nicht serialisierbar. </p> * * @return new non-serializable {@code java.text.Format}-object which * delegates all formatting and parsing work to this instance */ public Format toFormat() { return new TraditionalFormat<>(this); } /** * <p>Constructs a pattern-based formatter for plain date objects. </p> * * @param pattern format pattern * @param type the type of the pattern to be used * @param locale format locale * @return new {@code ChronoFormatter}-instance * @throws IllegalArgumentException if resolving of pattern fails * @see #ofPattern(String, PatternType, Locale, Chronology) * @since 3.1 */ /*[deutsch] * <p>Konstruiert einen Formatierer für reine Datumsobjekte. </p> * * @param pattern format pattern * @param type the type of the pattern to be used * @param locale format locale * @return new {@code ChronoFormatter}-instance * @throws IllegalArgumentException if resolving of pattern fails * @see #ofPattern(String, PatternType, Locale, Chronology) * @since 3.1 */ public static ChronoFormatter<PlainDate> ofDatePattern( String pattern, PatternType type, Locale locale ) { Builder<PlainDate> builder = new Builder<>(PlainDate.axis(), locale); addPattern(builder, pattern, type); try { return builder.build(); } catch (IllegalStateException ise) { throw new IllegalArgumentException(ise); } } /** * <p>Constructs a pattern-based formatter for clock time objects. </p> * * @param pattern format pattern * @param type the type of the pattern to be used * @param locale format locale * @return new {@code ChronoFormatter}-instance * @throws IllegalArgumentException if resolving of pattern fails * @see #ofPattern(String, PatternType, Locale, Chronology) * @since 3.1 */ /*[deutsch] * <p>Konstruiert einen Formatierer für Uhrzeitobjekte. </p> * * @param pattern format pattern * @param type the type of the pattern to be used * @param locale format locale * @return new {@code ChronoFormatter}-instance * @throws IllegalArgumentException if resolving of pattern fails * @see #ofPattern(String, PatternType, Locale, Chronology) * @since 3.1 */ public static ChronoFormatter<PlainTime> ofTimePattern( String pattern, PatternType type, Locale locale ) { Builder<PlainTime> builder = new Builder<>(PlainTime.axis(), locale); addPattern(builder, pattern, type); try { return builder.build(); } catch (IllegalStateException ise) { throw new IllegalArgumentException(ise); } } /** * <p>Constructs a pattern-based formatter for plain timestamps. </p> * * @param pattern format pattern * @param type the type of the pattern to be used * @param locale format locale * @return new {@code ChronoFormatter}-instance * @throws IllegalArgumentException if resolving of pattern fails * @see #ofPattern(String, PatternType, Locale, Chronology) * @since 3.1 */ /*[deutsch] * <p>Konstruiert einen Formatierer für einfache Zeitstempelobjekte. </p> * * @param pattern format pattern * @param type the type of the pattern to be used * @param locale format locale * @return new {@code ChronoFormatter}-instance * @throws IllegalArgumentException if resolving of pattern fails * @see #ofPattern(String, PatternType, Locale, Chronology) * @since 3.1 */ public static ChronoFormatter<PlainTimestamp> ofTimestampPattern( String pattern, PatternType type, Locale locale ) { Builder<PlainTimestamp> builder = new Builder<>(PlainTimestamp.axis(), locale); addPattern(builder, pattern, type); try { return builder.build(); } catch (IllegalStateException ise) { throw new IllegalArgumentException(ise); } } /** * <p>Constructs a pattern-based formatter for global timestamp objects. </p> * * @param pattern format pattern * @param type the type of the pattern to be used * @param locale format locale * @param tzid timezone id * @return new format object for formatting {@code Moment}-objects using given locale and timezone * @throws IllegalArgumentException if resolving of pattern fails * @see #ofPattern(String, PatternType, Locale, Chronology) * @since 3.1 */ /*[deutsch] * <p>Konstruiert einen Formatierer für globale Zeitstempelobjekte. </p> * * @param pattern format pattern * @param type the type of the pattern to be used * @param locale format locale * @param tzid timezone id * @return new format object for formatting {@code Moment}-objects using given locale and timezone * @throws IllegalArgumentException if resolving of pattern fails * @see #ofPattern(String, PatternType, Locale, Chronology) * @since 3.1 */ public static ChronoFormatter<Moment> ofMomentPattern( String pattern, PatternType type, Locale locale, TZID tzid ) { Builder<Moment> builder = new Builder<>(Moment.axis(), locale); addPattern(builder, pattern, type); try { return builder.build().withTimezone(tzid); } catch (IllegalStateException ise) { throw new IllegalArgumentException(ise); } } /** * <p>Constructs a pattern-based formatter for general chronologies. </p> * * <p>If given pattern type is equal to CLDR or derived from CLDR then an additional * sanity check will be performed such that following combinations of symbols are excluded: </p> * * <ul> * <li>"h" or "K" without "a" or "b" or "B" * (12-hour-clock requires am/pm-marker or dayperiod)</li> * <li>"Y" with "M" or "L" but without "w" * (Y as week-based-year requires a week-date-format)</li> * <li>"D" with "M" or "L" but without "d" * (D is the day of year but not the day of month)</li> * </ul> * * <p>Note that this check will only be done here but not on builder level (since v4.20). The * check does not claim to find all insane combinations of symbols but intends to prevent at least * the most wide-spread pattern errors. </p> * * @param <T> generic chronological type * @param pattern format pattern * @param type the type of the pattern to be used * @param locale format locale * @param chronology chronology with format pattern support * @return new {@code ChronoFormatter}-instance * @throws IllegalArgumentException if resolving of pattern fails * @see ChronoFormatter.Builder#addPattern(String, PatternType) * @since 3.14/4.11 */ /*[deutsch] * <p>Konstruiert einen musterbasierten Formatierer für allgemeine Chronologien. </p> * * <p>Falls der Mustertyp gleich CLDR oder von CLDR abgeleitet ist, wird eine zusätzliche * Musterprüfung ausgeführt, die folgende Symbolkombinationen ausschließt: </p> * * <ul> * <li>"h" oder "K" ohne "a" oder "b" oder "B" * (12-Stunden-Uhr erfordert eine am/pm-Kennung oder einen Tagesabschnitt)</li> * <li>"Y" mit "M" oder "L" aber ohne "w" * (Y als wochenbasiertes Jahr erfordert ein wochenbasiertes Format)</li> * <li>"D" mit "M" oder "L" aber ohne "d" * (D ist der Tag des Jahres, nicht des Monats)</li> * </ul> * * <p>Hinweis: Diese Prüfung wird hier, aber nicht im {@code ChronoFormatter.Builder} * durchgeführt (seit v4.20). Sie hat auch nicht den Anspruch, alle ungesunden Kombinationen * zu finden, sondern soll lediglich einige besonders häufige Fehlerquellen abdecken. </p> * * @param <T> generic chronological type * @param pattern format pattern * @param type the type of the pattern to be used * @param locale format locale * @param chronology chronology with format pattern support * @return new {@code ChronoFormatter}-instance * @throws IllegalArgumentException if resolving of pattern fails * @see ChronoFormatter.Builder#addPattern(String, PatternType) * @since 3.14/4.11 */ public static <T> ChronoFormatter<T> ofPattern( String pattern, PatternType type, Locale locale, Chronology<T> chronology ) { Builder<T> builder = new Builder<>(chronology, locale); addPattern(builder, pattern, type); try { return builder.build(); } catch (IllegalStateException ise) { throw new IllegalArgumentException(ise); } } /** * <p>Constructs a style-based formatter for plain date objects. </p> * * @param style format style * @param locale format locale * @return new {@code ChronoFormatter}-instance * @see CalendarText#patternForDate(DisplayMode, Locale) * @since 3.10/4.7 */ /*[deutsch] * <p>Konstruiert einen Formatierer für reine Datumsobjekte. </p> * * @param style format style * @param locale format locale * @return new {@code ChronoFormatter}-instance * @see CalendarText#getFormatPatterns() * @see CalendarText#patternForDate(DisplayMode, Locale) * @since 3.10/4.7 */ public static ChronoFormatter<PlainDate> ofDateStyle( DisplayMode style, Locale locale ) { Builder<PlainDate> builder = new Builder<>(PlainDate.axis(), locale); builder.addProcessor(new StyleProcessor<>(style, style)); return builder.build(); } /** * <p>Constructs a style-based formatter for plain date objects. </p> * * @param style format style * @param locale format locale * @return new {@code ChronoFormatter}-instance * @see CalendarText#patternForTime(DisplayMode, Locale) * @since 3.10/4.7 */ /*[deutsch] * <p>Konstruiert einen Formatierer für reine Datumsobjekte. </p> * * @param style format style * @param locale format locale * @return new {@code ChronoFormatter}-instance * @see CalendarText#patternForTime(DisplayMode, Locale) * @since 3.10/4.7 */ public static ChronoFormatter<PlainTime> ofTimeStyle( DisplayMode style, Locale locale ) { Builder<PlainTime> builder = new Builder<>(PlainTime.axis(), locale); builder.addProcessor(new StyleProcessor<>(style, style)); return builder.build(); } /** * <p>Constructs a style-based formatter for plain timestamps. </p> * * @param dateStyle format style of date part * @param timeStyle format style of time part * @param locale format locale * @return new {@code ChronoFormatter}-instance * @see CalendarText#patternForTimestamp(DisplayMode, DisplayMode, Locale) * @since 3.10/4.7 */ /*[deutsch] * <p>Konstruiert einen Formatierer für globale Zeitstempel des Typs {@code PlainTimestamp}. </p> * * @param dateStyle format style of date part * @param timeStyle format style of time part * @param locale format locale * @return new {@code ChronoFormatter}-instance * @see CalendarText#patternForTimestamp(DisplayMode, DisplayMode, Locale) * @since 3.10/4.7 */ public static ChronoFormatter<PlainTimestamp> ofTimestampStyle( DisplayMode dateStyle, DisplayMode timeStyle, Locale locale ) { Builder<PlainTimestamp> builder = new Builder<>(PlainTimestamp.axis(), locale); builder.addProcessor(new StyleProcessor<>(dateStyle, timeStyle)); return builder.build(); } /** * <p>Constructs a style-based formatter for moments. </p> * * @param dateStyle format style of date part * @param timeStyle format style of time part * @param locale format locale * @param tzid timezone identifier * @return new {@code ChronoFormatter}-instance * @see CalendarText#patternForMoment(DisplayMode, DisplayMode, Locale) * @since 3.10/4.7 */ /*[deutsch] * <p>Konstruiert einen Formatierer für globale Zeitstempel des Typs {@code Moment}. </p> * * @param dateStyle format style of date part * @param timeStyle format style of time part * @param locale format locale * @param tzid timezone identifier * @return new {@code ChronoFormatter}-instance * @see CalendarText#patternForMoment(DisplayMode, DisplayMode, Locale) * @since 3.10/4.7 */ public static ChronoFormatter<Moment> ofMomentStyle( DisplayMode dateStyle, DisplayMode timeStyle, Locale locale, TZID tzid ) { Builder<Moment> builder = new Builder<>(Moment.axis(), locale); builder.addProcessor(new StyleProcessor<>(dateStyle, timeStyle)); return builder.build().withTimezone(tzid); } /** * <p>Constructs a style-based formatter for general chronologies. </p> * * @param <T> generic chronological type * @param style format style * @param locale format locale * @param chronology chronology with format pattern support * @return new {@code ChronoFormatter}-instance * @throws UnsupportedOperationException if given style is not supported * @see DisplayMode * @since 3.10/4.7 */ /*[deutsch] * <p>Konstruiert einen stilbasierten Formatierer für allgemeine Chronologien. </p> * * @param <T> generic chronological type * @param style format style * @param locale format locale * @param chronology chronology with format pattern support * @return new {@code ChronoFormatter}-instance * @throws UnsupportedOperationException if given style is not supported * @see DisplayMode * @since 3.10/4.7 */ public static <T extends LocalizedPatternSupport> ChronoFormatter<T> ofStyle( DisplayStyle style, Locale locale, Chronology<T> chronology ) { if (LocalizedPatternSupport.class.isAssignableFrom(chronology.getChronoType())) { Builder<T> builder = new Builder<>(chronology, locale); builder.addProcessor(new StyleProcessor<>(style, style)); return builder.build(); } else if (chronology.equals(Moment.axis())) { throw new UnsupportedOperationException("Timezone required, use 'ofMomentStyle()' instead."); } else { throw new UnsupportedOperationException("Localized format patterns not available: " + chronology); } } /** * <p>Constructs a builder for creating formatters. </p> * * @param <T> generic chronological type (subtype of {@code ChronoEntity}) * @param type reified chronological type * @param locale format locale * @return new {@code Builder}-instance * @throws IllegalArgumentException if given chronological type is not * formattable that is if no chronology can be derived from type * @see Chronology#lookup(Class) */ /*[deutsch] * <p>Konstruiert ein Hilfsobjekt zum Bauen eines Zeitformats. </p> * * @param <T> generic chronological type (subtype of {@code ChronoEntity}) * @param type reified chronological type * @param locale format locale * @return new {@code Builder}-instance * @throws IllegalArgumentException if given chronological type is not * formattable that is if no chronology can be derived from type * @see Chronology#lookup(Class) */ public static <T extends ChronoEntity<T>> ChronoFormatter.Builder<T> setUp( Class<T> type, Locale locale ) { if (type == null) { throw new NullPointerException("Missing chronological type."); } Chronology<T> chronology = Chronology.lookup(type); if (chronology == null) { throw new IllegalArgumentException("Not formattable: " + type); } return new Builder<>(chronology, locale); } /** * <p>Constructs a builder for creating formatters. </p> * * @param <T> generic chronological type (subtype of {@code ChronoEntity}) * @param chronology formattable chronology * @param locale format locale * @return new {@code Builder}-instance * @since 3.10/4.7 */ /*[deutsch] * <p>Konstruiert ein Hilfsobjekt zum Bauen eines Zeitformats. </p> * * @param <T> generic chronological type (subtype of {@code ChronoEntity}) * @param chronology formattable chronology * @param locale format locale * @return new {@code Builder}-instance * @since 3.10/4.7 */ public static <T> ChronoFormatter.Builder<T> setUp( Chronology<T> chronology, Locale locale ) { return new Builder<>(chronology, locale); } /** * <p>Constructs a builder for creating global formatters with usage of given calendar type. </p> * * <p>For formatting, it is necessary to set the timezone on the built formatter. For parsing, * the calendar variant is necessary. </p> * * @param <C> generic calendrical type with variant * @param locale format locale * @param overrideCalendar formattable calendar chronology * @return new {@code Builder}-instance applicable on {@code Moment} * @since 3.11/4.8 */ /*[deutsch] * <p>Konstruiert ein Hilfsobjekt zum Bauen eines globalen Zeitformats mit Verwendung * des angegebenen Kalendertyps. </p> * * <p>Zum Formatieren ist es notwendig, die Zeitzone am fertiggestellten Formatierer zu setzen. * Beim Parsen ist die Kalendervariante notwendig. </p> * * @param <C> generic calendrical type with variant * @param locale format locale * @param overrideCalendar formattable calendar chronology * @return new {@code Builder}-instance applicable on {@code Moment} * @since 3.11/4.8 */ public static <C extends CalendarVariant<C>> ChronoFormatter.Builder<Moment> setUpWithOverride( Locale locale, CalendarFamily<C> overrideCalendar ) { if (overrideCalendar == null) { throw new NullPointerException("Missing override calendar."); } return new Builder<>(Moment.axis(), locale, overrideCalendar); } /** * <p>Constructs a builder for creating global formatters with usage of given calendar type. </p> * * <p>For formatting, it is necessary to set the timezone on the built formatter. </p> * * @param <C> generic calendrical type * @param locale format locale * @param overrideCalendar formattable calendar chronology * @return new {@code Builder}-instance applicable on {@code Moment} * @since 3.11/4.8 */ /*[deutsch] * <p>Konstruiert ein Hilfsobjekt zum Bauen eines globalen Zeitformats mit Verwendung * des angegebenen Kalendertyps. </p> * * <p>Zum Formatieren ist es notwendig, die Zeitzone am fertiggestellten Formatierer zu setzen. </p> * * @param <C> generic calendrical type * @param locale format locale * @param overrideCalendar formattable calendar chronology * @return new {@code Builder}-instance applicable on {@code Moment} * @since 3.11/4.8 */ public static <C extends Calendrical<?, C>> ChronoFormatter.Builder<Moment> setUpWithOverride( Locale locale, Chronology<C> overrideCalendar ) { if (overrideCalendar == null) { throw new NullPointerException("Missing override calendar."); } return new Builder<>(Moment.axis(), locale, overrideCalendar); } /** * <p>Compares the chronologies, default attributes, default values and * the internal format structures. </p> */ /*[deutsch] * <p>Vergleicht die Chronologien, Standard-Attribute, Standard-Ersatzwerte * und die internen Formatstrukturen. </p> */ @Override public boolean equals(Object obj) { if (this == obj) { return true; } else if (obj instanceof ChronoFormatter) { ChronoFormatter<?> that = (ChronoFormatter<?>) obj; return ( this.chronology.equals(that.chronology) && isEqual(this.overrideHandler, that.overrideHandler) && this.globalAttributes.equals(that.globalAttributes) && this.defaults.equals(that.defaults) && this.steps.equals(that.steps) ); } else { return false; } } /*[deutsch] * <p>Berechnet den Hash-Code basierend auf dem internen Zustand. </p> */ @Override public int hashCode() { return ( 7 * this.chronology.hashCode() + 31 * this.globalAttributes.hashCode() + 37 * this.steps.hashCode()); } /** * <p>For debugging purposes. </p> */ /*[deutsch] * <p>Für Debugging-Zwecke. </p> */ @Override public String toString() { StringBuilder sb = new StringBuilder(256); sb.append("net.time4j.format.ChronoFormatter[chronology="); sb.append(this.chronology.getChronoType().getName()); if (this.overrideHandler != null) { sb.append(", override="); sb.append(this.overrideHandler); } sb.append(", default-attributes="); sb.append(this.globalAttributes); sb.append(", default-values="); sb.append(this.defaults); sb.append(", processors="); boolean first = true; for (FormatStep d : this.steps) { if (first) { first = false; sb.append('{'); } else { sb.append('|'); } sb.append(d); } sb.append("}]"); return sb.toString(); } // internal view used by FormatStep AttributeSet getAttributes0() { return this.globalAttributes; } // used by MultiFormatParser boolean isToleratingTrailingChars() { return this.trailing; } // used by CustomizedProcessor boolean isSingleStepOptimizationPossible() { return ((this.stepCount == 1) && !this.hasOptionals); } private boolean getSingleStepMode() { boolean optSingleStep = this.isSingleStepOptimizationPossible(); if (optSingleStep) { FormatProcessor<?> processor = this.steps.get(0).getProcessor(); if (processor instanceof CustomizedProcessor) { optSingleStep = CustomizedProcessor.class.cast(processor).isSingleStepMode(); } else if (!(processor instanceof StyleProcessor)) { optSingleStep = false; } } return optSingleStep; } private String format0(ChronoDisplay display) { StringBuilder buffer = new StringBuilder(this.steps.size() * 8); try { this.print(display, buffer, this.globalAttributes, false); } catch (IOException ioe) { throw new AssertionError(ioe); } return buffer.toString(); } private ChronoDisplay display( T formattable, AttributeQuery query ) { if (this.overrideHandler == null) { return this.chronology.preformat(formattable, query); } try { Class<?> otype = this.overrideHandler.getCalendarOverride().getChronoType(); StartOfDay startOfDay = query.get(Attributes.START_OF_DAY, this.overrideHandler.getDefaultStartOfDay()); Moment m = Moment.class.cast(formattable); TZID tzid = query.get(Attributes.TIMEZONE_ID); GeneralTimestamp<?> tsp; if (CalendarVariant.class.isAssignableFrom(otype)) { CalendarFamily<?> family = cast(this.overrideHandler.getCalendarOverride()); String variant = query.get(Attributes.CALENDAR_VARIANT); tsp = m.toGeneralTimestamp(family, variant, tzid, startOfDay); } else if (Calendrical.class.isAssignableFrom(otype)) { Chronology<? extends Calendrical> axis = cast(this.overrideHandler.getCalendarOverride()); tsp = m.toGeneralTimestamp(axis, tzid, startOfDay); } else { throw new IllegalStateException("Unexpected calendar override: " + otype); } return new ZonalDisplay(tsp, tzid); } catch (ClassCastException ex) { throw new IllegalArgumentException("Not formattable: " + formattable, ex); } catch (NoSuchElementException ex) { // missing timezone or calendar variant throw new IllegalArgumentException(ex.getMessage(), ex); } } private static <C> C parse( ChronoFormatter<?> cf, Chronology<C> outer, int depth, CharSequence text, ParseLog status, AttributeQuery attrs, Leniency leniency, boolean quickPath ) { Chronology<?> inner = outer.preparser(); if ((inner == null) || (outer == cf.deepestParser)) { return parse(cf, outer, outer.getExtensions(), text, status, attrs, leniency, depth > 0, quickPath); } Object intermediate; if (inner == cf.deepestParser) { // potentially limits recursion depth intermediate = parse(cf, inner, inner.getExtensions(), text, status, attrs, leniency, true, quickPath); } else { intermediate = parse(cf, inner, depth + 1, text, status, attrs, leniency, quickPath); } if (status.isError()) { return null; } else if (intermediate == null) { ChronoEntity<?> parsed = status.getRawValues(); status.setError( text.length(), getReason(parsed) + getDescription(parsed)); return null; } ChronoEntity<?> parsed = status.getRawValues0(); C result; try { if (inner instanceof TimeAxis) { ChronoElement<?> self = TimeAxis.class.cast(inner).element(); updateSelf(parsed, self, intermediate); result = outer.createFrom(parsed, attrs, leniency.isLax(), false); } else if (outer instanceof BridgeChronology) { result = outer.createFrom(ChronoEntity.class.cast(intermediate), Attributes.empty(), false, false); } else { throw new IllegalStateException("Unsupported chronology or preparser: " + outer); } } catch (RuntimeException re) { status.setError( text.length(), re.getMessage() + getDescription(parsed)); return null; } if (result == null) { if (!status.isError()) { status.setError( text.length(), getReason(parsed) + getDescription(parsed)); } return null; } else if (leniency.isStrict()) { return checkConsistency(parsed, result, text, status); } else { return result; } } private static <T> void updateSelf( ChronoEntity<?> parsed, ChronoElement<T> element, Object result ) { parsed.with(element, element.getType().cast(result)); } private static <T> T parse( ChronoFormatter<?> cf, ChronoMerger<T> merger, List<ChronoExtension> extensions, CharSequence text, ParseLog status, AttributeQuery attributes, Leniency leniency, boolean preparsing, boolean quickPath ) { int len = text.length(); if (status.getPosition() >= len) { throw new IndexOutOfBoundsException( "[" + status.getPosition() + "]: " + text.toString()); } // Phase 1: elementweise Interpretation und Sammeln der Elementwerte ChronoEntity<?> parsed = null; try { if (cf.singleStepMode && !preparsing) { ParsedValue parsedValue = new ParsedValue(); cf.steps.get(0).parse(text, status, attributes, parsedValue, quickPath); if (status.isError()) { return null; } try { T result = parsedValue.getResult(); if (result != null) { return result; } } catch (ClassCastException cce) { // ok, now let's handle it like having multiple steps } parsed = parsedValue; status.setRawValues(parsed); } else { parsed = cf.parseElements(text, status, attributes, quickPath, cf.countOfElements); status.setRawValues(parsed); } } catch (AmbivalentValueException ex) { if (!status.isError()) { status.setError(status.getPosition(), ex.getMessage()); } } if ((parsed == null) || status.isError()) { return null; } // Phase 2: Anreicherung mit Default-Werten if (!cf.defaults.isEmpty()) { Set<ChronoElement<?>> parsedElements = null; for (ChronoElement<?> de : cf.defaults.keySet()) { if (!parsed.contains(de)) { char s = de.getSymbol(); boolean inject = true; if (s != '\u0000') { if (parsedElements == null) { parsedElements = parsed.getRegisteredElements(); // lazy } for (ChronoElement<?> pe : parsedElements) { if (pe.getSymbol() == s) { inject = false; break; } } } if (inject) { setValue(parsed, de, cf.getDefaultValue(de, parsed)); } } } } // Phase 3: Auflösung von Elementwerten in chronologischen Erweiterungen if (cf.needsExtensions) { try { for (ChronoExtension ext : extensions) { parsed = ext.resolve(parsed, cf.getLocale(), attributes); } } catch (RuntimeException re) { status.setError( len, re.getMessage() + getDescription(parsed)); return null; } } // Phase 4: Transformation der Elementwerte zum Typ T (ChronoMerger) T result; try { result = merger.createFrom(parsed, attributes, leniency.isLax(), preparsing); } catch (RuntimeException re) { status.setError( len, re.getMessage() + getDescription(parsed)); return null; } if ( (cf.fracproc != null) && (result instanceof ChronoEntity) ) { // Sonderfall Bruchzahlelement ChronoEntity<?> entity = ChronoEntity.class.cast(result); result = cast(cf.fracproc.update(entity, parsed)); } // Phase 5: Konsistenzprüfung if (result == null) { if (!preparsing) { status.setError( len, getReason(parsed) + getDescription(parsed)); } return null; } else if (leniency.isStrict()) { return checkConsistency(parsed, result, text, status); } else { return result; } } private static <V> void setValue( ChronoEntity<?> parsed, ChronoElement<V> element, Object value ) { parsed.with(element, element.getType().cast(value)); } @SuppressWarnings("unchecked") private static <T> T cast(Object obj) { return (T) obj; } private static String getReason(ChronoEntity<?> parsed) { String reason; if (parsed.contains(ValidationElement.ERROR_MESSAGE)) { reason = "Validation failed => " + parsed.get(ValidationElement.ERROR_MESSAGE); parsed.with(ValidationElement.ERROR_MESSAGE, null); } else { reason = "Insufficient data:"; } return reason; } private static boolean isEqual( Object obj1, Object obj2 ) { return ((obj1 == null) ? (obj2 == null) : obj1.equals(obj2)); } private static <T> void addPattern( Builder<T> builder, String pattern, PatternType type ) { int n = pattern.length(); StringBuilder sb = new StringBuilder(n); for (int i = 0; i < n; i++) { char c = pattern.charAt(i); if (c == '\'') { i++; while (i < n) { if (pattern.charAt(i) == '\'') { if ((i + 1 < n) && (pattern.charAt(i + 1) == '\'')) { i++; } else { break; } } i++; } } else { sb.append(c); } } String p = sb.toString(); // literals are now stripped off switch (type) { case CLDR: case CLDR_24: case SIMPLE_DATE_FORMAT: case THREETEN: case NON_ISO_DATE: if (p.contains("h") || p.contains("K")) { if (!p.contains("a") && !p.contains("b") && !p.contains("B")) { throw new IllegalArgumentException( "12-hour-clock requires am/pm-marker or dayperiod: " + pattern); } } if (p.contains("Y")) { if ((p.contains("M") || p.contains("L")) && !p.contains("w")) { throw new IllegalArgumentException( "Y as week-based-year requires a week-date-format: " + pattern); } } if (p.contains("D")) { if ((p.contains("M") || p.contains("L")) && !p.contains("d")) { throw new IllegalArgumentException( "D is the day of year but not the day of month: " + pattern); } } break; default: // no sanity check } builder.addPattern(pattern, type); } private static <T> T checkConsistency( ChronoEntity<?> parsed, T result, CharSequence text, ParseLog status ) { // Zeitzonenkonversion ergibt immer Unterschied zwischen // lokaler und globaler Zeit => lokale Elemente nicht prüfen! if (result instanceof UnixTime) { UnixTime ut = UnixTime.class.cast(result); // check offset+tzid if ( parsed.contains(TimezoneElement.TIMEZONE_ID) && parsed.contains(TimezoneElement.TIMEZONE_OFFSET) ) { TZID tzid = parsed.get(TimezoneElement.TIMEZONE_ID); TZID offset = parsed.get(TimezoneElement.TIMEZONE_OFFSET); if (!Timezone.of(tzid).getOffset(ut).equals(offset)) { status.setError(text.length(), "Ambivalent offset information: " + tzid + " versus " + offset); return null; } } // check tz-naming if (status.getDSTInfo() != null) { TZID tzid = parsed.getTimezone(); try { boolean dst = Timezone.of(tzid).isDaylightSaving(ut); if (dst != status.getDSTInfo().booleanValue()) { StringBuilder reason = new StringBuilder(256); reason.append("Conflict found: "); reason.append("Parsed entity is "); if (!dst) { reason.append("not "); } reason.append("daylight-saving, but timezone name"); reason.append(" has not the appropriate form in {"); reason.append(text.toString()); reason.append("}."); status.setError(text.length(), reason.toString()); result = null; } } catch (IllegalArgumentException iae) { StringBuilder reason = new StringBuilder(256); reason.append("Unable to check timezone name: "); reason.append(iae.getMessage()); status.setError(text.length(), reason.toString()); return null; } } } else if (result instanceof ChronoDisplay) { ChronoEntity<?> date = null; ChronoDisplay test = (ChronoDisplay) result; if ( (result instanceof PlainTimestamp) && (PlainTimestamp.class.cast(result).getHour() == 0) && ( (parsed.getInt(PlainTime.ISO_HOUR) == 24) || (parsed.contains(PlainTime.COMPONENT) && (parsed.get(PlainTime.COMPONENT).getHour() == 24)) ) ) { date = PlainTimestamp.class.cast(result).toDate().minus(1, CalendarUnit.DAYS); } for (ChronoElement<?> e : parsed.getRegisteredElements()) { if ((e == PlainTime.SECOND_OF_MINUTE) && (parsed.getInt(PlainTime.SECOND_OF_MINUTE) == 60)) { continue; } if (date != null) { if (e.isDateElement()) { test = date; } else if (e.isTimeElement()) { test = PlainTime.midnightAtEndOfDay(); } } if (test.contains(e)) { Object value = null; boolean ok = true; if (e.getType() == Integer.class) { ChronoElement<Integer> ie = cast(e); int v = parsed.getInt(ie); if (test.getInt(ie) != v) { value = Integer.valueOf(v); ok = false; } } else { value = parsed.get(e); ok = test.get(e).equals(value); } if (!ok) { StringBuilder reason = new StringBuilder(256); reason.append("Conflict found: "); reason.append("Text {"); reason.append(text.toString()); reason.append("} with element "); reason.append(e.name()); reason.append(" {"); reason.append(value); reason.append("}, but parsed entity "); reason.append("has element value {"); reason.append(test.get(e)); reason.append("}."); status.setError(text.length(), reason.toString()); return null; } } } } return result; } private ChronoEntity<?> parseElements( CharSequence text, ParseLog status, AttributeQuery attributes, boolean quickPath, int countOfElements ) { ParsedValues values = new ParsedValues(countOfElements, this.indexable); values.setPosition(status.getPosition()); Deque<ParsedValues> data = null; if (this.hasOptionals) { data = new LinkedList<>(); data.push(values); } int previous = 0; int current = 0; int index = 0; int len = this.steps.size(); while (index < len) { FormatStep step = this.steps.get(index); ParsedValues parsedResult; if (data == null) { parsedResult = values; } else { current = step.getLevel(); int level = current; // Start einer optionalen Sektion: Stack erweitern while (level > previous) { values = new ParsedValues(countOfElements >>> 1, this.indexable); values.setPosition(status.getPosition()); data.push(values); level--; } // Ende einer optionalen Sektion: Werte im Stack sichern while (level < previous) { values = data.pop(); data.peek().putAll(values); level++; } parsedResult = data.peek(); } // Delegation der Element-Verarbeitung status.clearWarning(); step.parse(text, status, attributes, parsedResult, quickPath); // Im Warnzustand default-value verwenden? if (status.isWarning()) { ChronoElement<?> element = step.getProcessor().getElement(); if ((element != null) && this.defaults.containsKey(element)) { parsedResult.put(element, this.getDefaultValue(element, parsedResult)); parsedResult.with(ValidationElement.ERROR_MESSAGE, null); status.clearError(); status.clearWarning(); } } // Fehler-Auflösung if (status.isError()) { // nächsten oder-Block suchen int section = step.getSection(); int last = index; if (!step.isNewOrBlockStarted()) { for (int j = index + 1; j < len; j++) { FormatStep test = this.steps.get(j); if (test.isNewOrBlockStarted() && (test.getSection() == section)) { last = j; break; } } } if ((last > index) || step.isNewOrBlockStarted()) { // wenn gefunden, zum nächsten oder-Block springen if (data != null) { values = data.pop(); } status.clearError(); status.setPosition(values.getPosition()); values.reset(); // alte Werte verwerfen if (data != null) { data.push(values); } index = last; } else if (current == 0) { // Grundzustand => aussteigen if (data != null) { values = data.peek(); } values.setNoAmbivalentCheck(); return values; } else { // Ende des optionalen Abschnitts suchen for (int j = len - 1; j > index; j--) { if (this.steps.get(j).getSection() == section) { last = j; break; } } index = last; // Restauration der alten Werte und der Fehlerinformation current--; assert (data != null); values = data.pop(); status.clearError(); status.setPosition(values.getPosition()); } } else if (step.isNewOrBlockStarted()) { index = step.skipTrailingOrBlocks(); } // Schleifenzähler inkrementieren previous = current; index++; } // Verbleibende optionale Sektionen auflösen while (current > 0) { assert (data != null); values = data.pop(); data.peek().putAll(values); current--; } // Ergebnis if (data != null) { values = data.peek(); } values.setNoAmbivalentCheck(); return values; } private static String sub( int index, CharSequence text ) { int len = text.length(); if (len - index <= 10) { return text.subSequence(index, len).toString(); } return text.subSequence(index, index + 10).toString() + "..."; } private static String getDescription(ChronoEntity<?> parsed) { Set<ChronoElement<?>> elements = parsed.getRegisteredElements(); StringBuilder sb = new StringBuilder(elements.size() * 16); sb.append(" [parsed={"); boolean first = true; for (ChronoElement<?> element : elements) { if (first) { first = false; } else { sb.append(", "); } sb.append(element.name()); sb.append('='); sb.append(parsed.get(element)); } sb.append("}]"); return sb.toString(); } private Object getDefaultValue( ChronoElement<?> element, ChronoEntity<?> parsedData ) { Object obj = this.defaults.get(element); if (obj instanceof Supplier) { obj = Supplier.class.cast(obj).get(); } else if (obj instanceof ChronoElement) { obj = parsedData.get(ChronoElement.class.cast(obj)); } return obj; } private static Chronology<?> checkElement( Chronology<?> chronology, Chronology<?> override, ChronoElement<?> element ) { boolean support = chronology.isSupported(element); if (support) { return chronology; } if (override == null) { Chronology<?> child = chronology; while ((child = child.preparser()) != null) { if (child.isSupported(element)) { return child; } } } else if (element.isDateElement() && override.isSupported(element)) { return override; } else if (element.isTimeElement() && PlainTime.axis().isSupported(element)) { return PlainTime.axis(); } throw new IllegalArgumentException("Unsupported element: " + element.name()); } private static int getDepth( Chronology<?> test, Chronology<?> chronology, Chronology<?> override ) { if (override != null) { return -1; } else if (test.equals(chronology)) { return 0; } Chronology<?> child = chronology; int depth = 0; while ((child = child.preparser()) != null) { depth++; if (test.equals(child)) { return depth; } } return Integer.MAX_VALUE; } private List<FormatStep> freeze(List<FormatStep> steps) { List<FormatStep> frozen = new ArrayList<>(steps.size()); for (FormatStep step : steps) { frozen.add(step.quickPath(this)); } return Collections.unmodifiableList(frozen); } private boolean hasNoPreparser() { return ((this.chronology.preparser() == null) && (this.overrideHandler == null)); } //~ Innere Klassen ---------------------------------------------------- /** * <p>Builder for creating a new {@code ChronoFormatter}. </p> * * <p>This class is not <i>thread-safe</i> so a new instance is * necessary per thread. A new instance can be created by * {@link ChronoFormatter#setUp(Class, Locale)}. </p> * * @param <T> generic type of chronological entity * (subtype of {@code ChronoEntity}) * @author Meno Hochschild * @doctags.concurrency {mutable} */ /*[deutsch] * <p>Erzeugt ein neues Formatobjekt. </p> * * <p>Je Thread wird eine neue {@code Builder}-Instanz benötigt, * weil diese Klasse nicht <i>thread-safe</i> ist. Eine neue Instanz * wird mittels {@link ChronoFormatter#setUp(Class, Locale)} erzeugt. </p> * * @param <T> generic type of chronological entity * (subtype of {@code ChronoEntity}) * @author Meno Hochschild * @doctags.concurrency {mutable} */ public static final class Builder<T> { //~ Statische Felder/Initialisierungen ---------------------------- private static final AttributeKey<DayPeriod> CUSTOM_DAY_PERIOD = Attributes.createKey("CUSTOM_DAY_PERIOD", DayPeriod.class); //~ Instanzvariablen ---------------------------------------------- private final Chronology<T> chronology; private final Chronology<?> override; private final Locale locale; private List<FormatStep> steps; private LinkedList<AttributeSet> stack; private int sectionID; private int reservedIndex; private int leftPadWidth; private boolean prolepticGregorian; private DayPeriod dayPeriod; private Map<ChronoElement<?>, Object> defaultMap; private Chronology<?> deepestParser; private int depthOfParser; //~ Konstruktoren ------------------------------------------------- private Builder( Chronology<T> chronology, Locale locale ) { this(chronology, locale, null); } private Builder( Chronology<T> chronology, Locale locale, Chronology<?> override ) { super(); if (chronology == null) { throw new NullPointerException("Missing chronology."); } else if (locale == null) { throw new NullPointerException("Missing locale."); } this.chronology = chronology; this.override = override; this.locale = locale; this.steps = new ArrayList<>(); this.stack = new LinkedList<>(); this.sectionID = 0; this.reservedIndex = -1; this.leftPadWidth = 0; this.prolepticGregorian = false; this.dayPeriod = null; this.defaultMap = new HashMap<>(); this.deepestParser = chronology; this.depthOfParser = 0; } //~ Methoden ------------------------------------------------------ /** * <p>Returns the calendar override if set else the associated chronology. </p> * * @return Chronology * @since 3.11/4.8 */ /*[deutsch] * <p>Liefert die separate Kalenderchronologie, wenn gesetzt, sonst die zugehörige Chronologie. </p> * * @return Chronology * @since 3.11/4.8 */ public Chronology<?> getChronology() { return ((this.override == null) ? this.chronology : this.override); } /** * <p>Defines an integer format without sign for given * chronological element. </p> * * <p>Equivalent to {@code addInteger(element, minDigits, maxDigits, * SignPolicy.SHOW_NEVER}. </p> * * @param element chronological element * @param minDigits minimum count of digits in range 1-9 * @param maxDigits maximum count of digits in range 1-9 * @return this instance for method chaining * @throws IllegalArgumentException if any of {@code minDigits} and * {@code maxDigits} are out of range {@code 1-9} or if * {@code maxDigits < minDigits} or if given element is * not supported by chronology or its preparser * @throws IllegalStateException if a numerical element is added * multiple times in a row * @see Chronology#isSupported(ChronoElement) * @see SignPolicy#SHOW_NEVER * @see #addInteger(ChronoElement, int, int, SignPolicy) */ /*[deutsch] * <p>Definiert ein Ganzzahlformat ohne Vorzeichen für das * angegebene chronologische Element. </p> * * <p>Entspricht {@code addInteger(element, minDigits, maxDigits, * SignPolicy.SHOW_NEVER}. </p> * * @param element chronological element * @param minDigits minimum count of digits in range 1-9 * @param maxDigits maximum count of digits in range 1-9 * @return this instance for method chaining * @throws IllegalArgumentException if any of {@code minDigits} and * {@code maxDigits} are out of range {@code 1-9} or if * {@code maxDigits < minDigits} or if given element is * not supported by chronology or its preparser * @throws IllegalStateException if a numerical element is added * multiple times in a row * @see Chronology#isSupported(ChronoElement) * @see SignPolicy#SHOW_NEVER * @see #addInteger(ChronoElement, int, int, SignPolicy) */ public Builder<T> addInteger( ChronoElement<Integer> element, int minDigits, int maxDigits ) { return this.addNumber( element, false, minDigits, maxDigits, SignPolicy.SHOW_NEVER ); } /** * <p>Defines an integer format for given chronological * element. </p> * * <p>First a sign is expected (positive or negative) where the * last argument {@code signPolicy} controls the output and * interpretation. Following rules hold for the sequence of * digits to be formatted: </p> * * <ol><li>PRINT => If the resulting sequence of digits has * less than {@code minDigits} then it will be left-padded with * zero digit char until the sequence has {@code minDigits} * digits. But if there are more digits than {@code maxDigits} * then an {@code IllegalArgumentException} will be thrown. </li> * * <li>PARSE => At most {@code maxDigits} chars will be * interpreted as digits. If there are less than {@code minDigits} * then the text input will be invalid. Note: If there is no * strict or smart mode (lax) then the parser will always assume * {@code minDigits == 0} and {@code maxDigits = 9}. </li></ol> * * <p>Note: The argument {@code maxDigits} will only be taken into account * if the arabic number system is used. </p> * * <p>Example: </p> * <pre> * ChronoElement<Integer> element = PlainTime.MILLI_OF_SECOND; * int minDigits = 3; * int maxDigits = 6; * * ChronoFormatter<PlainTime> formatter = * ChronoFormatter.setUp(PlainTime.class, Locale.US) * .addInteger( * element, * minDigits, * maxDigits, * SignPolicy.SHOW_ALWAYS) * .build(); * System.out.println( * formatter.format(PlainTime.of(12, 0, 0, 12345678))); * // output: +012 * </pre> * * @param element chronological element * @param minDigits minimum count of digits in range 1-9 * @param maxDigits maximum count of digits in range 1-9 * @param signPolicy controls output of numeric sign * @return this instance for method chaining * @throws IllegalArgumentException if any of {@code minDigits} and * {@code maxDigits} are out of range {@code 1-9} or if * {@code maxDigits < minDigits} or if given element is * not supported by chronology or its preparser * @throws IllegalStateException if a numerical element is added * multiple times in a row * @see Chronology#isSupported(ChronoElement) * @see Attributes#LENIENCY */ /*[deutsch] * <p>Definiert ein Ganzzahlformat für das angegebene * chronologische Element. </p> * * <p>Zuerst wird das Vorzeichen erwartet (positiv oder negativ). * Das Argument {@code signPolicy} regelt hier die Ausgabe und * Interpretation. Für die Ziffernfolge der zu formatierenden * Zahl gilt: </p> * * <ol><li>PRINT => Hat die resultierende Ziffernfolge weniger * als {@code minDigits} Stellen, wird links mit der Nullziffer * aufgefüllt, bis {@code minDigits} Stellen erreicht sind. * Gibt es hingegen mehr als {@code maxDigits} Stellen, wird eine * {@code IllegalArgumentException} generiert. </li> * * <li>PARSE => Es werden bis zu {@code maxDigits} Zeichen als * Ziffern interpretiert. Gibt es aber weniger als {@code minDigits} * Stellen, wird die Texteingabe als ungültig angesehen. Zu * beachten: Ist ein laxer Parse-Modus angegeben, dann wird * unabhängig von den hier angegebenen Argumenten stets * {@code minDigits == 0} und die Obergrenze von {@code maxDigits = 9} * angenommen. </li></ol> * * <p>Hinweis: Das Argument {@code maxDigits} wird nur dann berücksichtigt, * wenn das arabische Zahlensystem verwendet wird. </p> * * <p>Beispiel: </p> * <pre> * ChronoElement<Integer> element = PlainTime.MILLI_OF_SECOND; * int minDigits = 3; * int maxDigits = 6; * * ChronoFormatter<PlainTime> formatter = * ChronoFormatter.setUp(PlainTime.class, Locale.US) * .addInteger( * element, * minDigits, * maxDigits, * SignPolicy.SHOW_ALWAYS) * .build(); * System.out.println( * formatter.format(PlainTime.of(12, 0, 0, 12345678))); * // Ausgabe: +012 * </pre> * * @param element chronological element * @param minDigits minimum count of digits in range 1-9 * @param maxDigits maximum count of digits in range 1-9 * @param signPolicy controls output of numeric sign * @return this instance for method chaining * @throws IllegalArgumentException if any of {@code minDigits} and * {@code maxDigits} are out of range {@code 1-9} or if * {@code maxDigits < minDigits} or if given element is * not supported by chronology or its preparser * @throws IllegalStateException if a numerical element is added * multiple times in a row * @see Chronology#isSupported(ChronoElement) * @see Attributes#LENIENCY */ public Builder<T> addInteger( ChronoElement<Integer> element, int minDigits, int maxDigits, SignPolicy signPolicy ) { return this.addNumber( element, false, minDigits, maxDigits, signPolicy ); } /** * <p>Defines an integer format for given chronological * element. </p> * * <p>Like {@link #addInteger(ChronoElement, int, int, * SignPolicy)} but on long-basis. </p> * * @param element chronological element * @param minDigits minimum count of digits in range 1-18 * @param maxDigits maximum count of digits in range 1-18 * @param signPolicy controls output of numeric sign * @return this instance for method chaining * @throws IllegalArgumentException if any of {@code minDigits} and * {@code maxDigits} are out of range {@code 1-18} or if * {@code maxDigits < minDigits} or if given element is * not supported by chronology or its preparser * @throws IllegalStateException if a numerical element is added * multiple times in a row * @see Chronology#isSupported(ChronoElement) */ /*[deutsch] * <p>Definiert ein Ganzzahlformat für das angegebene * chronologische Element. </p> * * <p>Wie {@link #addInteger(ChronoElement, int, int, * SignPolicy)}, aber auf long-Basis. </p> * * @param element chronological element * @param minDigits minimum count of digits in range 1-18 * @param maxDigits maximum count of digits in range 1-18 * @param signPolicy controls output of numeric sign * @return this instance for method chaining * @throws IllegalArgumentException if any of {@code minDigits} and * {@code maxDigits} are out of range {@code 1-18} or if * {@code maxDigits < minDigits} or if given element is * not supported by chronology or its preparser * @throws IllegalStateException if a numerical element is added * multiple times in a row * @see Chronology#isSupported(ChronoElement) */ public Builder<T> addLongNumber( ChronoElement<Long> element, int minDigits, int maxDigits, SignPolicy signPolicy ) { return this.addNumber( element, false, minDigits, maxDigits, signPolicy ); } /** * <p>Defines an integer format without sign and with fixed width * for given chronological element. </p> * * <p>Almost equivalent to * {@code addInteger(element, digits, digits, SignPolicy.SHOW_NEVER)} * but with following important difference: </p> * * <p>If this method directly follow after other numerical elements * then the fixed width defined here will be preserved in preceding * elements so parsing of those ancestors will not consume too many * digits (<i>adjacent digit parsing</i>). </p> * * @param element chronological element * @param digits fixed count of digits in range 1-9 * @return this instance for method chaining * @throws IllegalArgumentException if {@code digits} is out of * range {@code 1-9} or if given element is not supported * by chronology or its preparser * @see Chronology#isSupported(ChronoElement) * @see SignPolicy#SHOW_NEVER * @see #addInteger(ChronoElement, int, int, SignPolicy) */ /*[deutsch] * <p>Definiert ein Ganzzahlformat ohne Vorzeichen und mit fester * Breite für das angegebene chronologische Element. </p> * * <p>Entspricht im wesentlichen der Methode * {@code addInteger(element, digits, digits, SignPolicy.SHOW_NEVER)} * mit folgendem wichtigen Unterschied: </p> * * <p>Folgt diese Methode direkt nach anderen numerischen Elementen, * wird die hier definierte feste Breite beim Parsen vorreserviert, * so daß vorangehende numerische Elemente nicht zuviele * Ziffern interpretieren (<i>adjacent digit parsing</i>). </p> * * @param element chronological element * @param digits fixed count of digits in range 1-9 * @return this instance for method chaining * @throws IllegalArgumentException if {@code digits} is out of * range {@code 1-9} or if given element is not supported * by chronology * @see Chronology#isSupported(ChronoElement) * @see SignPolicy#SHOW_NEVER * @see #addInteger(ChronoElement, int, int, SignPolicy) */ public Builder<T> addFixedInteger( ChronoElement<Integer> element, int digits ) { return this.addNumber( element, true, digits, digits, SignPolicy.SHOW_NEVER ); } /** * <p>Defines an integer format without sign for given chronological * enumeration element. </p> * * <p>If the element is compatible to the interface * {@code NumericalElement} then its value will first converted to * an integer else the ordinal number of enum will be used. A sign * is never printed or expected. Example: </p> * * <pre> * ChronoFormatter<PlainDate> formatter = * ChronoFormatter.setUp(PlainDate.class, Locale.US) * .addNumerical(Weekmodel.of(Locale.US).localDayOfWeek(), 1, 1) * .build(); * System.out.println( * formatter.format(PlainDate.of(2013, 6, 14))); // Freitag * // output: 6 (next to last day of US week) * </pre> * * @param <V> generic type of element values * @param element chronological element * @param minDigits minimum count of digits in range 1-9 * @param maxDigits maximum count of digits in range 1-9 * @return this instance for method chaining * @throws IllegalArgumentException if any of {@code minDigits} and * {@code maxDigits} are out of range {@code 1-9} or if * {@code maxDigits < minDigits} or if given element is * not supported by chronology or its preparser * @throws IllegalStateException if a numerical element is added * multiple times in a row * @see Chronology#isSupported(ChronoElement) * @see NumericalElement#numerical(java.lang.Object) * NumericalElement.numerical(V) * @see SignPolicy#SHOW_NEVER */ /*[deutsch] * <p>Definiert ein Ganzzahlformat ohne Vorzeichen für das * angegebene chronologische Aufzählungselement. </p> * * <p>Ist das Element kompatibel zum Interface {@code NumericalElement}, * dann wird auf Basis seiner numerischen Konversion zu einem Integer * formatiert, sonst auf Basis seiner Enum-Ordinalzahl. Ein Vorzeichen * wird nie ausgegeben oder erwartet. Beispiel: </p> * * <pre> * ChronoFormatter<PlainDate> formatter = * ChronoFormatter.setUp(PlainDate.class, Locale.US) * .addNumerical(Weekmodel.of(Locale.US).localDayOfWeek(), 1, 1) * .build(); * System.out.println( * formatter.format(PlainDate.of(2013, 6, 14))); // Freitag * // Ausgabe: 6 (vorletzter Tag der US-Woche) * </pre> * * @param <V> generic type of element values * @param element chronological element * @param minDigits minimum count of digits in range 1-9 * @param maxDigits maximum count of digits in range 1-9 * @return this instance for method chaining * @throws IllegalArgumentException if any of {@code minDigits} and * {@code maxDigits} are out of range {@code 1-9} or if * {@code maxDigits < minDigits} or if given element is * not supported by chronology or its preparser * @throws IllegalStateException if a numerical element is added * multiple times in a row * @see Chronology#isSupported(ChronoElement) * @see NumericalElement#numerical(java.lang.Object) * NumericalElement.numerical(V) * @see SignPolicy#SHOW_NEVER */ public <V extends Enum<V>> Builder<T> addNumerical( ChronoElement<V> element, int minDigits, int maxDigits ) { return this.addNumber( element, false, minDigits, maxDigits, SignPolicy.SHOW_NEVER ); } /** * <p>Defines an integer format without sign and with fixed width * for given chronological enumeration element. </p> * * <p>Almost equivalent to {@code addNumerical(element, digits, digits)} * but with following important difference: </p> * * <p>If this method directly follow after other numerical elements * then the fixed width defined here will be preserved in preceding * elements so parsing of those ancestors will not consume too many * digits (<i>adjacent digit parsing</i>). </p> * * @param <V> generic type of element values * @param element chronological element * @param digits fixed count of digits in range 1-9 * @return this instance for method chaining * @throws IllegalArgumentException if {@code digits} is out of * range {@code 1-9} or if given element is not supported * by chronology or its preparser * @see Chronology#isSupported(ChronoElement) * @see #addNumerical(ChronoElement, int, int) * addNumerical(ChronoElement, int, int) */ /*[deutsch] * <p>Definiert ein Ganzzahlformat ohne Vorzeichen und mit fester Breite * für das angegebene chronologische Aufzählungselement. </p> * * <p>Entspricht im wesentlichen der Methode * {@code addNumerical(element, digits, digits)} mit * folgendem wichtigen Unterschied: </p> * * <p>Folgt diese Methode direkt nach anderen numerischen Elementen, * wird die hier definierte feste Breite beim Parsen vorreserviert, * so daß vorangehende numerische Elemente nicht zuviele * Ziffern interpretieren (<i>adjacent digit parsing</i>). </p> * * @param <V> generic type of element values * @param element chronological element * @param digits fixed count of digits in range 1-9 * @return this instance for method chaining * @throws IllegalArgumentException if {@code digits} is out of * range {@code 1-9} or if given element is not supported * by chronology or its preparser * @see Chronology#isSupported(ChronoElement) * @see #addNumerical(ChronoElement, int, int) * addNumerical(ChronoElement, int, int) */ public <V extends Enum<V>> Builder<T> addFixedNumerical( ChronoElement<V> element, int digits ) { return this.addNumber( element, true, digits, digits, SignPolicy.SHOW_NEVER ); } /** * <p>Defines a fractional format for given chronological element * including a possible decimal separator char but without any * integer part by mapping the context-dependent value range to * the interval [0.0-1.0). </p> * * <p>First a leading decimal separator char will be formatted * if required by last argument (for example in US the dot, in * Germany a comma). Then the fractional digits follow by mean of * the formula {@code (value - min) / (max - min + 1)}. Possible * gaps like offset-jumps in the value range mapping will be kept. * The fractional representation is most suitable for elements * with a fixed value range, for example {@code MINUTE_OF_HOUR} * or {@code MILLI_OF_SECOND}. </p> * * <ol><li>PRINT => If the resulting sequence of digits after * the decimal separator char has less than {@code minDigits} * then it will be right-padded with zero digit char until there * are {@code minDigits} digits. But if there are more than * {@code maxDigits} then the sequence of digits will be * truncated. In the special case of {@code minDigits == 0} * and if the sequence to be formatted has no digits then the * decimal separator char will be left out. </li> * * <li>PARSE => At most {@code maxDigits} chars will be * interpreted as digits. If there are less than {@code minDigits} * then the text input will be invalid but only in strict mode. Note: * If there is just a lax mode then the parser will always assume * {@code minDigits == 0} and {@code maxDigits = 9} unless a fixed * width was implicitly specified by setting {@code minDigits == maxDigits} * and without decimal separator char (then <i>adjacent digit parsing</i>). </li> * </ol> * * <p>Example: </p> * <pre> * ChronoElement<Integer> element = PlainTime.MICRO_OF_SECOND; * int minDigits = 3; * int maxDigits = 6; * * ChronoFormatter<PlainTime> formatter = * ChronoFormatter.setUp(PlainTime.class, Locale.US) * .addFraction( * element, * minDigits, * maxDigits, * true * ).build(); * System.out.println( * formatter.format(PlainTime.of(12, 0, 0, 12345678))); * // output in US: .012345 * </pre> * * <p>Note: A fractional element must not be directly preceded by * another numerical element. </p> * * @param element chronological element * @param minDigits minimum count of digits after decimal * separator in range 0-9 * @param maxDigits maximum count of digits after decimal * separator in range 1-9 * @param decimalSeparator shall decimal separator be visible? * @return this instance for method chaining * @throws IllegalArgumentException if {@code minDigits} is out of * range {@code 0-9} or if {@code maxDigits} is out of range * {@code 1-9} or if {@code maxDigits < minDigits} or if * given element is not supported by chronology or its * preparser or if there is already a fractional or decimal * part defined * @see Chronology#isSupported(ChronoElement) * @see Attributes#LENIENCY */ /*[deutsch] * <p>Definiert ein Bruchzahlformat für das angegebene * chronologische Element inklusive Dezimaltrennzeichen, aber ohne * Integerteil, indem der kontextabhängige Wertbereich auf das * Intervall [0.0-1.0) abgebildet wird. </p> * * <p>Zuerst wird ein führendes Dezimaltrennzeichen in * lokalisierter Form formatiert, falls mit dem letzten Argument * angefordert (zum Beispiel in den USA ein Punkt, in Deutschland * ein Komma). Dann folgen die Nachkommastellen mit Hilfe der * Formel {@code (value - min) / (max - min + 1)}. Eventuelle * Lücken wie Zeitzonensprünge im Wertbereich bleiben * erhalten. Am besten eignet sich die fraktionale Darstellung * für Elemente mit einem festen Wertbereich, zum Beispiel * {@code MINUTE_OF_HOUR} oder {@code MILLI_OF_SECOND}. </p> * * <ol><li>PRINT => Hat die resultierende Ziffernfolge nach dem * Dezimaltrennzeichen weniger als {@code minDigits} Stellen, wird * rechts mit der Nullziffer aufgefüllt, bis {@code minDigits} * Stellen erreicht sind. Gibt es hingegen mehr als {@code maxDigits} * Stellen, wird die Ziffernfolge bei {@code maxDigits} Stellen * abgeschnitten. Wird als Sonderfall {@code minDigits == 0} definiert, * und hat die Ziffernfolge keine Nachkommastellen, wird auch das * Dezimaltrennzeichen weggelassen. </li> * * <li>PARSE => Es werden bis zu {@code maxDigits} Zeichen als * Ziffern interpretiert. Gibt es aber weniger als {@code minDigits} * Stellen, wird die Texteingabe als ungültig angesehen, aber nur * im strikten Modus. Zu beachten: Ist nur ein laxer Parse-Modus angegeben, * dann wird unabhängig von den hier angegebenen Argumenten stets * {@code minDigits == 0} und {@code maxDigits == 9} angenommen, * es sei denn, es wurde implizit eine feste Breite mittels * {@code minDigits == maxDigits} und ohne Dezimaltrennzeichen * angegeben (dann gilt <i>adjacent digit parsing</i>). </li> * </ol> * * <p>Beispiel: </p> * <pre> * ChronoElement<Integer> element = PlainTime.MICRO_OF_SECOND; * int minDigits = 3; * int maxDigits = 6; * * ChronoFormatter<PlainTime> formatter = * ChronoFormatter.setUp(PlainTime.class, Locale.US) * .addFraction( * element, * minDigits, * maxDigits, * true * ).build(); * System.out.println( * formatter.format(PlainTime.of(12, 0, 0, 12345678))); * // Ausgabe in den USA: .012345 * </pre> * * <p>Hinweis: Direkt hinter einem fraktionalen Element darf kein * anderes numerisches Element folgen. </p> * * @param element chronological element * @param minDigits minimum count of digits after decimal * separator in range 0-9 * @param maxDigits maximum count of digits after decimal * separator in range 1-9 * @param decimalSeparator shall decimal separator be visible? * @return this instance for method chaining * @throws IllegalArgumentException if {@code minDigits} is out of * range {@code 0-9} or if {@code maxDigits} is out of range * {@code 1-9} or if {@code maxDigits < minDigits} or if * given element is not supported by chronology or its * preparser or if there is already a fractional or decimal * part defined * @see Chronology#isSupported(ChronoElement) * @see Attributes#LENIENCY */ public Builder<T> addFraction( ChronoElement<Integer> element, int minDigits, int maxDigits, boolean decimalSeparator ) { this.checkElement(element); boolean fixedWidth = (!decimalSeparator && (minDigits == maxDigits)); this.ensureOnlyOneFractional(fixedWidth, decimalSeparator); FormatProcessor<?> processor = new FractionProcessor( element, minDigits, maxDigits, decimalSeparator ); if ( (this.reservedIndex != -1) && fixedWidth ) { int ri = this.reservedIndex; FormatStep numStep = this.steps.get(ri); this.addProcessor(processor); FormatStep lastStep = this.steps.get(this.steps.size() - 1); if (numStep.getSection() == lastStep.getSection()) { this.reservedIndex = ri; this.steps.set(ri, numStep.reserve(minDigits)); } } else { this.addProcessor(processor); } return this; } /** * <p>Defines a fixed unsigned decimal format for given chronological * element. </p> * * <ol><li><p>PRINT => If the resulting sequence of digits before * the decimal separator char has less than {@code precision - scale} * digits then it will be left-padded with zero digit chars. Otherwise * if there are more integer digits than allowed then an exception will * be thrown.</p> * * <p>If the resulting sequence of digits after the decimal separator * is smaller than {@code scale} then the sequence will be right-padded * with zero digit chars. Otherwise the sequence of decimal digits will * be truncated if necessary. </p></li> * * <li>PARSE => Exactly {@code precision} chars will be * interpreted as digits in strict mode. Otherwise as many digits are * parsed as available and found. </li> * </ol> * * <p>Example: </p> * <pre> * ChronoElement<BigDecimal> element = PlainTime.DECIMAL_MINUTE; * int precision = 3; * int scale = 1; * * ChronoFormatter<PlainTime> formatter = * ChronoFormatter.setUp(PlainTime.class, Locale.US) * .addPattern("HH:", PatternType.CLDR) * .addFixedDecimal(element, precision, scale) * .build(); * System.out.println(formatter.format(PlainTime.of(12, 8, 30))); * // output: 12:08.5 * </pre> * * <p>Note: A decimal element must not be directly preceded by * another numerical element. </p> * * @param element chronological element * @param precision total count of digits {@code >= 2} * @param scale digits after decimal separator {@code >= 1} * @return this instance for method chaining * @throws IllegalArgumentException if {@code precision} is smaller * than {@code 2} or if {@code scale} is smaller than * {@code 1} or if {@code precision <= scale} or if * given element is not supported by chronology or its * preparser or if there is already a fractional or decimal * part defined * @see Chronology#isSupported(ChronoElement) * @see Attributes#LENIENCY */ /*[deutsch] * <p>Definiert ein festes Dezimalformat ohne Vorzeichen für das * angegebene chronologische Element. </p> * * <ol><li><p>PRINT => Wenn die Anzahl der Ziffern vor dem * Dezimaltrennzeichen kleiner als {@code precision - scale} ist, * dann wird von links mit Null-Ziffern aufgefüllt. Gibt es * andererseits mehr Ziffern als vorgegeben, wird eine Ausnahme * geworfen. </p> * * <p>Ist die Anzahl der Ziffern nach dem Dezimaltrennzeichen kleiner * als {@code scale}, dann wird die Sequenz rechts mit Null-Ziffern * aufgefüllt. Andernfalls wird die Sequenz bei Bedarf * abgeschnitten. </p></li> * * <li>PARSE => Exakt {@code precision} Zeichen werden nebst dem * Dezimaltrennzeichen als Ziffern im strikten Modus interpretiert. * Andernfalls werden soviele Ziffern interpretiert, wie welche * vorhanden sind. </li> * </ol> * * <p>Beispiel: </p> * <pre> * ChronoElement<BigDecimal> element = PlainTime.DECIMAL_MINUTE; * int precision = 3; * int scale = 1; * * ChronoFormatter<PlainTime> formatter = * ChronoFormatter.setUp(PlainTime.class, Locale.US) * .addPattern("HH:", PatternType.CLDR) * .addFixedDecimal(element, precision, scale) * .build(); * System.out.println(formatter.format(PlainTime.of(12, 8, 30))); * // Ausgabe: 12:08.5 * </pre> * * <p>Hinweis: Direkt hinter einem dezimalen Element darf kein * anderes numerisches Element folgen. </p> * * @param element chronological element * @param precision total count of digits {@code >= 2} * @param scale digits after decimal separator {@code >= 1} * @return this instance for method chaining * @throws IllegalArgumentException if {@code precision} is smaller * than {@code 2} or if {@code scale} is smaller than * {@code 1} or if {@code precision <= scale} or if * given element is not supported by chronology or its * preparser or if there is already a fractional or decimal * part defined * @see Chronology#isSupported(ChronoElement) * @see Attributes#LENIENCY */ public Builder<T> addFixedDecimal( ChronoElement<BigDecimal> element, int precision, int scale ) { this.checkElement(element); this.ensureDecimalDigitsOnlyOnce(); FormatProcessor<?> processor = new DecimalProcessor(element, precision, scale); if (this.reservedIndex != -1) { int ri = this.reservedIndex; FormatStep numStep = this.steps.get(ri); this.addProcessor(processor); FormatStep lastStep = this.steps.get(this.steps.size() - 1); if (numStep.getSection() == lastStep.getSection()) { this.reservedIndex = ri; this.steps.set(ri, numStep.reserve(precision - scale)); } } else { this.addProcessor(processor); } return this; } /** * <p>Defines an ordinal format for given chronological * element in english language. </p> * * <p>An ordinal indicator will be printed or parsed after a * given sequence of digits. In English the indicators "st", * "nd", "rd" and "th" are used * dependent on the value of the element. </p> * * @param element chronological element * @return this instance for method chaining * @throws IllegalArgumentException if given element is not * supported by the underlying chronology or its preparser * @throws IllegalStateException if a numerical element is added * multiple times in a row * @since 1.2 * @see Chronology#isSupported(ChronoElement) * @see #addOrdinal(ChronoElement,Map) */ /*[deutsch] * <p>Definiert ein Ordinalformat für das angegebene chronologische * Element in der englischen Sprache. </p> * * <p>Ein Ordinal-Indikator wird als Suffix an eine Ziffernfolge * angehängt. In Englisch gibt es die Indikatoren "st", * "nd", "rd" und "th", welche vom * numerischen Wert des Elements abhängig sind. </p> * * @param element chronological element * @return this instance for method chaining * @throws IllegalArgumentException if given element is not * supported by the underlying chronology or its preparser * @throws IllegalStateException if a numerical element is added * multiple times in a row * @since 1.2 * @see Chronology#isSupported(ChronoElement) * @see #addOrdinal(ChronoElement,Map) */ public Builder<T> addEnglishOrdinal(ChronoElement<Integer> element) { return this.addOrdinalProcessor(element, null); } /** * <p>Defines an ordinal format for given chronological * element. </p> * * <p>An ordinal indicator will be printed or parsed after a * given sequence of digits. In English the indicators "st", * "nd", "rd" and "th" are used * dependent on the value of the element. In many other languages a * fixed literal can be sufficient (although often context-dependent). * This method is necessary if the indicators of a given language * depend on the numerical value of the element. </p> * * <p>Example for French generating HTML-text: </p> * <pre> * ChronoElement<Integer> element = PlainDate.DAY_OF_MONTH; * Map<PluralCategory, String> indicators = * new HashMap<PluralCategory, String>(); * indicators.put(PluralCategory.ONE, "<sup>er</sup>"); * indicators.put(PluralCategory.OTHER, "<sup>e</sup>"); * * ChronoFormatter<PlainDate> formatter = * ChronoFormatter.setUp(PlainDate.class, Locale.FRENCH) * .addOrdinal(element, indicators) * .addLiteral(" jour") * .build(); * System.out.println( * formatter.format(PlainDate.of(2014, 8, 1))); * // output: 1<sup>er</sup> jour * </pre> * * <p>Note that dependent on the context other strings can be necessary, * for example French also uses feminine forms (for the week etc.). </p> * * @param element chronological element * @param indicators ordinal indicators to be used as suffixes * @return this instance for method chaining * @throws IllegalArgumentException if there is no indicator at least * for the plural category OTHER or if given element is not * supported by the underlying chronology or its preparser * @throws IllegalStateException if a numerical element is added * multiple times in a row * @since 1.2 * @see Chronology#isSupported(ChronoElement) */ /*[deutsch] * <p>Definiert ein Ordinalformat für das angegebene chronologische * Element. </p> * * <p>Ein Ordinal-Indikator wird als Suffix an eine Ziffernfolge * angehängt. In Englisch gibt es die Indikatoren "st", * "nd", "rd" und "th", welche vom * numerischen Wert des Elements abhängig sind. In vielen * anderen Sprachen ist eine festes Literal ausreichend (obwohl oft * kontext-abhängig). Diese Methode ist notwendig, wenn die * Indikatoren in einer gegebenen Sprache vom numerischen Wert des * Elements abhängig sind. </p> * * <p>Beispiel für Französisch - erzeugt HTML-Text: </p> * <pre> * ChronoElement<Integer> element = PlainDate.DAY_OF_MONTH; * Map<PluralCategory, String> indicators = * new HashMap<PluralCategory, String>(); * indicators.put(PluralCategory.ONE, "<sup>er</sup>"); * indicators.put(PluralCategory.OTHER, "<sup>e</sup>"); * * ChronoFormatter<PlainDate> formatter = * ChronoFormatter.setUp(PlainDate.class, Locale.FRENCH) * .addOrdinal(element, indicators) * .addLiteral(" jour") * .build(); * System.out.println( * formatter.format(PlainDate.of(2014, 8, 1))); * // Ausgabe: 1<sup>er</sup> jour * </pre> * * <p>Zu beachten ist, daß abhängig vom Kontext andere * Textformen notwendig sein können. Zum Beispiel kennt die * französische Sprache auch weibliche Formen (etwa für * die Woche etc.). </p> * * @param element chronological element * @param indicators ordinal indicators to be used as suffixes * @return this instance for method chaining * @throws IllegalArgumentException if there is no indicator at least * for the plural category OTHER or if given element is not * supported by the underlying chronology or its preparser * @throws IllegalStateException if a numerical element is added * multiple times in a row * @since 1.2 * @see Chronology#isSupported(ChronoElement) */ public Builder<T> addOrdinal( ChronoElement<Integer> element, Map<PluralCategory, String> indicators ) { if (indicators == null) { throw new NullPointerException("Missing ordinal indicators."); } return this.addOrdinalProcessor(element, indicators); } /** * <p>Defines a literal element with exactly one char. </p> * * <p>Equivalent to {@link #addLiteral(String) addLiteral(String.valueOf(literal))}. </p> * * @param literal single literal char * @return this instance for method chaining * @throws IllegalArgumentException if the char represents a non-printable ASCII-char */ /*[deutsch] * <p>Definiert ein Literalelement mit genau einem festen Zeichen. </p> * * <p>Äquivalent zu {@link #addLiteral(String) addLiteral(String.valueOf(literal))}. </p> * * @param literal single literal char * @return this instance for method chaining * @throws IllegalArgumentException if the char represents a non-printable ASCII-char */ public Builder<T> addLiteral(char literal) { return this.addLiteral(String.valueOf(literal)); } /** * <p>Defines a literal element with exactly one char which can also be an alternative char * during parsing. </p> * * <p>The literal char is a punctuation mark or a letter symbol. * Decimal digits as literal chars are not permitted. </p> * * @param literal single non-digit literal char (preferred in printing) * @param alt alternative non-digit literal char for parsing * @return this instance for method chaining * @throws IllegalArgumentException if any char represents a non-printable ASCII-char or a decimal digit * @since 3.1 */ /*[deutsch] * <p>Definiert ein Literalelement mit genau einem festen Zeichen, das beim Parsen * auch ein Alternativzeichen sein kann. </p> * * <p>Es handelt sich um ein Interpunktionszeichen oder ein Buchstabensymbol. * Dezimalziffern als Literal sind nicht zugelassen. </p> * * @param literal single non-digit literal char (preferred in printing) * @param alt alternative non-digit literal char for parsing * @return this instance for method chaining * @throws IllegalArgumentException if any char represents a non-printable ASCII-char or a decimal digit * @since 3.1 */ public Builder<T> addLiteral( char literal, char alt ) { this.addProcessor(new LiteralProcessor(literal, alt)); return this; } /** * <p>Defines a literal element with any chars. </p> * * <p>Usually the literal char sequence consists of punctuation marks or letter symbols. * Exceptionally, digit chars are possible although such chars will only be parsed as * literals and not associated with any chronological meaning. </p> * * @param literal literal char sequence * @return this instance for method chaining * @throws IllegalArgumentException if given literal is empty or starts with a non-printable ASCII-char */ /*[deutsch] * <p>Definiert ein Literalelement mit beliebigen Zeichen. </p> * * <p>In der Regel handelt es sich um Interpunktionszeichen oder Buchstabensymbole. * In Ausnahmefällen können es auch Ziffern sein, die dann aber eben nur * als Literale und nicht chronologisch interpretiert werden. </p> * * @param literal literal char sequence * @return this instance for method chaining * @throws IllegalArgumentException if given literal is empty or starts with a non-printable ASCII-char */ public Builder<T> addLiteral(String literal) { LiteralProcessor processor = new LiteralProcessor(literal); int reserved = processor.getPrefixedDigitArea(); if (reserved > 0) { FormatStep last = ( this.steps.isEmpty() ? null : this.steps.get(this.steps.size() - 1) ); if ((last != null) && last.isDecimal() && !last.isNewOrBlockStarted()) { throw new IllegalStateException( "Numerical literal can't be inserted after an element with decimal digits."); } } if ((reserved == 0) || (this.reservedIndex == -1)) { this.addProcessor(processor); } else { int ri = this.reservedIndex; FormatStep numStep = this.steps.get(ri); this.addProcessor(processor); if (numStep.getSection() == this.steps.get(this.steps.size() - 1).getSection()) { this.reservedIndex = ri; this.steps.set(ri, numStep.reserve(reserved)); } } return this; } /** * <p>Defines a literal element with a char which will be searched * in given format attribute. </p> * * <p>A localized decimal separator char as literal will be possible * if the argument is equal to {@link Attributes#DECIMAL_SEPARATOR}. * Note: If given format attribute does not exist at runtime then * the formatting will fail. </p> * * @param attribute attribute defining a literal char which must be a non-digit char * @return this instance for method chaining */ /*[deutsch] * <p>Definiert ein Literalelement mit einem Zeichen, das in einem * Formatattribut gesucht wird. </p> * * <p>Ein lokalisiertes Dezimaltrennzeichen als Literal ist auch * möglich, wenn als Argument {@link Attributes#DECIMAL_SEPARATOR} * angegeben wird. Hinweis: Existiert das Formatattribut nicht zur * Laufzeit, wird die Formatierung scheitern. </p> * * @param attribute attribute defining a literal char which must be a non-digit char * @return this instance for method chaining */ public Builder<T> addLiteral(AttributeKey<Character> attribute) { this.addProcessor(new LiteralProcessor(attribute)); return this; } /** * <p>Defines a sequence of optional white space. </p> * * <p>If printed a space char will be used. In parsing however, * any white space of arbitrary length will be ignored and * skipped. </p> * * @return this instance for method chaining */ /*[deutsch] * <p>Definiert optionale nicht-anzeigbare Zeichen. </p> * * <p>Beim Formatieren wird ein Leerzeichen ausgegeben, beim Parsen * eine beliebig lange Kette von nicht-anzeigbaren Zeichen ignoriert * und übersprungen. </p> * * @return this instance for method chaining */ public Builder<T> addIgnorableWhitespace() { this.addProcessor(IgnorableWhitespaceProcessor.SINGLETON); return this; } /** * <p>Skips all characters from input as unparseable until at least given count of characters is left. </p> * * <p>Note: This method is only relevant for parsing. During printing, this method does nothing. </p> * * @param keepRemainingChars minimum count of characters which should be reserved for following steps * @return this instance for method chaining * @throws IllegalArgumentException if {@code keepRemainingChars < 0} * @see #skipUnknown(IntPredicate, int) * @see Attributes#TRAILING_CHARACTERS * @since 3.18/4.14 */ /*[deutsch] * <p>Ignoriert alle Zeichen als nicht-interpretierbar, bis wenigstens die angegebene Anzahl von Zeichen * übrigbleibt. </p> * * <p>Hinweis: Diese Methode ist nur beim Parsen relevant, in der Textausgabe macht die Methode nichts. </p> * * @param keepRemainingChars minimum count of characters which should be reserved for following steps * @return this instance for method chaining * @throws IllegalArgumentException if {@code keepRemainingChars < 0} * @see #skipUnknown(IntPredicate, int) * @see Attributes#TRAILING_CHARACTERS * @since 3.18/4.14 */ public Builder<T> skipUnknown(int keepRemainingChars) { this.addProcessor(new SkipProcessor(keepRemainingChars)); return this; } /** * <p>Skips all characters accepted by given condition as unparseable. </p> * * <p>Every input character will be handled by given condition as code point. If the condition * always returns {@code true} then {@code maxIterations} effectively models a fixed width of * characters to be skipped. </p> * * <p>Note: This method is only relevant for parsing. During printing, this method does nothing. </p> * * @param unparseableCondition condition which marks accepted characters as unparseable * @param maxIterations maximum count of invocations on given condition * @return this instance for method chaining * @throws IllegalArgumentException if {@code maxIterations < 1} * @see #skipUnknown(int) * @since 4.14 * @doctags.spec The first condition argument must be immutable or stateless (for example a lambda expression). */ /*[deutsch] * <p>Ignoriert alle Zeichen als nicht-interpretierbar, die von der angegebenen Bedingung akzeptiert * werden. </p> * * <p>Jedes Eingabezeichen wird von der angegebenen Bedingung als Code-Punkt (int) behandelt. Wenn * die Bedingung immer {@code true} liefert, dann stellt {@code maxIterations} effektiv eine feste * Breite von zu entfernenden Zeichen dar. </p> * * <p>Hinweis: Diese Methode ist nur beim Parsen relevant, in der Textausgabe macht die Methode nichts. </p> * * @param unparseableCondition condition which marks accepted characters as unparseable * @param maxIterations maximum count of invocations on given condition * @return this instance for method chaining * @throws IllegalArgumentException if {@code maxIterations < 1} * @see #skipUnknown(int) * @since 4.14 * @doctags.spec The first condition argument must be immutable or stateless (for example a lambda expression). */ public Builder<T> skipUnknown( IntPredicate unparseableCondition, int maxIterations ) { this.addProcessor(new SkipProcessor(unparseableCondition, maxIterations)); return this; } /** * <p>Processes given format pattern of given pattern type to a * sequence of format elements. </p> * * <p>The letters a-z and A-Z are treated as format symbols. The square brackets * "[" and "]" define an {@link #startOptionalSection() optional section} * which can be nested, too. The char "|" starts a new {@link #or() or-block}. And * the chars "#", "{" and "}" are reserved for the future. All * other chars will be interpreted as literals. If a reserved char shall be treated as literal * then it must be escaped by the apostroph "'". The apostroph itself can be * treated as literal by double apostroph. </p> * * <p>For exact interpretation and description of format symbols * see the implementations of interface {@code ChronoPattern}. </p> * * @param formatPattern pattern of symbols to be used in formatting * @param patternType type of pattern how to interprete symbols * @return this instance for method chaining * @throws IllegalArgumentException if resolving of pattern fails * @see PatternType */ /*[deutsch] * <p>Verarbeitet ein beliebiges Formatmuster des angegebenen Typs. </p> * * <p>Als Formatsymbole werden die Buchstaben a-z und A-Z erkannt. Die eckigen Klammern * "[" und "]" leiten eine {@link #startOptionalSection() optionale Sektion} * ein, die auch verschachtelt werden darf. Das Zeichen "|" startet einen neuen * {@link #or() oder-Block}. Die Zeichen "#", "{" und "}" sind * für die Zukunft reserviert. Alle anderen Zeichen werden als Literale interpretiert. * Falls ein reserviertes Zeichen auch als Literal gelten soll, muß es mittels eines * Apostrophs "'" gekennzeichnet werden (ESCAPE). Das Apostroph selbst wird durch * Verdoppelung als Literal interpretiert. </p> * * <p>Zur genauen Interpretation der Formatsymbole sei auf die * Implementierungen des Interface {@code ChronoPattern} verwiesen. </p> * * @param formatPattern pattern of symbols to be used in formatting * @param patternType type of pattern how to interprete symbols * @return this instance for method chaining * @throws IllegalArgumentException if resolving of pattern fails * @see PatternType */ public Builder<T> addPattern( String formatPattern, PatternType patternType ) { if (patternType == null) { throw new NullPointerException("Missing pattern type."); } Map<ChronoElement<?>, ChronoElement<?>> replacement = Collections.emptyMap(); int n = formatPattern.length(); Locale loc = this.locale; StringBuilder literal = new StringBuilder(); if (!this.stack.isEmpty()) { loc = this.stack.getLast().getLocale(); } for (int i = 0; i < n; i++) { char c = formatPattern.charAt(i); if (isSymbol(c)) { this.addLiteralChars(literal); int start = i++; while ((i < n) && formatPattern.charAt(i) == c) { i++; } Map<ChronoElement<?>, ChronoElement<?>> map = patternType.registerSymbol(this, loc, c, i - start); if (!map.isEmpty()) { if (replacement.isEmpty()) { replacement = map; } else { Map<ChronoElement<?>, ChronoElement<?>> tmp = new HashMap<>(replacement); tmp.putAll(map); replacement = tmp; } } i--; // Schleifenzähler nicht doppelt inkrementieren } else if (c == '\'') { this.addLiteralChars(literal); int start = i++; while (i < n) { if (formatPattern.charAt(i) == '\'') { if ( (i + 1 < n) && (formatPattern.charAt(i + 1) == '\'') ) { i++; } else { break; } } i++; } if (i >= n) { throw new IllegalArgumentException( "String literal in pattern not closed: " + formatPattern); } if (start + 1 == i) { this.addLiteral('\''); } else { String s = formatPattern.substring(start + 1, i); this.addLiteral(s.replace("''", "'")); } } else if (c == '[') { this.addLiteralChars(literal); this.startOptionalSection(); } else if (c == ']') { this.addLiteralChars(literal); this.endSection(); } else if (c == '|') { try { this.addLiteralChars(literal); this.or(); } catch (IllegalStateException ise) { throw new IllegalArgumentException(ise); } } else if ((c == '#') || (c == '{') || (c == '}')) { throw new IllegalArgumentException( "Pattern contains reserved character: '" + c + "'"); } else { literal.append(c); } } this.addLiteralChars(literal); if (!replacement.isEmpty()) { int len = this.steps.size(); for (int i = 0; i < len; i++) { FormatStep step = this.steps.get(i); ChronoElement<?> element = step.getProcessor().getElement(); if (replacement.containsKey(element)) { ChronoElement<?> newElement = replacement.get(element); this.steps.set(i, step.updateElement(newElement)); } } } return this; } /** * <p>Defines a text format for given chronological element. </p> * * @param element chronological text element * @return this instance for method chaining * @throws IllegalArgumentException if given element is not * supported by chronology or its preparser * @see Chronology#isSupported(ChronoElement) */ /*[deutsch] * <p>Definiert ein Textformat für das angegebene Element. </p> * * @param element chronological text element * @return this instance for method chaining * @throws IllegalArgumentException if given element is not * supported by chronology or its preparser * @see Chronology#isSupported(ChronoElement) */ public Builder<T> addText(TextElement<?> element) { this.checkElement(element); this.addProcessor(TextProcessor.create(element)); return this; } /** * <p>Defines a text format for given chronological element. </p> * * @param <V> generic type of element values * @param element chronological element * @return this instance for method chaining * @throws IllegalArgumentException if given element is not * supported by chronology * @see Chronology#isSupported(ChronoElement) */ /*[deutsch] * <p>Definiert ein Textformat für das angegebene Element. </p> * * @param <V> generic type of element values * @param element chronological element * @return this instance for method chaining * @throws IllegalArgumentException if given element is not * supported by chronology * @see Chronology#isSupported(ChronoElement) */ public <V extends Enum<V>> Builder<T> addText(ChronoElement<V> element) { this.checkElement(element); if (element instanceof TextElement) { TextElement<?> te = TextElement.class.cast(element); this.addProcessor(TextProcessor.create(te)); } else { // String-Ressource ist enum.toString() Map<V, String> empty = Collections.emptyMap(); this.addProcessor(new LookupProcessor<>(element, empty)); } return this; } /** * <p>Defines a text format for given chronological element with * user-defined string resources. </p> * * @param <V> generic type of element values * @param element chronological element * @param lookup text resources for lookup * @return this instance for method chaining * @throws IllegalArgumentException if given element is not * supported by chronology or its preparser * @see Chronology#isSupported(ChronoElement) */ /*[deutsch] * <p>Definiert ein Textformat für das angegebene Element mit * benutzerdefinierten String-Ressourcen. </p> * * @param <V> generic type of element values * @param element chronological element * @param lookup text resources for lookup * @return this instance for method chaining * @throws IllegalArgumentException if given element is not * supported by chronology or its preparser * @see Chronology#isSupported(ChronoElement) */ public <V extends Enum<V>> Builder<T> addText( ChronoElement<V> element, Map<V, String> lookup ) { this.checkElement(element); this.addProcessor(new LookupProcessor<>(element, lookup)); return this; } /** * <p>Defines a text format for a fixed day period (am/pm/midnight/noon). </p> * * @return this instance for method chaining * @throws IllegalStateException if the underlying chronology does not support day periods * @see net.time4j.DayPeriod#fixed() * @since 3.13/4.10 */ /*[deutsch] * <p>Definiert ein Textformat für einen festen Tagesabschnitt (am/pm/midnight/noon). </p> * * @return this instance for method chaining * @throws IllegalStateException if the underlying chronology does not support day periods * @see net.time4j.DayPeriod#fixed() * @since 3.13/4.10 */ public Builder<T> addDayPeriodFixed() { TextElement<?> te = this.findDayPeriodElement(true, null); return this.addText(te); } /** * <p>Defines a text format for a flexible day period (morning/afternoon etc). </p> * * @return this instance for method chaining * @throws IllegalStateException if the underlying chronology does not support day periods * @see net.time4j.DayPeriod#approximate() * @since 3.13/4.10 */ /*[deutsch] * <p>Definiert ein Textformat für einen flexiblen Tagesabschnitt (morgens/nachmittags usw.). </p> * * @return this instance for method chaining * @throws IllegalStateException if the underlying chronology does not support day periods * @see net.time4j.DayPeriod#approximate() * @since 3.13/4.10 */ public Builder<T> addDayPeriodApproximate() { TextElement<?> te = this.findDayPeriodElement(false, null); return this.addText(te); } /** * <p>Defines a text format for a custom day period. </p> * * @param timeToLabels mapping from start times to custom dayperiod labels * @return this instance for method chaining * @throws IllegalStateException if already called once or if the underlying chronology * does not support day periods * @throws IllegalArgumentException if given map is empty or contains empty values * @see DayPeriod#of(Map) * @since 3.13/4.10 */ /*[deutsch] * <p>Definiert ein Textformat für einen benutzerdefinierten Tagesabschnitt. </p> * * @param timeToLabels mapping from start times to custom dayperiod labels * @return this instance for method chaining * @throws IllegalStateException if already called once or if the underlying chronology * does not support day periods * @throws IllegalArgumentException if given map is empty or contains empty values * @see DayPeriod#of(Map) * @since 3.13/4.10 */ public Builder<T> addDayPeriod(Map<PlainTime, String> timeToLabels) { if (this.dayPeriod != null) { throw new IllegalStateException("Cannot add custom day period more than once."); } DayPeriod dp = DayPeriod.of(timeToLabels); TextElement<?> te = this.findDayPeriodElement(false, dp); this.dayPeriod = dp; this.addProcessor(TextProcessor.createProtected(te)); return this; } /** * <p>Defines a customized format element for given chronological * element. </p> * * <p>Equivalent to {@code addCustomized(element, formatter, formatter)}. </p> * * @param <V> generic type of element values * @param element chronological element * @param formatter customized formatter object as delegate * @return this instance for method chaining * @throws IllegalArgumentException if given element is not * supported by chronology or its preparser * @see Chronology#isSupported(ChronoElement) * @see #addCustomized(ChronoElement, ChronoPrinter, ChronoParser) */ /*[deutsch] * <p>Definiert ein benutzerdefiniertes Format für das angegebene * chronologische Element. </p> * * <p>Äquivalent zu {@code addCustomized(element, formatter, formatter)}. </p> * * @param <V> generic type of element values * @param element chronological element * @param formatter customized formatter object as delegate * @return this instance for method chaining * @throws IllegalArgumentException if given element is not * supported by chronology or its preparser * @see Chronology#isSupported(ChronoElement) * @see #addCustomized(ChronoElement, ChronoPrinter, ChronoParser) */ public <V extends ChronoEntity<V>> Builder<T> addCustomized( ChronoElement<V> element, final ChronoFormatter<V> formatter ) { return this.addCustomized(element, formatter, formatter); } /** * <p>Defines a customized format element for given chronological * element. </p> * * <p>If the printer or the parser are of type {@code ChronoFormatter} * then the outer format attributes and default values will be overtaken * by the embedded printer or parser. </p> * * @param <V> generic type of element values * @param element chronological element * @param printer customized printer * @param parser customized parser * @return this instance for method chaining * @throws IllegalArgumentException if given element is not * supported by chronology or its preparser * @see Chronology#isSupported(ChronoElement) */ /*[deutsch] * <p>Definiert ein benutzerdefiniertes Format für das angegebene * chronologische Element. </p> * * <p>Wenn der angegebene {@code ChronoPrinter} oder {@code ChronoParser vom Typ * {@code ChronoFormatter} sind, dann werden die äßeren Formatattribute * und Standardwerte vom äßeren Formatierer zum eingebetteten Formatierer * transferiert. </p> * * @param <V> generic type of element values * @param element chronological element * @param printer customized printer * @param parser customized parser * @return this instance for method chaining * @throws IllegalArgumentException if given element is not * supported by chronology or its preparser * @see Chronology#isSupported(ChronoElement) */ public <V> Builder<T> addCustomized( ChronoElement<V> element, ChronoPrinter<V> printer, ChronoParser<V> parser ) { this.checkElement(element); this.addProcessor(new CustomizedProcessor<>(element, printer, parser)); return this; } /** * <p>Defines a special format element for a two-digit-year. </p> * * <p>It is possible to specify a pivot year by setting the * attribute {@code Attributes.PIVOT_YEAR} to a meaningful year. * If this attribute is missing then Time4J will set the year * twenty years after now as pivot year by default. </p> * * <p>If this format element is directly preceded by other numerical * elements with variable width then the fixed width of 2 will be * preserved such that the preceding elements will not consume too * many digits (<i>adjacent digit parsing</i>). Otherwise this * format element can also parse more than two digits if there is * no strict mode with the consequence that the parsed year will be * interpreted as absolute full year. </p> * * @param element year element (name must start with the * prefix "YEAR") * @return this instance for method chaining * @throws IllegalArgumentException if given element is not * supported by chronology * @see Attributes#PIVOT_YEAR */ /*[deutsch] * <p>Definiert eine zweistellige Jahresangabe. </p> * * <p>Die Angabe eines Kippjahres ist mit Hilfe des Attributs * {@code Attributes.PIVOT_YEAR} möglich und ist beim * Interpretieren zweistelliger Jahresangaben von Bedeutung. * Fehlt das Attribut, wird Time4J standardmäß 20 Jahre * in der Zukunft das Kippjahr setzen. </p> * * <p>Folgt diese Methode direkt nach anderen numerischen Elementen * mit variabler Breite, wird die hier definierte feste Breite beim * Parsen vorreserviert, so daß vorangehende numerische Elemente * nicht zuviele Ziffern interpretieren (<i>adjacent digit parsing</i>). * Andernfalls können auch mehr als zwei Ziffern interpretiert * werden, sofern kein strikter Modus vorliegt mit der Folge, daß * eine solche Jahreszahl als absolutes volles Jahr interpretiert * wird. </p> * * @param element year element (name must start with the * prefix "YEAR") * @return this instance for method chaining * @throws IllegalArgumentException if given element is not * supported by chronology * @see Attributes#PIVOT_YEAR */ public Builder<T> addTwoDigitYear(ChronoElement<Integer> element) { return this.addTwoDigitYear(element, false); } /** * <p>Adds a timezone identifier. </p> * * <p>Parsing of a timezone ID is case-sensitive. All timezone IDs * which will be provided by {@link Timezone#getAvailableIDs()} * will be supported - with the exception of old IDs like * "Asia/Riyadh87" or "CST6CDT" which contain * some digits. Offset-IDs like the canonical form of * {@code ZonalOffset} or "GMT" are supported, too. * An exceptional case are again deprecated IDs like * "Etc/GMT+12". </p> * * @return this instance for method chaining * @throws IllegalStateException if the underlying chronology does not correspond * to the type {@link net.time4j.base.UnixTime} */ /*[deutsch] * <p>Fügt eine Zeitzonen-ID hinzu. </p> * * <p>Die Groß- und Kleinschreibung der Zeitzonen-ID ist * relevant. Unterstützt werden alle IDs, die von * {@link Timezone#getAvailableIDs()} geliefert werden - mit * Ausnahme von alten IDs wie "Asia/Riyadh87" oder * "CST6CDT", die Ziffern enthalten. Offset-IDs wie * die kanonische Form von {@code ZonalOffset} oder "GMT" * werden ebenfalls unterstützt. Eine Ausnahme sind hier * veraltete IDs wie "Etc/GMT+12". </p> * * @return this instance for method chaining * @throws IllegalStateException if the underlying chronology does not correspond * to the type {@link net.time4j.base.UnixTime} */ public Builder<T> addTimezoneID() { if (hasUnixChronology(this.chronology)) { this.addProcessor(TimezoneIDProcessor.INSTANCE); return this; } else { throw new IllegalStateException( "Only unix timestamps can have a timezone id."); } } /** * <p>Adds a short localized timezone name (an abbreviation in specific non-location format). </p> * * <p>Dependent on the current locale, the preferred timezone IDs * in a country will be determined first. The parsing of * timezone names is case-sensitive. </p> * * <p>Note that Time4J will try to find a unique mapping from names to * IDs for US in smart parsing mode. However, this is only an imperfect * approximation to current practice. A counter example is Phoenix * which does not observe daylight savings although it has the same * name "MST" as Denver. </p> * * @return this instance for method chaining * @throws IllegalStateException if the underlying chronology does not correspond * to the type {@link net.time4j.base.UnixTime} * @see #addShortTimezoneName(Set) */ /*[deutsch] * <p>Fügt die Abkürzung eines Zeitzonennamens hinzu. </p> * * <p>Mit Hilfe der aktuellen Ländereinstellung werden zuerst * die bevorzugten Zeitzonen-IDs bestimmt. Die Groß- und * Kleinschreibung der Zeitzonennamen wird beachtet. </p> * * <p>Hinweis: Time4J versucht das Beste, um eine eindeutige Abbildung * von Namen auf IDs für die USA im smart-Modus zu finden. Aber * das ist nur eine nicht perfekte Annäherung an die aktuelle * Praxis. Ein Gegenbeispiel ist Phoenix, das keine Sommerzeit kennt, * obwohl es den gleichen Namen "MST" wie Denver hat. </p> * * @return this instance for method chaining * @throws IllegalStateException if the underlying chronology does not correspond * to the type {@link net.time4j.base.UnixTime} * @see #addShortTimezoneName(Set) */ public Builder<T> addShortTimezoneName() { this.checkMomentChrono(); this.addProcessor(new TimezoneNameProcessor(true)); return this; } /** * <p>Adds a long localized timezone name (in specific non-location format). </p> * * <p>Dependent on the current locale, the preferred timezone IDs * in a country will be determined first. The parsing of * timezone names is case-sensitive. </p> * * <p>Note that Time4J will try to find a unique mapping from names to * IDs for US in smart parsing mode. However, this is only an imperfect * approximation to current practice. A counter example is Phoenix * which does not observe daylight savings although it has the same * name "Mountain Standard Time" as Denver. </p> * * @return this instance for method chaining * @throws IllegalStateException if the underlying chronology does not correspond * to the type {@link net.time4j.base.UnixTime} * @see #addLongTimezoneName(Set) */ /*[deutsch] * <p>Fügt einen langen Zeitzonennamen hinzu. </p> * * <p>Mit Hilfe der aktuellen Ländereinstellung werden zuerst * die bevorzugten Zeitzonen-IDs bestimmt. Die Groß- und * Kleinschreibung der Zeitzonennamen wird beachtet. </p> * * <p>Hinweis: Time4J versucht das Beste, um eine eindeutige Abbildung * von Namen auf IDs für die USA im smart-Modus zu finden. Aber * das ist nur eine nicht perfekte Annäherung an die aktuelle * Praxis. Ein Gegenbeispiel ist Phoenix, das keine Sommerzeit kennt, * obwohl es den gleichen Namen "Mountain Standard Time" * wie Denver hat. </p> * * @return this instance for method chaining * @throws IllegalStateException if the underlying chronology does not correspond * to the type {@link net.time4j.base.UnixTime} * @see #addLongTimezoneName(Set) */ public Builder<T> addLongTimezoneName() { this.checkMomentChrono(); this.addProcessor(new TimezoneNameProcessor(false)); return this; } /** * <p>Adds a short localized timezone name (an abbreviation in specific non-location format). </p> * * <p>Parsing of timezone names is case-sensitive. </p> * * @param preferredZones preferred timezone ids for resolving * duplicates * @return this instance for method chaining * @throws IllegalStateException if the underlying chronology does not correspond * to the type {@link net.time4j.base.UnixTime} * @see #addLongTimezoneName(Set) */ /*[deutsch] * <p>Fügt die Abkürzung eines Zeitzonennamens hinzu. </p> * * <p>Die Groß- und Kleinschreibung der Zeitzonennamen wird * beachtet. </p> * * @param preferredZones preferred timezone ids for resolving * duplicates * @return this instance for method chaining * @throws IllegalStateException if the underlying chronology does not correspond * to the type {@link net.time4j.base.UnixTime} * @see #addLongTimezoneName(Set) */ public Builder<T> addShortTimezoneName(Set<TZID> preferredZones) { this.checkMomentChrono(); this.addProcessor(new TimezoneNameProcessor(true, preferredZones)); return this; } /** * <p>Adds a long localized timezone name (in specific non-location format). </p> * * <p>Parsing of timezone names is case-sensitive. </p> * * @param preferredZones preferred timezone ids for resolving * duplicates * @return this instance for method chaining * @throws IllegalStateException if the underlying chronology does not correspond * to the type {@link net.time4j.base.UnixTime} * @see #addShortTimezoneName(Set) */ /*[deutsch] * <p>Fügt einen langen Zeitzonennamen hinzu. </p> * * <p>Die Groß- und Kleinschreibung der Zeitzonennamen wird * beachtet. </p> * * @param preferredZones preferred timezone ids for resolving * duplicates * @return this instance for method chaining * @throws IllegalStateException if the underlying chronology does not correspond * to the type {@link net.time4j.base.UnixTime} * @see #addShortTimezoneName(Set) */ public Builder<T> addLongTimezoneName(Set<TZID> preferredZones) { this.checkMomentChrono(); this.addProcessor(new TimezoneNameProcessor(false, preferredZones)); return this; } /** * <p>Adds a timezone offset in typical ISO-8601-notation. </p> * * <p>The offset format is "±HH:mm" or in case of * zero offset simply "Z". Equivalent to the expression * {@code addTimezoneOffset(DisplayMode.MEDIUM, true, * Collections.singletonList("Z"))}. </p> * * @return this instance for method chaining */ /*[deutsch] * <p>Fügt einen Zeitzonen-Offset in typischer ISO-8601-Notation * hinzu. </p> * * <p>Das Offset-Format ist "±HH:mm" oder im Fall * des Null-Offsets einfach "Z". Entspricht dem Ausdruck * {@code addTimezoneOffset(DisplayMode.MEDIUM, true, * Collections.singletonList("Z"))}. </p> * * @return this instance for method chaining */ public Builder<T> addTimezoneOffset() { return this.addTimezoneOffset( DisplayMode.MEDIUM, true, Collections.singletonList("Z")); } /** * <p>Adds a timezone offset in canonical notation. </p> * * <p>This format element is also applicable on chronological * entities without timezone reference like {@code PlainTime} * provided that a timezone offset is set as format attribute. * Dependent on given arguments following formats are * defined: </p> * * <div style="margin-top:5px;"> * <table border="1"> * <caption>Legend</caption> * <tr> * <th> </th> * <th>SHORT</th> * <th>MEDIUM</th> * <th>LONG</th> * <th>FULL</th> * </tr> * <tr> * <td>basic</td> * <td>±HH[mm]</td> * <td>±HHmm</td> * <td>±HHmm[ss[.{fraction}]]</td> * <td>±HHmmss[.{fraction}]</td> * </tr> * <tr> * <td>extended</td> * <td>±HH[:mm]</td> * <td>±HH:mm</td> * <td>±HH:mm[:ss[.{fraction}]]</td> * <td>±HH:mm:ss[.{fraction}]</td> * </tr> * </table> * </div> * * <p>Notes: All components given in square brackets are optional. * During printing, they will only appear if they are different from {@code 0}. * During parsing, they can be left out of the text to be parsed. A fractional * second part with 9 digits is always optional (unless a dot exists) * and is only possible in case of a longitudinal offset. The modes * SHORT and MEDIUM correspond to ISO-8601 where an offset should * only have hours and minutes. The hour part might consist of one digit * only if the parsing mode is not strict. </p> * * <p>The third argument determines what kind of text should be * interpreted as zero offset. The formatted output always uses * the first list entry while parsing expects any list entries. </p> * * @param precision display mode of offset format * @param extended extended or basic ISO-8601-mode * @param zeroOffsets list of replacement texts if offset is zero * @return this instance for method chaining * @throws IllegalArgumentException if any replacement text consists * of white-space only or if given replacement list is empty * @see ChronoEntity#getTimezone() */ /*[deutsch] * <p>Fügt einen Zeitzonen-Offset in kanonischer Notation * hinzu. </p> * * <p>Anwendbar ist dieses Formatierungselement auch auf lokale * Typen ohne Zeitzonenbezug wie z.B. {@code PlainTime}, setzt dann * aber voraus, daß ein Zeitzonen-Offset als Attribut des * {@code ChronoFormatter} mitgegeben wird. In Abhängigkeit * von den Argumenten sind folgende Formate definiert: </p> * * <div style="margin-top:5px;"> * <table border="1"> * <caption>Legende</caption> * <tr> * <th> </th> * <th>SHORT</th> * <th>MEDIUM</th> * <th>LONG</th> * <th>FULL</th> * </tr> * <tr> * <td>basic</td> * <td>±HH[mm]</td> * <td>±HHmm</td> * <td>±HHmm[ss[.{fraction}]]</td> * <td>±HHmmss[.{fraction}]</td> * </tr> * <tr> * <td>extended</td> * <td>±HH[:mm]</td> * <td>±HH:mm</td> * <td>±HH:mm[:ss[.{fraction}]]</td> * <td>±HH:mm:ss[.{fraction}]</td> * </tr> * </table> * </div> * * <p>Hinweise: Die in eckigen Klammern angegebenen Komponenten * sind optional, erscheinen also beim Formatieren nur, wenn sie von {@code 0} * verschieden sind. Der Interpretierer duldet auch Fehler in diesen Teilen. * Ein fraktionaler stets 9-stelliger Sekundenteil ist immer optional (wenn nicht * anfangs ein Punkt existiert) und nur dann möglich, wenn ein longitudinaler * Offset verwendet wird. Die Genauigkeitsangaben SHORT und MEDIUM entsprechen der * ISO-8601-Notation, in der nur Stunden und Minuten formatiert werden. Der Stundenteil * mag aus nur einer Ziffer bestehen, wenn der Interpretierer nicht strikt ist. </p> * * <p>Das dritte Argument legt fest, was als Null-Offset interpretiert * werden soll. Die formatierte Ausgabe benutzt immer den ersten * Listeneintrag. </p> * * @param precision display mode of offset format * @param extended extended or basic ISO-8601-mode * @param zeroOffsets list of replacement texts if offset is zero * @return this instance for method chaining * @throws IllegalArgumentException if any replacement text consists * of white-space only or if given replacement list is empty * @see ChronoEntity#getTimezone() */ public Builder<T> addTimezoneOffset( DisplayMode precision, boolean extended, List<String> zeroOffsets ) { this.addProcessor( new TimezoneOffsetProcessor(precision, extended, zeroOffsets)); return this; } /** * <p>Adds a timezone offset in short localized notation. </p> * * <p>This format element is also applicable on chronological * entities without timezone reference like {@code PlainTime} * provided that a timezone offset is set as format attribute. * The format "GMT±H[:mm[:ss]]" will be used. </p> * * <p>Notes: The minute component given in square brackets is * optional in short format and appears only if it is different * from {@code 0}. The GMT-prefix can also be like "UTC" * or "UT" when parsing. A localized GMT-notation is * possible provided that the resource files * "tzname.properties" have an entry with the key * "offset-pattern". If the format attribute * {@code Attributes.NO_GMT_PREFIX} is set to {@code true} * then the GMT-prefix will be suppressed. The format attribute * {@code Attributes.ZERO_DIGIT} (usually set indirect via the * locale) controls which localized set of digits will be used. * The sign is possibly localized, too. </p> * * @return this instance for method chaining * @see ChronoEntity#getTimezone() * @see #addLongLocalizedOffset() * @see Attributes#NO_GMT_PREFIX */ /*[deutsch] * <p>Fügt einen Zeitzonen-Offset in kurzer lokalisierter Notation * hinzu. </p> * * <p>Anwendbar ist dieses Formatierungselement auch auf lokale * Typen ohne Zeitzonenbezug wie z.B. {@code PlainTime}, setzt dann * aber voraus, daß ein Zeitzonen-Offset als Attribut des * {@code ChronoFormatter} mitgegeben wird. Als Format wird * "GMT±H[:mm[:ss]]" verwendet. </p> * * <p>Hinweise: Die in eckigen Klammern angegebene Minutenkomponente * ist optional, erscheint also nur, wenn sie von {@code 0} verschieden * ist. Das GMT-Präfix darf beim Parsen auch als "UTC" * oder "UT" vorliegen. Außerdem ist eine lokalisierte * GMT-Notation möglich, indem in den Ressourcendateien * "tzname.properties" ein Eintrag mit dem Schlüssel * "offset-pattern" vorhanden ist. Wenn das Formatattribut * {@code Attributes.NO_GMT_PREFIX} auf {@code true} gesetzt wird, * dann wird das GMT-Präfix unterdrückt. Das Formatattribut * {@code Attributes.ZERO_DIGIT} (normalerweise nur implizit über die * Sprache gesetzt) steuert, welcher lokalisierter Satz von Ziffernzeichen * verwendet wird. Das Vorzeichen ist eventuell auch lokalisiert. </p> * * @return this instance for method chaining * @see ChronoEntity#getTimezone() * @see #addLongLocalizedOffset() * @see Attributes#NO_GMT_PREFIX */ public Builder<T> addShortLocalizedOffset() { this.addProcessor(new LocalizedGMTProcessor(true)); return this; } /** * <p>Adds a timezone offset in long localized notation. </p> * * <p>This format element is also applicable on chronological * entities without timezone reference like {@code PlainTime} * provided that a timezone offset is set as format attribute. * The format "GMT±HH:mm[:ss]" will be used. </p> * * <p>Notes: The GMT-prefix can also be like "UTC" * or "UT" when parsing. A localized GMT-notation is * possible provided that the resource files * "tzname.properties" have an entry with the key * "offset-pattern". If the format attribute * {@code Attributes.NO_GMT_PREFIX} is set to {@code true} * then the GMT-prefix will be suppressed. The format attribute * {@code Attributes.ZERO_DIGIT} (usually set indirect via the * locale) controls which localized set of digits will be used. * The sign is possibly localized, too. </p> * * @return this instance for method chaining * @see ChronoEntity#getTimezone() * @see #addShortLocalizedOffset() * @see Attributes#NO_GMT_PREFIX */ /*[deutsch] * <p>Fügt einen Zeitzonen-Offset in langer lokalisierter Notation * hinzu. </p> * * <p>Anwendbar ist dieses Formatierungselement auch auf lokale * Typen ohne Zeitzonenbezug wie z.B. {@code PlainTime}, setzt dann * aber voraus, daß ein Zeitzonen-Offset als Attribut des * {@code ChronoFormatter} mitgegeben wird. Als Format wird * "GMT±HH:mm[:ss]" verwendet. </p> * * <p>Hinweise: Das GMT-Präfix darf beim Parsen auch als * "UTC" oder "UT" vorliegen. Außerdem * ist eine lokalisierte GMT-Notation möglich, indem in den * Ressourcendateien "tzname.properties" ein Eintrag mit * dem Schlüssel "offset-pattern" vorhanden ist. Wenn * das Formatattribut {@code Attributes.NO_GMT_PREFIX} auf {@code true} * gesetzt wird, dann wird das GMT-Präfix unterdrückt. Das * Formatattribut {@code Attributes.ZERO_DIGIT} (normalerweise nur * implizit über die Sprache gesetzt) steuert, welcher lokalisierter * Satz von Ziffernzeichen verwendet wird. Das Vorzeichen ist eventuell * auch lokalisiert. </p> * * @return this instance for method chaining * @see ChronoEntity#getTimezone() * @see #addShortLocalizedOffset() * @see Attributes#NO_GMT_PREFIX */ public Builder<T> addLongLocalizedOffset() { this.addProcessor(new LocalizedGMTProcessor(false)); return this; } /** * <p>Defines for the next format element of the same section so * many pad chars until the element width has reached the width * specified. </p> * * <p>Note: This method will be ignored if it is directly followed * by a new section or if the current section is closed * or if there are no more format elements. </p> * * @param width fixed width of following format step * @return this instance for method chaining * @throws IllegalArgumentException if given width is negative * @see Attributes#PAD_CHAR * @see #padPrevious(int) */ /*[deutsch] * <p>Definiert zum nächsten Element der gleichen Sektion soviele * Füllzeichen, bis die Elementbreite die angegebene Breite * erreicht hat. </p> * * <p>Zu beachten: Diese Methode wird ignoriert, wenn unmittelbar * danach eine neue Sektion gestartet, die aktuelle Sektion beendet * oder gar kein Element mehr hinzugefügt wird. </p> * * @param width fixed width of following format step * @return this instance for method chaining * @throws IllegalArgumentException if given width is negative * @see Attributes#PAD_CHAR * @see #padPrevious(int) */ public Builder<T> padNext(int width) { if (width < 0) { throw new IllegalArgumentException("Negative pad width: " + width); } else if (width > 0) { this.leftPadWidth = width; } return this; } /** * <p>Defines for the previous format element of the same * section so many pad chars until the element width has * reached the width specified. </p> * * @param width fixed width of previous format step * @return this instance for method chaining * @throws IllegalArgumentException if given width is negative * @see Attributes#PAD_CHAR * @see #padNext(int) */ /*[deutsch] * <p>Definiert zum vorherigen Element der gleichen Sektion soviele * Füllzeichen, bis die Elementbreite die angegebene Breite * erreicht hat. </p> * * @param width fixed width of previous format step * @return this instance for method chaining * @throws IllegalArgumentException if given width is negative * @see Attributes#PAD_CHAR * @see #padNext(int) */ public Builder<T> padPrevious(int width) { if (width < 0) { throw new IllegalArgumentException("Negative pad width: " + width); } else if ( !this.steps.isEmpty() && (width > 0) ) { int index = this.steps.size() - 1; FormatStep lastStep = this.steps.get(index); int currentSection = 0; if (!this.stack.isEmpty()) { currentSection = this.stack.getLast().getSection(); } if ((currentSection == lastStep.getSection()) && !lastStep.isNewOrBlockStarted()) { this.steps.set(index, lastStep.pad(0, width)); } } return this; } /** * <p>Starts a new optional section where errors in parsing will * not cause an exception but just be ignored. </p> * * <p>Note: Printing is not optional and will always be enabled. </p> * * @return this instance for method chaining */ /*[deutsch] * <p>Startet einen neuen optionalen Abschnitt, in dem Fehler beim * Interpretieren nicht zum Abbruch führen, sondern nur ignoriert * werden. </p> * * <p>Hinweis: Die Textausgabe ist nicht optional, sondern immer aktiv. </p> * * @return this instance for method chaining */ public Builder<T> startOptionalSection() { return this.startOptionalSection(null); } /** * <p>Starts a new optional section where errors in parsing will * not cause an exception but just be ignored. </p> * * <p>Printing will only happen if given condition is true. </p> * * @param printCondition optional condition for printing * @return this instance for method chaining */ /*[deutsch] * <p>Startet einen neuen optionalen Abschnitt, in dem Fehler beim * Interpretieren nicht zum Abbruch führen, sondern nur ignoriert * werden. </p> * * <p>Die Textausgabe erfolgt nur, wenn die angegebene Bedingung wahr ist. </p> * * @param printCondition optional condition for printing * @return this instance for method chaining */ public Builder<T> startOptionalSection(final ChronoCondition<ChronoDisplay> printCondition) { this.resetPadding(); Attributes.Builder ab = new Attributes.Builder(); AttributeSet previous = null; ChronoCondition<ChronoDisplay> cc = null; if (!this.stack.isEmpty()) { previous = this.stack.getLast(); ab.setAll(previous.getAttributes()); cc = previous.getCondition(); } int newLevel = getLevel(previous) + 1; int newSection = ++this.sectionID; if (printCondition != null) { final ChronoCondition<ChronoDisplay> old = cc; if (old == null) { cc = printCondition; } else { cc = context -> ( old.test(context) && printCondition.test(context)); } } AttributeSet as = new AttributeSet(ab.build(), this.locale, newLevel, newSection, cc); this.stack.addLast(as); return this; } /** * <p>Starts a new section with given sectional attribute. </p> * * <p>The new section takes over all attributes of current section * if available. Sectional attributes cannot be overridden by the * default attributes of {@code ChronoFormatter}. </p> * * @param key attribute key * @param value attribute value * @return this instance for method chaining */ /*[deutsch] * <p>Startet einen neuen Abschnitt mit dem angegebenen sektionalen * boolean-Attribut. </p> * * <p>Der neue Abschnitt übernimmt alle Attribute des aktuellen * Attributabschnitts, falls vorhanden. Sektionale Attribute können * durch Standard-Attribute des {@code ChronoFormatter} nicht * übersteuert werden. </p> * * @param key attribute key * @param value attribute value * @return this instance for method chaining */ public Builder<T> startSection( AttributeKey<Boolean> key, boolean value ) { checkAttribute(key); this.resetPadding(); AttributeSet as; Attributes.Builder ab; if (this.stack.isEmpty()) { ab = new Attributes.Builder(); as = new AttributeSet(ab.set(key, value).build(), this.locale); } else { AttributeSet old = this.stack.getLast(); ab = new Attributes.Builder(); ab.setAll(old.getAttributes()); ab.set(key, value); as = old.withAttributes(ab.build()); } this.stack.addLast(as); return this; } /** * <p>Starts a new section with given sectional attribute. </p> * * <p>The new section takes over all attributes of current section * if available. Sectional attributes cannot be overridden by the * default attributes of {@code ChronoFormatter}. </p> * * @param key attribute key * @param value attribute value * @return this instance for method chaining */ /*[deutsch] * <p>Startet einen neuen Abschnitt mit dem angegebenen sektionalen * int-Attribut. </p> * * <p>Der neue Abschnitt übernimmt alle Attribute des aktuellen * Attributabschnitts, falls vorhanden. Sektionale Attribute können * durch Standard-Attribute des {@code ChronoFormatter} nicht * übersteuert werden. </p> * * @param key attribute key * @param value attribute value * @return this instance for method chaining */ public Builder<T> startSection( AttributeKey<Integer> key, int value ) { checkAttribute(key); this.resetPadding(); AttributeSet as; Attributes.Builder ab; if (this.stack.isEmpty()) { ab = new Attributes.Builder(); as = new AttributeSet(ab.set(key, value).build(), this.locale); } else { AttributeSet old = this.stack.getLast(); ab = new Attributes.Builder(); ab.setAll(old.getAttributes()); ab.set(key, value); as = old.withAttributes(ab.build()); } this.stack.addLast(as); return this; } /** * <p>Starts a new section with given sectional attribute. </p> * * <p>The new section takes over all attributes of current section * if available. Sectional attributes cannot be overridden by the * default attributes of {@code ChronoFormatter}. </p> * * @param key attribute key * @param value attribute value * @return this instance for method chaining */ /*[deutsch] * <p>Startet einen neuen Abschnitt mit dem angegebenen sektionalen * char-Attribut. </p> * * <p>Der neue Abschnitt übernimmt alle Attribute des aktuellen * Attributabschnitts, falls vorhanden. Sektionale Attribute können * durch Standard-Attribute des {@code ChronoFormatter} nicht * übersteuert werden. </p> * * @param key attribute key * @param value attribute value * @return this instance for method chaining */ public Builder<T> startSection( AttributeKey<Character> key, char value ) { checkAttribute(key); this.resetPadding(); AttributeSet as; Attributes.Builder ab; if (this.stack.isEmpty()) { ab = new Attributes.Builder(); as = new AttributeSet(ab.set(key, value).build(), this.locale); } else { AttributeSet old = this.stack.getLast(); ab = new Attributes.Builder(); ab.setAll(old.getAttributes()); ab.set(key, value); as = old.withAttributes(ab.build()); } this.stack.addLast(as); return this; } /** * <p>Starts a new section with given sectional attribute. </p> * * <p>The new section takes over all attributes of current section * if available. Sectional attributes cannot be overridden by the * default attributes of {@code ChronoFormatter}. </p> * * @param <A> generic type of attribute (enum-based) * @param key attribute key * @param value attribute value * @return this instance for method chaining */ /*[deutsch] * <p>Startet einen neuen Abschnitt mit dem angegebenen sektionalen * enum-Attribut. </p> * * <p>Der neue Abschnitt übernimmt alle Attribute des aktuellen * Attributabschnitts, falls vorhanden. Sektionale Attribute können * durch Standard-Attribute des {@code ChronoFormatter} nicht * übersteuert werden. </p> * * @param <A> generic type of attribute (enum-based) * @param key attribute key * @param value attribute value * @return this instance for method chaining */ public <A extends Enum<A>> Builder<T> startSection( AttributeKey<A> key, A value ) { checkAttribute(key); this.resetPadding(); AttributeSet as; Attributes.Builder ab; if (this.stack.isEmpty()) { ab = new Attributes.Builder(); as = new AttributeSet(ab.set(key, value).build(), this.locale); } else { AttributeSet old = this.stack.getLast(); ab = new Attributes.Builder(); ab.setAll(old.getAttributes()); ab.set(key, value); as = old.withAttributes(ab.build()); } this.stack.addLast(as); return this; } /** * <p>Removes the last sectional attribute. </p> * * @return this instance for method chaining * @throws java.util.NoSuchElementException if there is no section * which was started with {@code startSection()} * @see #startSection(AttributeKey, boolean) * @see #startSection(AttributeKey, Enum) * @see #startSection(AttributeKey, int) * @see #startSection(AttributeKey, char) */ /*[deutsch] * <p>Entfernt das letzte sektionale Attribut. </p> * * @return this instance for method chaining * @throws java.util.NoSuchElementException if there is no section * which was started with {@code startSection()} * @see #startSection(AttributeKey, boolean) * @see #startSection(AttributeKey, Enum) * @see #startSection(AttributeKey, int) * @see #startSection(AttributeKey, char) */ public Builder<T> endSection() { this.stack.removeLast(); this.resetPadding(); return this; } /** * <p>Starts a new block inside the current section such that the following parts will only be * taken into account in case of failure according to or-logic. </p> * * <p>Example of usage (here with format pattern char "|"): </p> * * <pre> * ChronoFormatter<PlainDate> f = * ChronoFormatter.ofDatePattern("E, [dd.MM.|MM/dd/]uuuu", PatternType.CLDR, Locale.ENGLISH); * PlainDate expected = PlainDate.of(2015, 12, 31); * assertThat(f.parse("Thu, 31.12.2015"), is(expected)); * assertThat(f.parse("Thu, 12/31/2015"), is(expected)); * </pre> * * <p>Note: Or-blocks can also be used outside of an optional section. </p> * * <p><strong>General notes about usage:</strong> </p> * * <p>a) If two patterns are combined then the order must be from the most complete * pattern to the least complete one. Example: Use "MM/dd/yyyy HH:mm|MM/dd/yyyy" * and not "MM/dd/yyyy|MM/dd/yyyy HH:mm". This is especially important if the formatter * in question use default values because the single components will be processed before evaluating * any default values (which is a late step in parsing). </p> * * <p>b) If two patterns have the same degree of completeness then that component should * be noted first which is more likely to be expected in input. </p> * * @return this instance for method chaining * @throws IllegalStateException if called twice * or called after the end of an optional section * or if there is not yet any defined format step in current section * @since 3.14/4.11 */ /*[deutsch] * <p>Startet einen neuen Block innerhalb des aktuellen Abschnitts so, daß alle folgenden * Blöcke nur im Fehlerfall verarbeitet werden (entsprechend der oder-Logik). </p> * * <p>Anwendungsbeispiel (hier mit Hilfe des Formatmusterzeichens "|"): </p> * * <pre> * ChronoFormatter<PlainDate> f = * ChronoFormatter.ofDatePattern("E, [dd.MM.|MM/dd/]uuuu", PatternType.CLDR, Locale.ENGLISH); * PlainDate expected = PlainDate.of(2015, 12, 31); * assertThat(f.parse("Thu, 31.12.2015"), is(expected)); * assertThat(f.parse("Thu, 12/31/2015"), is(expected)); * </pre> * * <p>Hinweis: Oder-Blöcke können auch außerhalb optionaler Sektionen verwendet werden. </p> * * <p><strong>Allgemeine Bestimmungen zum Gebrauch:</strong> </p> * * <p>a) Wenn zwei Formatmuster miteinander kombiniert werden, dann muß die Reihenfolge so * gewählt werden, daß das Formatmuster vorangeht, das einen höheren Grad an * Vollständigkeit besitzt. Beispiel: Verwende "MM/dd/yyyy HH:mm|MM/dd/yyyy" * und nicht "MM/dd/yyyy|MM/dd/yyyy HH:mm". Das ist besonders wichtig, wenn der * fragliche {@code ChronoFormatter} Standardwerte verwendet, weil die einzelnen Formatelemente * vor der Auswertung irgendwelcher Standardwerte zuerst ausgewertet werden. </p> * * <p>b) Falls zwei Formatmuster den gleichen Grad an Vollständigkeit haben, dann sollte * das Formatmuster vorangehen, das in den zu erwartenden Eingabewerten wahrscheinlicher zutrifft. </p> * * @return this instance for method chaining * @throws IllegalStateException if called twice * or called after the end of an optional section * or if there is not yet any defined format step in current section * @since 3.14/4.11 */ public Builder<T> or() { int index = -1; FormatStep lastStep = null; int lastSection = -1; int currentSection = 0; if (!this.stack.isEmpty()) { currentSection = this.stack.getLast().getSection(); } if (!this.steps.isEmpty()) { index = this.steps.size() - 1; lastStep = this.steps.get(index); lastSection = lastStep.getSection(); } if (currentSection == lastSection) { this.steps.set(index, lastStep.startNewOrBlock()); this.resetPadding(); this.reservedIndex = -1; // reset adjacent digit parsing } else { throw new IllegalStateException("Cannot start or-block without any previous step in current section."); } return this; } /** * <p>Defines a default value if the parser has not parsed or found a value for given element. </p> * * @param <V> generic element value type * @param element chronological element * @param value replacement value, not {@code null} * @return this instance for method chaining * @throws IllegalArgumentException if given element is not supported by the underlying chronology * @since 3.22/4.18 */ /*[deutsch] * <p>Definiert einen Standardwert, wenn der Interpretierer keinen Wert zum angegebenen Element * gefunden hat. </p> * * @param <V> generic element value type * @param element chronological element * @param value replacement value, not {@code null} * @return this instance for method chaining * @throws IllegalArgumentException if given element is not supported by the underlying chronology * @since 3.22/4.18 */ public <V> Builder<T> setDefault( ChronoElement<V> element, V value ) { if (value == null) { throw new NullPointerException("Missing default value."); } this.checkElement(element); this.defaultMap.put(element, value); return this; } /** * <p>Defines a supplier for a default value if the parser has not parsed or found a value * for given element. </p> * * @param <V> generic element value type * @param element chronological element * @param supplier supplier for replacement value, not {@code null} * @return this instance for method chaining * @throws IllegalArgumentException if given element is not supported by the underlying chronology * @since 3.22/4.18 */ /*[deutsch] * <p>Definiert einen Standardwertlieferanten, wenn der Interpretierer keinen Wert zum angegebenen Element * gefunden hat. </p> * * @param <V> generic element value type * @param element chronological element * @param supplier supplier for replacement value, not {@code null} * @return this instance for method chaining * @throws IllegalArgumentException if given element is not supported by the underlying chronology * @since 3.22/4.18 */ public <V> Builder<T> setDefaultSupplier( ChronoElement<V> element, Supplier<V> supplier ) { if (supplier == null) { throw new NullPointerException("Missing supplier for default value."); } this.checkElement(element); this.defaultMap.put(element, supplier); return this; } /** * <p>Finishes the build and creates a new {@code ChronoFormatter}. </p> * * @return new {@code ChronoFormatter}-instance with standard global format attributes * @throws IllegalStateException if there is no format element at all or none after or-operator in same section */ /*[deutsch] * <p>Schließt den Build-Vorgang ab und erstellt ein neues Zeitformat. </p> * * @return new {@code ChronoFormatter}-instance with standard global format attributes * @throws IllegalStateException if there is no format element at all or none after or-operator in same section */ public ChronoFormatter<T> build() { return this.build(Attributes.empty()); } /** * <p>Finishes the build and creates a new {@code ChronoFormatter}. </p> * * @param attributes new set of global format attributes * @return new {@code ChronoFormatter}-instance * @throws IllegalStateException if there is no format element at all or none after or-operator in same section * @since 3.22/4.18 */ /*[deutsch] * <p>Schließt den Build-Vorgang ab und erstellt ein neues Zeitformat. </p> * * @param attributes new set of global format attributes * @return new {@code ChronoFormatter}-instance * @throws IllegalStateException if there is no format element at all or none after or-operator in same section * @since 3.22/4.18 */ public ChronoFormatter<T> build(Attributes attributes) { Map<Integer, FormatStep> m = null; for (int index = 0, len = this.steps.size(); index < len; index++) { FormatStep step = this.steps.get(index); if (step.isNewOrBlockStarted()) { int section = step.getSection(); boolean ok = false; for (int j = len - 1; j > index; j--) { if (this.steps.get(j).getSection() == section) { if (m == null) { m = new HashMap<>(); } m.put(Integer.valueOf(index), step.markLastOrBlock(j)); ok = true; break; } } if (!ok) { throw new IllegalStateException("Missing format processor after or-operator."); } } } if (m != null) { for (Integer key : m.keySet()) { this.steps.set(key.intValue(), m.get(key)); } } ChronoFormatter<T> formatter = new ChronoFormatter<>( this.chronology, this.override, this.locale, this.steps, this.defaultMap, attributes, this.deepestParser ); if (this.prolepticGregorian) { // see PatternType.THREETEN formatter = formatter.with(ChronoHistory.PROLEPTIC_GREGORIAN); } if (this.dayPeriod != null) { AttributeSet as = formatter.globalAttributes; as = as.withInternal(CUSTOM_DAY_PERIOD, this.dayPeriod); formatter = new ChronoFormatter<>(formatter, as); } return formatter; } // Spezialmethode für Jahreselemente, siehe auch issue #307 Builder<T> addYear( ChronoElement<Integer> element, int count, boolean protectedMode, boolean threeten ) { FormatStep last = (this.steps.isEmpty() ? null : this.steps.get(this.steps.size() - 1)); if ( (last == null) || last.isNewOrBlockStarted() || !last.isNumerical() || (count != 4) ) { SignPolicy signPolicy = ( (count < 4 || !threeten) ? SignPolicy.SHOW_WHEN_NEGATIVE : SignPolicy.SHOW_WHEN_BIG_NUMBER); return this.addNumber(element, false, count, 9, signPolicy, protectedMode); } // adjacent digit parsing return this.addNumber(element, true, 4, 4, SignPolicy.SHOW_NEVER, protectedMode); } // Spezialmethode, um das CLDR-Symbol u immer als proleptic-iso-year zu schützen Builder<T> addProlepticIsoYearWithTwoDigits() { return this.addTwoDigitYear(PlainDate.YEAR, true); } // Spezialmethode für PatternType.THREETEN void setProlepticGregorian() { this.prolepticGregorian = true; } private <V> Builder<T> addNumber( ChronoElement<V> element, boolean fixedWidth, int minDigits, int maxDigits, SignPolicy signPolicy ) { return this.addNumber(element, fixedWidth, minDigits, maxDigits, signPolicy, false); } private <V> Builder<T> addNumber( ChronoElement<V> element, boolean fixedWidth, int minDigits, int maxDigits, SignPolicy signPolicy, boolean protectedMode ) { this.checkElement(element); FormatStep last = this.checkAfterDecimalDigits(element); NumberProcessor<V> np = new NumberProcessor<>( element, fixedWidth, minDigits, maxDigits, signPolicy, protectedMode ); if (fixedWidth) { if (this.reservedIndex == -1) { this.addProcessor(np); } else { int ri = this.reservedIndex; FormatStep numStep = this.steps.get(ri); this.addProcessor(np); FormatStep lastStep = this.steps.get(this.steps.size() - 1); if (numStep.getSection() == lastStep.getSection()) { this.reservedIndex = ri; this.steps.set(ri, numStep.reserve(minDigits)); } } } else if ((last != null) && last.isNumerical() && !last.isNewOrBlockStarted()) { throw new IllegalStateException( "Numerical element with variable width can't be inserted " + "after another numerical element. " + "Consider \"addFixedXXX()\" instead."); } else { this.addProcessor(np); this.reservedIndex = this.steps.size() - 1; } return this; } private Builder<T> addTwoDigitYear( ChronoElement<Integer> element, boolean protectedMode ) { this.checkElement(element); this.checkAfterDecimalDigits(element); FormatProcessor<?> processor = new TwoDigitYearProcessor(element, protectedMode); if (this.reservedIndex == -1) { this.addProcessor(processor); this.reservedIndex = this.steps.size() - 1; } else { int ri = this.reservedIndex; FormatStep numStep = this.steps.get(ri); this.startSection(Attributes.LENIENCY, Leniency.STRICT); this.addProcessor(processor); this.endSection(); FormatStep lastStep = this.steps.get(this.steps.size() - 1); if (numStep.getSection() == lastStep.getSection()) { this.reservedIndex = ri; this.steps.set(ri, numStep.reserve(2)); } } return this; } private Builder<T> addOrdinalProcessor( ChronoElement<Integer> element, Map<PluralCategory, String> indicators ) { this.checkElement(element); FormatStep last = this.checkAfterDecimalDigits(element); OrdinalProcessor p = new OrdinalProcessor(element, indicators); if ((last != null) && last.isNumerical() && !last.isNewOrBlockStarted()) { throw new IllegalStateException( "Ordinal element with variable width can't be inserted " + "after another numerical element."); } else { this.addProcessor(p); } return this; } private void addProcessor(FormatProcessor<?> processor) { this.reservedIndex = -1; AttributeSet attrs = null; int level = 0; int section = 0; if (!this.stack.isEmpty()) { attrs = this.stack.getLast(); level = attrs.getLevel(); section = attrs.getSection(); } FormatStep step = new FormatStep(processor, level, section, attrs); if (this.leftPadWidth > 0) { step = step.pad(this.leftPadWidth, 0); this.leftPadWidth = 0; } this.steps.add(step); } private TextElement<?> findDayPeriodElement( boolean fixed, DayPeriod dp ) { Attributes attrs = new Attributes.Builder(this.getChronology()).build(); AttributeQuery aq = attrs; if (dp != null) { AttributeSet as; if (this.stack.isEmpty()) { as = new AttributeSet(attrs, this.locale); } else { as = this.stack.getLast(); } aq = as.withInternal(CUSTOM_DAY_PERIOD, dp); } for (ChronoExtension extension : PlainTime.axis().getExtensions()) { for (ChronoElement<?> element : extension.getElements(this.locale, aq)) { if (fixed && (element.getSymbol() == 'b') && this.isDayPeriodSupported(element)) { return cast(element); } else if (!fixed && (element.getSymbol() == 'B') && this.isDayPeriodSupported(element)) { return cast(element); } } } throw new IllegalStateException("Day periods are not supported: " + this.getChronology().getChronoType()); } private boolean isDayPeriodSupported(ChronoElement<?> element) { if (!element.name().endsWith("_DAY_PERIOD")) { return false; } if ((this.override == null) && !this.chronology.isSupported(element)) { Chronology<?> child = this.chronology; while ((child = child.preparser()) != null) { if (child.isSupported(element)) { return true; } } return false; } return true; } private static int getLevel(AttributeSet attributes) { if (attributes == null) { return 0; } return attributes.getLevel(); } private static void checkAttribute(AttributeKey<?> key) { if (key.name().charAt(0) == '_') { throw new IllegalArgumentException("Internal attribute not allowed: " + key.name()); } } private void checkMomentChrono() { if (!hasUnixChronology(this.chronology)) { throw new IllegalStateException( "Timezone names in specific non-location format can only be reliably combined " + "with instant-like types, for example \"Moment\"."); } } private static boolean hasUnixChronology(Chronology<?> chronology) { Chronology<?> c = chronology; do { if (UnixTime.class.isAssignableFrom(c.getChronoType())) { return true; } } while ((c = c.preparser()) != null); return false; } private void resetPadding() { this.leftPadWidth = 0; } private static boolean isSymbol(char c) { return ( ((c >= 'A') && (c <= 'Z')) || ((c >= 'a') && (c <= 'z')) ); } private void checkElement(ChronoElement<?> element) { Chronology<?> test = ChronoFormatter.checkElement(this.chronology, this.override, element); int depth = ChronoFormatter.getDepth(test, this.chronology, this.override); if (depth >= this.depthOfParser) { this.deepestParser = test; this.depthOfParser = depth; } } private void ensureDecimalDigitsOnlyOnce() { for (FormatStep step : this.steps) { if (step.isDecimal()) { throw new IllegalArgumentException( "Cannot define more than one element" + " with decimal digits."); } } } private void ensureOnlyOneFractional( boolean fixedWidth, boolean decimalSeparator ) { this.ensureDecimalDigitsOnlyOnce(); if ( !fixedWidth && !decimalSeparator && (this.reservedIndex != -1) ) { throw new IllegalArgumentException( "Cannot add fractional element with variable width " + "after another numerical element with variable width."); } } private FormatStep checkAfterDecimalDigits(ChronoElement<?> element) { FormatStep last = ( this.steps.isEmpty() ? null : this.steps.get(this.steps.size() - 1) ); if (last == null) { return null; } if (last.isDecimal() && !last.isNewOrBlockStarted()) { throw new IllegalStateException( element.name() + " can't be inserted after an element" + " with decimal digits."); } return last; } private void addLiteralChars(StringBuilder literal) { if (literal.length() > 0) { this.addLiteral(literal.toString()); literal.setLength(0); } } } /** * @serial exclude */ @SuppressWarnings("serial") // Not serializable! private static class TraditionalFormat<T> extends Format { //~ Statische Felder/Initialisierungen ---------------------------- private static final Map<String, DateFormat.Field> FIELD_MAP; static { Map<String, DateFormat.Field> map = new HashMap<>(); map.put("YEAR", DateFormat.Field.YEAR); map.put("YEAR_OF_ERA", DateFormat.Field.YEAR); map.put("YEAR_OF_WEEKDATE", DateFormat.Field.YEAR); map.put("WEEK_OF_YEAR", DateFormat.Field.WEEK_OF_YEAR); map.put("WEEK_OF_MONTH", DateFormat.Field.WEEK_OF_MONTH); map.put("BOUNDED_WEEK_OF_YEAR", DateFormat.Field.WEEK_OF_YEAR); map.put("BOUNDED_WEEK_OF_MONTH", DateFormat.Field.WEEK_OF_MONTH); map.put("MONTH_OF_YEAR", DateFormat.Field.MONTH); map.put("MONTH_AS_NUMBER", DateFormat.Field.MONTH); map.put("HISTORIC_MONTH", DateFormat.Field.MONTH); map.put("WEEKDAY_IN_MONTH", DateFormat.Field.DAY_OF_WEEK_IN_MONTH); map.put("SECOND_OF_MINUTE", DateFormat.Field.SECOND); map.put("MINUTE_OF_HOUR", DateFormat.Field.MINUTE); map.put("MILLI_OF_SECOND", DateFormat.Field.MILLISECOND); map.put("DIGITAL_HOUR_OF_DAY", DateFormat.Field.HOUR_OF_DAY0); map.put("DIGITAL_HOUR_OF_AMPM", DateFormat.Field.HOUR0); map.put("CLOCK_HOUR_OF_DAY", DateFormat.Field.HOUR_OF_DAY1); map.put("CLOCK_HOUR_OF_AMPM", DateFormat.Field.HOUR1); map.put("AM_PM_OF_DAY", DateFormat.Field.AM_PM); map.put("DAY_OF_MONTH", DateFormat.Field.DAY_OF_MONTH); map.put("HISTORIC_DAY_OF_MONTH", DateFormat.Field.DAY_OF_MONTH); map.put("DAY_OF_WEEK", DateFormat.Field.DAY_OF_WEEK); map.put("LOCAL_DAY_OF_WEEK", DateFormat.Field.DAY_OF_WEEK); map.put("DAY_OF_YEAR", DateFormat.Field.DAY_OF_YEAR); map.put("TIMEZONE_ID", DateFormat.Field.TIME_ZONE); map.put("ERA", DateFormat.Field.ERA); FIELD_MAP = Collections.unmodifiableMap(map); } //~ Instanzvariablen ---------------------------------------------- private final ChronoFormatter<T> formatter; //~ Konstruktoren ------------------------------------------------- TraditionalFormat(ChronoFormatter<T> formatter) { super(); this.formatter = formatter; } //~ Methoden ------------------------------------------------------ @Override public StringBuffer format( Object obj, StringBuffer toAppendTo, FieldPosition pos ) { pos.setBeginIndex(0); pos.setEndIndex(0); try { AttributeQuery attrs = this.formatter.globalAttributes; String calendarType = attrs.get(Attributes.CALENDAR_TYPE, ISO_CALENDAR_TYPE); T formattable = this.formatter.getChronology().getChronoType().cast(obj); Set<ElementPosition> positions = this.formatter.print(formattable, toAppendTo, attrs); if (calendarType.equals(ISO_CALENDAR_TYPE)) { for (ElementPosition position : positions) { DateFormat.Field field = toField(position.getElement()); if ( (field != null) && (field.equals(pos.getFieldAttribute()) || ((field.getCalendarField() == pos.getField()) && (pos.getField() != -1)) || (field.equals(DateFormat.Field.TIME_ZONE) && (pos.getField() == DateFormat.TIMEZONE_FIELD)) || (field.equals(DateFormat.Field.HOUR_OF_DAY1) && (pos.getField() == DateFormat.HOUR_OF_DAY1_FIELD)) || (field.equals(DateFormat.Field.HOUR1) && (pos.getField() == DateFormat.HOUR1_FIELD)) ) ) { pos.setBeginIndex(position.getStartIndex()); pos.setEndIndex(position.getEndIndex()); break; } } } return toAppendTo; } catch (ClassCastException cce) { throw new IllegalArgumentException( "Not formattable: " + obj, cce); } catch (IOException ioe) { throw new IllegalArgumentException( "Cannot print object: " + obj, ioe); } } @Override public AttributedCharacterIterator formatToCharacterIterator(Object o) { String calendarType = this.formatter.globalAttributes.get(Attributes.CALENDAR_TYPE, ISO_CALENDAR_TYPE); if (calendarType.equals(ISO_CALENDAR_TYPE)) { try { StringBuilder toAppendTo = new StringBuilder(); T formattable = this.formatter.getChronology().getChronoType().cast(o); Set<ElementPosition> positions = this.formatter.print(formattable, toAppendTo); AttributedString as = new AttributedString(toAppendTo.toString()); for (ElementPosition position : positions) { DateFormat.Field field = toField(position.getElement()); if (field != null) { as.addAttribute( field, field, position.getStartIndex(), position.getEndIndex()); } } return as.getIterator(); } catch (ClassCastException cce) { throw new IllegalArgumentException( "Not formattable: " + o, cce); } } return super.formatToCharacterIterator(o); } @Override public Object parseObject( String source, ParsePosition pos ) { ParseLog status = new ParseLog(pos.getIndex()); T result = this.formatter.parse(source, status); if (result == null) { pos.setErrorIndex(status.getErrorIndex()); } else { pos.setIndex(status.getPosition()); } return result; } private static DateFormat.Field toField(ChronoElement<?> element) { return FIELD_MAP.get(element.name()); } } private static class OverrideHandler<C> implements ChronoMerger<GeneralTimestamp<C>> { //~ Instanzvariablen ---------------------------------------------- private final Chronology<C> override; private final List<ChronoExtension> extensions; //~ Konstruktoren ------------------------------------------------- private OverrideHandler(Chronology<C> override) { super(); this.override = override; List<ChronoExtension> list = new ArrayList<>(); list.addAll(this.override.getExtensions()); list.addAll(PlainTime.axis().getExtensions()); this.extensions = Collections.unmodifiableList(list); } //~ Methoden ------------------------------------------------------ static <C> OverrideHandler<C> of(Chronology<C> override) { if (override == null) { return null; } return new OverrideHandler<>(override); } @Override @Deprecated public GeneralTimestamp<C> createFrom( ChronoEntity<?> entity, AttributeQuery attributes, boolean preparsing ) { boolean lenient = attributes.get(Attributes.LENIENCY, Leniency.SMART).isLax(); return this.createFrom(entity, attributes, lenient, preparsing); } @Override public GeneralTimestamp<C> createFrom( ChronoEntity<?> entity, AttributeQuery attributes, boolean lenient, boolean preparsing ) { C date = this.override.createFrom(entity, attributes, lenient, preparsing); PlainTime time = PlainTime.axis().createFrom(entity, attributes, lenient, preparsing); Object tsp; if (date instanceof CalendarVariant) { tsp = GeneralTimestamp.of(CalendarVariant.class.cast(date), time); } else if (date instanceof Calendrical) { tsp = GeneralTimestamp.of(Calendrical.class.cast(date), time); } else { throw new IllegalStateException("Cannot determine calendar type: " + date); } return cast(tsp); } @Override public StartOfDay getDefaultStartOfDay() { return this.override.getDefaultStartOfDay(); } public List<ChronoExtension> getExtensions() { return this.extensions; } public Chronology<?> getCalendarOverride() { return this.override; } @Override public boolean equals(Object obj) { if (this == obj) { return true; } else if (obj instanceof OverrideHandler) { OverrideHandler that = (OverrideHandler) obj; return this.override.equals(that.override); } else { return false; } } @Override public int hashCode() { return this.override.hashCode(); } @Override public String toString() { return this.override.getChronoType().getName(); } @Override public ChronoDisplay preformat( GeneralTimestamp<C> context, AttributeQuery attributes ) { throw new UnsupportedOperationException("Not used."); } @Override public Chronology<?> preparser() { throw new UnsupportedOperationException("Not used."); } @Override public GeneralTimestamp<C> createFrom( TimeSource<?> clock, AttributeQuery attributes ) { throw new UnsupportedOperationException("Not used."); } @Override public String getFormatPattern( DisplayStyle style, Locale locale ) { throw new UnsupportedOperationException("Not used."); } } private static class ZonalDisplay implements ChronoDisplay, UnixTime { //~ Instanzvariablen ---------------------------------------------- private final GeneralTimestamp<?> tsp; private final TZID tzid; //~ Konstruktoren ------------------------------------------------- private ZonalDisplay( GeneralTimestamp<?> tsp, TZID tzid ) { super(); this.tsp = tsp; this.tzid = tzid; } //~ Methoden ------------------------------------------------------ @Override public boolean contains(ChronoElement<?> element) { return this.tsp.contains(element); } @Override public <V> V get(ChronoElement<V> element) { return this.tsp.get(element); } @Override public int getInt(ChronoElement<Integer> element) { return this.tsp.getInt(element); } @Override public <V> V getMinimum(ChronoElement<V> element) { return this.tsp.getMinimum(element); } @Override public <V> V getMaximum(ChronoElement<V> element) { return this.tsp.getMaximum(element); } @Override public boolean hasTimezone() { return true; } @Override public TZID getTimezone() { return this.tzid; } @Override public long getPosixTime() { return this.getUnixTime().getPosixTime(); // can be used by TimezoneNameProcessor when printing } @Override public int getNanosecond() { return this.getUnixTime().getNanosecond(); } private UnixTime getUnixTime() { StartOfDay startOfDay; try { Class type = this.tsp.toDate().getClass(); startOfDay = Chronology.lookup(type).getDefaultStartOfDay(); } catch (RuntimeException re) { startOfDay = StartOfDay.MIDNIGHT; // fallback } return this.tsp.in(Timezone.of(this.tzid), startOfDay); } } }