/* * ----------------------------------------------------------------------- * Copyright © 2013-2016 Meno Hochschild, <http://www.menodata.de/> * ----------------------------------------------------------------------- * This file (Iso8601Format.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.ClockUnit; import net.time4j.Moment; import net.time4j.PlainDate; import net.time4j.PlainTime; import net.time4j.PlainTimestamp; import net.time4j.Weekmodel; import net.time4j.engine.AttributeQuery; import net.time4j.engine.ChronoCondition; import net.time4j.engine.ChronoDisplay; import net.time4j.engine.ChronoElement; import net.time4j.engine.ChronoEntity; import net.time4j.engine.ChronoFunction; import net.time4j.format.Attributes; import net.time4j.format.DisplayMode; import net.time4j.format.Leniency; import net.time4j.format.NumberSystem; import net.time4j.tz.ZonalOffset; import java.io.IOException; import java.text.ParseException; import java.util.Collections; import java.util.Locale; import java.util.Set; import static net.time4j.PlainDate.*; import static net.time4j.PlainTime.*; /** * <p>Collection of predefined format objects for ISO-8601. </p> * * <p>All formatters are strict by default. The preferred decimal separator is the comma during printing. * This configuration follows the official recommendation of ISO-8601. However, if the system property * "net.time4j.format.iso.decimal.dot" is set to "true" then the dot will be used. * When parsing, both comma or dot are understood. </p> * * <p>Note: Most produced formatters ignore any format attribute change. As exception case, timezone * attributes are recognized by {@code BASIC_DATE_TIME_OFFSET} or {@code EXTENDED_DATE_TIME_OFFSET}. </p> * * @author Meno Hochschild */ /*[deutsch] * <p>Sammlung von vordefinierten Format-Objekten für ISO-8601. </p> * * <p>Alle Formatierer sind per Vorgabe strikt. Das bevorzugte Dezimaltrennzeichen ist das Komma in * der Textausgabe. Diese Konfiguration folgt der offiziellen Empfehlung von ISO-8601. Wenn aber die * System-Property "net.time4j.format.iso.decimal.dot" auf den Wert "true" gesetzt * ist, dann wird der Punkt als Dezimaltrennzeichen verwendet. Alle Textinterpretierer verstehen * sowohl das Komma wie auch den Punkt als Dezimaltrennzeichen. </p> * * <p>Hinweis: Die meisten Formatierer ignorieren Formatattributänderungen. Allerdings können * die Formatierer {@code BASIC_DATE_TIME_OFFSET} und {@code EXTENDED_DATE_TIME_OFFSET} ausnahmsweise * zeitzonenbezogene Attribute verarbeiten. </p> * * @author Meno Hochschild */ public class Iso8601Format { //~ Statische Felder/Initialisierungen -------------------------------- private static final IsoDecimalStyle DEFAULT_ISO_DECIMAL_STYLE = ( Boolean.getBoolean("net.time4j.format.iso.decimal.dot") ? IsoDecimalStyle.DOT : IsoDecimalStyle.COMMA); private static final NonZeroCondition NON_ZERO_SECOND = new NonZeroCondition(PlainTime.SECOND_OF_MINUTE); private static final NonZeroCondition NON_ZERO_FRACTION = new NonZeroCondition(PlainTime.NANO_OF_SECOND); private static final ChronoCondition<ChronoDisplay> SECOND_PART = NON_ZERO_SECOND.or(NON_ZERO_FRACTION); /** * <p>Defines the <i>basic</i> ISO-8601-format with year, month and day * of month using the pattern "uuuuMMdd". </p> */ /*[deutsch] * <p>Definiert das <i>basic</i> ISO-8601-Format mit Jahr, Monat und * Tag des Monats im Muster "uuuuMMdd". </p> */ public static final ChronoFormatter<PlainDate> BASIC_CALENDAR_DATE; /** * <p>Defines the <i>extended</i> ISO-8601-format with year, month and * day of month using the pattern "uuuu-MM-dd". </p> */ /*[deutsch] * <p>Definiert das <i>extended</i> ISO-8601-Format mit Jahr, Monat und * Tag des Monats im Muster "uuuu-MM-dd". </p> */ public static final ChronoFormatter<PlainDate> EXTENDED_CALENDAR_DATE; /** * <p>Defines the <i>basic</i> ISO-8601-format with year and day of year * using the pattern "uuuuDDD". </p> */ /*[deutsch] * <p>Definiert das <i>basic</i> ISO-8601-Format mit Jahr und * Tag des Jahres im Muster "uuuuDDD". </p> */ public static final ChronoFormatter<PlainDate> BASIC_ORDINAL_DATE; /** * <p>Defines the <i>extended</i> ISO-8601-format with year and day * of year using the pattern "uuuu-DDD". </p> */ /*[deutsch] * <p>Definiert das <i>extended</i> ISO-8601-Format mit Jahr und * Tag des Jahres im Muster "uuuu-DDD". </p> */ public static final ChronoFormatter<PlainDate> EXTENDED_ORDINAL_DATE; /** * <p>Defines the <i>basic</i> ISO-8601-format for a week date using * the pattern "YYYYWwwE". </p> */ /*[deutsch] * <p>Definiert das <i>basic</i> ISO-8601-Format für ein * Wochendatum im Muster "YYYYWwwE". </p> */ public static final ChronoFormatter<PlainDate> BASIC_WEEK_DATE; /** * <p>Defines the <i>extended</i> ISO-8601-format for a week date * using the pattern "YYYY-Www-E". </p> */ /*[deutsch] * <p>Definiert das <i>extended</i> ISO-8601-Format für ein * Wochendatum im Muster "YYYY-Www-E". </p> */ public static final ChronoFormatter<PlainDate> EXTENDED_WEEK_DATE; /** * <p>Similar to {@link #BASIC_CALENDAR_DATE} but its parser can also * understand ordinal dates or week dates. </p> * * @since 3.22/4.18 */ /*[deutsch] * <p>Ähnlich wie {@link #BASIC_CALENDAR_DATE}, aber der zugehörige * Textinterpretierer kann auch ein Ordinaldatum oder Wochendatum verstehen. </p> * * @since 3.22/4.18 */ public static final ChronoFormatter<PlainDate> BASIC_DATE; /** * <p>Similar to {@link #EXTENDED_CALENDAR_DATE} but its parser can also * understand ordinal dates or week dates. </p> * * @since 3.22/4.18 */ /*[deutsch] * <p>Ähnlich wie {@link #EXTENDED_CALENDAR_DATE}, aber der zugehörige * Textinterpretierer kann auch ein Ordinaldatum oder Wochendatum verstehen. </p> * * @since 3.22/4.18 */ public static final ChronoFormatter<PlainDate> EXTENDED_DATE; /** * <p>Defines the <i>basic</i> ISO-8601-format for a wall time with * hour, minute and optional second using the pattern * "HH[mm[ss[,SSSSSSSSS]]]". </p> * * <p>The minute part is optional during parsing, but will always * be printed. The parser also accepts a leading char T. </p> */ /*[deutsch] * <p>Definiert das <i>basic</i> ISO-8601-Format für eine * Uhrzeit mit Stunde, Minute und optionaler Sekunde im Muster * "HH[mm[ss[,SSSSSSSSS]]]". Der Parser akzeptiert auch ein * führendes T. </p> * * <p>Der Minutenteil ist beim Parsen optional, wird aber beim * Formatieren immer ausgegeben. </p> */ public static final ChronoFormatter<PlainTime> BASIC_WALL_TIME; /** * <p>Defines the <i>extended</i> ISO-8601-format for a wall time * with hour, minute and optional second using the pattern * "HH[:mm[:ss[,SSSSSSSSS]]]". </p> * * <p>The minute part is optional during parsing, but will always * be printed. The parser also accepts a leading char T. </p> */ /*[deutsch] * <p>Definiert das <i>extended</i> ISO-8601-Format für eine * Uhrzeit mit Stunde, Minute und optionaler Sekunde im Muster * "HH[:mm[:ss[,SSSSSSSSS]]]". </p> * * <p>Der Minutenteil ist beim Parsen optional, wird aber beim * Formatieren immer ausgegeben. Der Parser akzeptiert auch ein * führendes T. </p> */ public static final ChronoFormatter<PlainTime> EXTENDED_WALL_TIME; /** * <p>Defines the <i>basic</i> ISO-8601-format for a composition of * calendar date and wall time with hour and minute using the pattern * "uuuuMMdd'T'HH[mm[ss[,SSSSSSSSS]]]". </p> * * <p>Second and nanosecond elements are optional. Furthermore, * the count of decimal digits is flexible (0-9). The minute part is * optional during parsing, but will always be printed. The parser * will also understand a combination of ordinal date or week date * together with the wall time. </p> */ /*[deutsch] * <p>Definiert das <i>basic</i> ISO-8601-Format für eine Kombination * aus Kalenderdatum und Uhrzeit mit Stunde und Minute im Muster * "uuuuMMdd'T'HH[mm[ss[,SSSSSSSSS]]]". </p> * * <p>Sekunde und Nanosekunde sind optional. Auch die Anzahl der * Dezimalstellen ist variabel (0-9). Der Minutenteil ist beim Parsen * optional, wird aber beim Formatieren immer ausgegeben. Der Interpretierer * wird auch eine Kombination aus Ordinaldatum oder Wochendatum zusammen * mit einer Uhrzeit verstehen. </p> */ public static final ChronoFormatter<PlainTimestamp> BASIC_DATE_TIME; /** * <p>Defines the <i>extended</i> ISO-8601-format for a composition of * calendar date and wall time with hour and minute using the pattern * "uuuu-MM-dd'T'HH[:mm[:ss[,SSSSSSSSS]]]". </p> * * <p>Second and nanosecond elements are optional. Furthermore, * the count of decimal digits is flexible (0-9). The minute part is * optional during parsing, but will always be printed. The parser * will also understand a combination of ordinal date or week date * together with the wall time. </p> */ /*[deutsch] * <p>Definiert das <i>extended</i> ISO-8601-Format für eine * Kombination aus Kalenderdatum und Uhrzeit mit Stunde und Minute * im Muster "uuuu-MM-dd'T'HH[:mm[:ss[,SSSSSSSSS]]]". </p> * * <p>Sekunde und Nanosekunde sind optional. Auch die Anzahl der * Dezimalstellen ist variabel (0-9). Der Minutenteil ist beim Parsen * optional, wird aber beim Formatieren immer ausgegeben. Der Interpretierer * wird auch eine Kombination aus Ordinaldatum oder Wochendatum zusammen * mit einer Uhrzeit verstehen. </p> */ public static final ChronoFormatter<PlainTimestamp> EXTENDED_DATE_TIME; /** * <p>Defines the <i>basic</i> ISO-8601-format for a composition of * calendar date, wall time and timezone offset using the pattern * "uuuuMMdd'T'HH[mm[ss[,SSSSSSSSS]]]{offset}". </p> * * <p>Second and nanosecond elements are optional. Furthermore, * the count of decimal digits is flexible (0-9). The minute part is * optional during parsing, but will always be printed. The offset * part is for printing equivalent to XX, for parsing equivalent to X. * The parser will also understand a combination of ordinal date or * week date together with the wall time. </p> * * <p>By default, the timezone offset used for printing is UTC+00:00. * Users can override this offset by calling the method * {@link ChronoFormatter#withTimezone(net.time4j.tz.TZID)}. </p> */ /*[deutsch] * <p>Definiert das <i>basic</i> ISO-8601-Format für eine Kombination * aus Kalenderdatum, Uhrzeit mit Stunde und Minute und Offset im Muster * "uuuuMMdd'T'HH[mm[ss[,SSSSSSSSS]]]{offset}". </p> * * <p>Sekunde und Nanosekunde sind optional. Auch die Anzahl der * Dezimalstellen ist variabel (0-9). Der Minutenteil ist beim Parsen * optional, wird aber beim Formatieren immer ausgegeben. Der Offset-Teil * ist für die formatierte Ausgabe äquivalent zu XX, beim * Interpretieren äquivalent zu X. Der Interpretierer wird auch * eine Kombination aus Ordinaldatum oder Wochendatum zusammen mit einer * Uhrzeit verstehen. </p> * * <p>Standardmäßig wird für die formatierte Ausgabe der * Zeitzonen-Offset UTC+00:00 verwendet. Diese Vorgabe kann mit Hilfe von * {@link ChronoFormatter#withTimezone(net.time4j.tz.TZID)} geändert * werden. </p> */ public static final ChronoFormatter<Moment> BASIC_DATE_TIME_OFFSET; /** * <p>Defines the <i>extended</i> ISO-8601-format for a composition of * calendar date, wall time and timezone offset using the pattern * "uuuu-MM-dd'T'HH[:mm[:ss[,SSSSSSSSS]]]{offset}". </p> * * <p>Second and nanosecond elements are optional. Furthermore, * the count of decimal digits is flexible (0-9). The minute part is * optional during parsing, but will always be printed. The offset * part is for printing equivalent to XXX, for parsing equivalent * to X (but with colon!). The parser will also understand a combination * of ordinal date or week date together with the wall time. </p> * * <p>By default, the timezone offset used for printing is UTC+00:00. * Users can override this offset by calling the method * {@link ChronoFormatter#withTimezone(net.time4j.tz.TZID)}. </p> */ /*[deutsch] * <p>Definiert das <i>extended</i> ISO-8601-Format für eine * Kombination aus Kalenderdatum, Uhrzeit mit Stunde und Minute und Offset * im Muster "uuuu-MM-dd'T'HH[:mm[:ss[,SSSSSSSSS]]]{offset}". </p> * * <p>Sekunde und Nanosekunde sind optional. Auch die Anzahl der * Dezimalstellen ist variabel (0-9). Der Minutenteil ist beim Parsen * optional, wird aber beim Formatieren immer ausgegeben. Der Offset-Teil * ist für die formatierte Ausgabe äquivalent zu XXX, beim * Interpretieren äquivalent zu X (aber mit Doppelpunkt!). Der * Interpretierer wird auch eine Kombination aus Ordinaldatum oder * Wochendatum zusammen mit einer Uhrzeit verstehen. </p> * * <p>Standardmäßig wird für die formatierte Ausgabe der * Zeitzonen-Offset UTC+00:00 verwendet. Diese Vorgabe kann mit Hilfe von * {@link ChronoFormatter#withTimezone(net.time4j.tz.TZID)} geändert * werden. </p> */ public static final ChronoFormatter<Moment> EXTENDED_DATE_TIME_OFFSET; static { BASIC_CALENDAR_DATE = calendarFormat(false); EXTENDED_CALENDAR_DATE = calendarFormat(true); BASIC_ORDINAL_DATE = ordinalFormat(false); EXTENDED_ORDINAL_DATE = ordinalFormat(true); BASIC_WEEK_DATE = weekdateFormat(false); EXTENDED_WEEK_DATE = weekdateFormat(true); BASIC_DATE = generalDateFormat(false); EXTENDED_DATE = generalDateFormat(true); BASIC_WALL_TIME = timeFormat(false, DEFAULT_ISO_DECIMAL_STYLE); EXTENDED_WALL_TIME = timeFormat(true, DEFAULT_ISO_DECIMAL_STYLE); BASIC_DATE_TIME = timestampFormat(false, DEFAULT_ISO_DECIMAL_STYLE); EXTENDED_DATE_TIME = timestampFormat(true, DEFAULT_ISO_DECIMAL_STYLE); BASIC_DATE_TIME_OFFSET = momentFormat(false, DEFAULT_ISO_DECIMAL_STYLE); EXTENDED_DATE_TIME_OFFSET = momentFormat(true, DEFAULT_ISO_DECIMAL_STYLE); } //~ Konstruktoren ----------------------------------------------------- private Iso8601Format() { // no instantiation } //~ Methoden ---------------------------------------------------------- /** * <p>Obtains a printer with given styles for printing a calendar date. </p> * * @param style iso-compatible date style * @return ChronoPrinter * @since 4.18 */ /*[deutsch] * <p>Liefert einen {@code ChronoPrinter} mit den angegebenen Stilen zur Ausgabe eines Kalenderdatums. </p> * * @param style iso-compatible date style * @return ChronoPrinter * @since 4.18 */ public static ChronoPrinter<PlainDate> ofDate(IsoDateStyle style) { switch (style) { case BASIC_CALENDAR_DATE: return Iso8601Format.BASIC_CALENDAR_DATE; case BASIC_ORDINAL_DATE: return Iso8601Format.BASIC_ORDINAL_DATE; case BASIC_WEEK_DATE: return Iso8601Format.BASIC_WEEK_DATE; case EXTENDED_CALENDAR_DATE: return Iso8601Format.EXTENDED_CALENDAR_DATE; case EXTENDED_ORDINAL_DATE: return Iso8601Format.EXTENDED_ORDINAL_DATE; case EXTENDED_WEEK_DATE: return Iso8601Format.EXTENDED_WEEK_DATE; default: throw new UnsupportedOperationException(style.name()); } } /** * <p>Obtains a printer with given decimal style for printing a clock time * in basic format "HHmm[ss[,SSSSSSSSS]]". </p> * * <p>It is recommended to store the result in a static final constant for achieving best performance. </p> * * @param decimalStyle iso-compatible decimal style * @param precision controls the precision of output format with constant length * @return ChronoPrinter as new instance * @since 4.18 */ /*[deutsch] * <p>Liefert einen {@code ChronoPrinter} mit dem angegebenen Dezimalstil zur Ausgabe einer Uhrzeit * im <i>basic</i>-Format "HHmm[ss[,SSSSSSSSS]]". </p> * * <p>Im Interesse einer maximalen Performance wird empfohlen, das Ergebnis dieser Methode in einer * statischen Konstanten zu speichern. </p> * * @param decimalStyle iso-compatible decimal style * @param precision controls the precision of output format with constant length * @return ChronoPrinter as new instance * @since 4.18 */ public static ChronoPrinter<PlainTime> ofBasicTime( IsoDecimalStyle decimalStyle, ClockUnit precision ) { ChronoFormatter.Builder<PlainTime> builder = ChronoFormatter.setUp(PlainTime.class, Locale.ROOT); addWallTime(builder, false, decimalStyle, precision); return builder.build().with(Leniency.STRICT); } /** * <p>Obtains a printer with given decimal style for printing a clock time * in extended format "HH:mm[:ss[,SSSSSSSSS]]". </p> * * <p>It is recommended to store the result in a static final constant for achieving best performance. </p> * * @param decimalStyle iso-compatible decimal style * @param precision controls the precision of output format with constant length * @return ChronoPrinter as new instance * @since 4.18 */ /*[deutsch] * <p>Liefert einen {@code ChronoPrinter} mit dem angegebenen Dezimalstil zur Ausgabe einer Uhrzeit * im <i>extended</i>-Format "HH:mm[:ss[,SSSSSSSSS]]". </p> * * <p>Im Interesse einer maximalen Performance wird empfohlen, das Ergebnis dieser Methode in einer * statischen Konstanten zu speichern. </p> * * @param decimalStyle iso-compatible decimal style * @param precision controls the precision of output format with constant length * @return ChronoPrinter as new instance * @since 4.18 */ public static ChronoPrinter<PlainTime> ofExtendedTime( IsoDecimalStyle decimalStyle, ClockUnit precision ) { ChronoFormatter.Builder<PlainTime> builder = ChronoFormatter.setUp(PlainTime.class, Locale.ROOT); addWallTime(builder, true, decimalStyle, precision); return builder.build().with(Leniency.STRICT); } /** * <p>Obtains a printer with given styles for printing a timestamp. </p> * * <p>It is recommended to store the result in a static final constant for achieving best performance. </p> * * @param dateStyle iso-compatible date style * @param decimalStyle iso-compatible decimal style * @param precision controls the precision of output format with constant length * @return ChronoPrinter as new instance * @since 4.18 */ /*[deutsch] * <p>Liefert einen {@code ChronoPrinter} mit den angegebenen Stilen zur Ausgabe eines Zeitstempels. </p> * * <p>Im Interesse einer maximalen Performance wird empfohlen, das Ergebnis dieser Methode in einer * statischen Konstanten zu speichern. </p> * * @param dateStyle iso-compatible date style * @param decimalStyle iso-compatible decimal style * @param precision controls the precision of output format with constant length * @return ChronoPrinter as new instance * @since 4.18 */ public static ChronoPrinter<PlainTimestamp> ofTimestamp( IsoDateStyle dateStyle, IsoDecimalStyle decimalStyle, ClockUnit precision ) { ChronoFormatter.Builder<PlainTimestamp> builder = ChronoFormatter.setUp(PlainTimestamp.axis(), Locale.ROOT); builder.addCustomized( PlainDate.COMPONENT, Iso8601Format.ofDate(dateStyle), (text, status, attributes) -> null); builder.addLiteral('T'); addWallTime(builder, dateStyle.isExtended(), decimalStyle, precision); return builder.build().with(Leniency.STRICT); } /** * <p>Obtains a printer with given styles for printing a moment. </p> * * <p>It is recommended to store the result in a static final constant for achieving best performance. </p> * * @param dateStyle iso-compatible date style * @param decimalStyle iso-compatible decimal style * @param precision controls the precision of output format with constant length * @return ChronoPrinter as new instance * @since 4.18 */ /*[deutsch] * <p>Liefert einen {@code ChronoPrinter} mit den angegebenen Stilen zur Ausgabe eines Moments. </p> * * <p>Im Interesse einer maximalen Performance wird empfohlen, das Ergebnis dieser Methode in einer * statischen Konstanten zu speichern. </p> * * @param dateStyle iso-compatible date style * @param decimalStyle iso-compatible decimal style * @param precision controls the precision of output format with constant length * @return ChronoPrinter as new instance * @since 4.18 */ public static ChronoPrinter<Moment> ofMoment( IsoDateStyle dateStyle, IsoDecimalStyle decimalStyle, ClockUnit precision, ZonalOffset offset ) { ChronoFormatter.Builder<Moment> builder = ChronoFormatter.setUp(Moment.axis(), Locale.ROOT); builder.addCustomized( PlainDate.COMPONENT, Iso8601Format.ofDate(dateStyle), (text, status, attributes) -> null); builder.addLiteral('T'); addWallTime(builder, dateStyle.isExtended(), decimalStyle, precision); builder.addTimezoneOffset(DisplayMode.MEDIUM, dateStyle.isExtended(), Collections.singletonList("Z")); return builder.build().with(Leniency.STRICT).withTimezone(offset); } /** * <p>Parses given ISO-8601-compatible date string in basic or extended format. </p> * * @param iso text like "20160101", "2016001", "2016W011", * "2016-01-01", "2016-001" or "2016-W01-1" * @return PlainDate * @throws ParseException if parsing fails for any reason * @since 3.22/4.18 */ /*[deutsch] * <p>Interpretiert den angegebenen ISO-8601-kompatiblen Datumstext im <i>basic</i>-Format * oder im <i>extended</i>-Format. </p> * * @param iso text like "20160101", "2016001", "2016W011", * "2016-01-01", "2016-001" or "2016-W01-1" * @return PlainDate * @throws ParseException if parsing fails for any reason * @since 3.22/4.18 */ public static PlainDate parseDate(CharSequence iso) throws ParseException { ParseLog plog = new ParseLog(); PlainDate date = parseDate(iso, plog); if ((date == null) || plog.isError()) { throw new ParseException(plog.getErrorMessage(), plog.getErrorIndex()); } else if (plog.getPosition() < iso.length()) { throw new ParseException("Trailing characters found: " + iso, plog.getPosition()); } else { return date; } } /** * <p>Parses given ISO-8601-compatible date string in basic or extended format. </p> * * @param iso text like "20160101", "2016001", "2016W011", * "2016-01-01", "2016-001" or "2016-W01-1" * @param plog new mutable instance of {@code ParseLog} * @return PlainDate or {@code null} in case of error * @throws IndexOutOfBoundsException if the start position is at end of text or even behind * @see ParseLog#isError() * @since 3.22/4.18 */ /*[deutsch] * <p>Interpretiert den angegebenen ISO-8601-kompatiblen Datumstext im <i>basic</i>-Format * oder im <i>extended</i>-Format. </p> * * @param iso text like "20160101", "2016001", "2016W011", * "2016-01-01", "2016-001" or "2016-W01-1" * @param plog new mutable instance of {@code ParseLog} * @return PlainDate or {@code null} in case of error * @throws IndexOutOfBoundsException if the start position is at end of text or even behind * @see ParseLog#isError() * @since 3.22/4.18 */ public static PlainDate parseDate( CharSequence iso, ParseLog plog ) { int hyphens = 0; int n = iso.length(); int start = plog.getPosition(); int len = n - start; if (len < 7) { plog.setError(n, "Too short to be compatible with ISO-8601: " + iso.subSequence(start, n)); return null; } LOOP: for (int i = start + 1; i < n; i++) { switch (iso.charAt(i)) { case '-': // leading sign is ignored, see initial loop index hyphens++; break; case 'W': return ((hyphens > 0) ? EXTENDED_WEEK_DATE.parse(iso, plog) : BASIC_WEEK_DATE.parse(iso, plog)); case 'T': case '/': len = i - start; break LOOP; default: // continue } } if (hyphens == 0) { len -= 4; // year length is usually 4 digits char c = iso.charAt(start); if ((c == '+') || (c == '-')) { len -= 2; // concerns years with at least 5 digits or more } return ((len == 3) ? BASIC_ORDINAL_DATE.parse(iso, plog) : BASIC_CALENDAR_DATE.parse(iso, plog)); } else if (hyphens == 1) { return EXTENDED_ORDINAL_DATE.parse(iso, plog); } else { return EXTENDED_CALENDAR_DATE.parse(iso, plog); } } private static ChronoFormatter<PlainDate> calendarFormat(boolean extended) { ChronoFormatter.Builder<PlainDate> builder = ChronoFormatter .setUp(PlainDate.class, Locale.ROOT) .startSection(Attributes.NUMBER_SYSTEM, NumberSystem.ARABIC) .startSection(Attributes.ZERO_DIGIT, '0') .addInteger(YEAR, 4, 9, SignPolicy.SHOW_WHEN_BIG_NUMBER); if (extended) { builder.addLiteral('-'); } builder.addFixedInteger(MONTH_AS_NUMBER, 2); if (extended) { builder.addLiteral('-'); } return builder.addFixedInteger(DAY_OF_MONTH, 2).endSection().endSection().build().with(Leniency.STRICT); } private static ChronoFormatter<PlainDate> ordinalFormat(boolean extended) { ChronoFormatter.Builder<PlainDate> builder = ChronoFormatter .setUp(PlainDate.class, Locale.ROOT) .startSection(Attributes.NUMBER_SYSTEM, NumberSystem.ARABIC) .startSection(Attributes.ZERO_DIGIT, '0') .addInteger(YEAR, 4, 9, SignPolicy.SHOW_WHEN_BIG_NUMBER); if (extended) { builder.addLiteral('-'); } return builder.addFixedInteger(DAY_OF_YEAR, 3).endSection().endSection().build().with(Leniency.STRICT); } private static ChronoFormatter<PlainDate> weekdateFormat(boolean extended) { ChronoFormatter.Builder<PlainDate> builder = ChronoFormatter .setUp(PlainDate.class, Locale.ROOT) .startSection(Attributes.NUMBER_SYSTEM, NumberSystem.ARABIC) .startSection(Attributes.ZERO_DIGIT, '0') .addInteger(YEAR_OF_WEEKDATE, 4, 9, SignPolicy.SHOW_WHEN_BIG_NUMBER); if (extended) { builder.addLiteral('-'); } builder.addLiteral('W'); builder.addFixedInteger(Weekmodel.ISO.weekOfYear(), 2); if (extended) { builder.addLiteral('-'); } return builder.addFixedNumerical(DAY_OF_WEEK, 1).endSection().endSection().build().with(Leniency.STRICT); } private static ChronoFormatter<PlainDate> generalDateFormat(boolean extended) { ChronoFormatter.Builder<PlainDate> builder = ChronoFormatter.setUp(PlainDate.class, Locale.ROOT); builder.addCustomized( PlainDate.COMPONENT, generalDatePrinter(extended), generalDateParser(extended)); return builder.build().with(Leniency.STRICT); } private static ChronoFormatter<PlainTime> timeFormat( boolean extended, IsoDecimalStyle decimalStyle ) { ChronoFormatter.Builder<PlainTime> builder = ChronoFormatter.setUp(PlainTime.class, Locale.ROOT); builder.skipUnknown(c -> (c == 'T'), 1); addWallTime(builder, extended, decimalStyle); return builder.build().with(Leniency.STRICT); } private static ChronoFormatter<PlainTimestamp> timestampFormat( boolean extended, IsoDecimalStyle decimalStyle ) { ChronoFormatter.Builder<PlainTimestamp> builder = ChronoFormatter.setUp(PlainTimestamp.class, Locale.ROOT); builder.addCustomized( PlainDate.COMPONENT, generalDatePrinter(extended), generalDateParser(extended)); builder.addLiteral('T'); addWallTime(builder, extended, decimalStyle); return builder.build().with(Leniency.STRICT); } private static ChronoFormatter<Moment> momentFormat( boolean extended, IsoDecimalStyle decimalStyle ) { ChronoFormatter.Builder<Moment> builder = ChronoFormatter.setUp(Moment.class, Locale.ROOT); builder.addCustomized( Moment.axis().element(), momentFormat(DisplayMode.MEDIUM, extended, decimalStyle), momentFormat(DisplayMode.SHORT, extended, decimalStyle) ); // here timezone offset is needed for changing Moment to ZonalDateTime when printing return builder.build().with(Leniency.STRICT).withTimezone(ZonalOffset.UTC); } private static ChronoFormatter<Moment> momentFormat( DisplayMode mode, boolean extended, IsoDecimalStyle decimalStyle ) { ChronoFormatter.Builder<Moment> builder = ChronoFormatter.setUp(Moment.class, Locale.ROOT); builder.addCustomized( PlainDate.COMPONENT, generalDatePrinter(extended), generalDateParser(extended)); builder.addLiteral('T'); addWallTime(builder, extended, decimalStyle); // not optional, offset must be present during parsing builder.addTimezoneOffset( mode, extended, Collections.singletonList("Z")); return builder.build().with(Leniency.STRICT); } private static ChronoPrinter<PlainDate> generalDatePrinter(final boolean extended) { return new ChronoPrinter<PlainDate>() { @Override public Set<ElementPosition> print( PlainDate formattable, StringBuilder buffer, AttributeQuery attributes ) { ChronoFormatter<PlainDate> f = (extended ? EXTENDED_CALENDAR_DATE : BASIC_CALENDAR_DATE); return f.print(formattable, buffer); // attributes are ignored to ensure quick path evaluation } // TODO: remove with v5.0 @Override public <R> R print( PlainDate formattable, Appendable buffer, AttributeQuery attributes, ChronoFunction<ChronoDisplay, R> query ) throws IOException { ChronoFormatter<PlainDate> f = (extended ? EXTENDED_CALENDAR_DATE : BASIC_CALENDAR_DATE); f.formatToBuffer(formattable, buffer); return null; // always ignored } }; } private static ChronoParser<PlainDate> generalDateParser(final boolean extended) { return (text, status, attributes) -> { int hyphens = 0; int n = text.length(); int start = status.getPosition(); int len = n - start; LOOP: for (int i = start + 1; i < n; i++) { switch (text.charAt(i)) { case '-': // leading sign is ignored, see initial loop index hyphens++; break; case 'W': if (extended) { return EXTENDED_WEEK_DATE.parse(text, status); } else { return BASIC_WEEK_DATE.parse(text, status); } case 'T': case '/': len = i - start; break LOOP; default: // continue } } if (extended) { if (hyphens == 1) { return EXTENDED_ORDINAL_DATE.parse(text, status); } else { return EXTENDED_CALENDAR_DATE.parse(text, status); } } else { len -= 4; // year length is usually 4 digits char c = text.charAt(start); if ((c == '+') || (c == '-')) { len -= 2; // concerns years with at least 5 digits or more } if (len == 3) { return BASIC_ORDINAL_DATE.parse(text, status); } else { return BASIC_CALENDAR_DATE.parse(text, status); } } }; } private static <T extends ChronoEntity<T>> void addWallTime( ChronoFormatter.Builder<T> builder, boolean extended, IsoDecimalStyle decimalStyle ) { builder.startSection(Attributes.NUMBER_SYSTEM, NumberSystem.ARABIC); builder.startSection(Attributes.ZERO_DIGIT, '0'); builder.addFixedInteger(ISO_HOUR, 2); builder.startOptionalSection(); if (extended) { builder.addLiteral(':'); } builder.addFixedInteger(MINUTE_OF_HOUR, 2); builder.startOptionalSection(SECOND_PART); if (extended) { builder.addLiteral(':'); } builder.addFixedInteger(SECOND_OF_MINUTE, 2); builder.startOptionalSection(NON_ZERO_FRACTION); switch (decimalStyle) { case COMMA: builder.addLiteral(',', '.'); break; case DOT: builder.addLiteral('.', ','); break; default: throw new UnsupportedOperationException(decimalStyle.name()); } builder.addFraction(NANO_OF_SECOND, 1, 9, false); for (int i = 0; i < 5; i++) { builder.endSection(); } } private static <T extends ChronoEntity<T>> void addWallTime( ChronoFormatter.Builder<T> builder, boolean extended, IsoDecimalStyle decimalStyle, ClockUnit precision ) { if (precision == null) { throw new NullPointerException("Missing precision."); } builder.startSection(Attributes.NUMBER_SYSTEM, NumberSystem.ARABIC); builder.startSection(Attributes.ZERO_DIGIT, '0'); builder.addFixedInteger(ISO_HOUR, 2); if (precision == ClockUnit.HOURS) { builder.endSection(); builder.endSection(); return; } if (extended) { builder.addLiteral(':'); } builder.addFixedInteger(MINUTE_OF_HOUR, 2); if (precision == ClockUnit.MINUTES) { builder.endSection(); builder.endSection(); return; } if (extended) { builder.addLiteral(':'); } builder.addFixedInteger(SECOND_OF_MINUTE, 2); if (precision == ClockUnit.SECONDS) { builder.endSection(); builder.endSection(); return; } switch (decimalStyle) { case COMMA: builder.addLiteral(',', '.'); break; case DOT: builder.addLiteral('.', ','); break; default: throw new UnsupportedOperationException(decimalStyle.name()); } if (precision == ClockUnit.MILLIS) { builder.addFraction(NANO_OF_SECOND, 3, 3, false); } else if (precision == ClockUnit.MICROS) { builder.addFraction(NANO_OF_SECOND, 6, 6, false); } else if (precision == ClockUnit.NANOS) { builder.addFraction(NANO_OF_SECOND, 9, 9, false); } builder.endSection(); builder.endSection(); } //~ Innere Klassen ---------------------------------------------------- private static class NonZeroCondition implements ChronoCondition<ChronoDisplay> { //~ Instanzvariablen ---------------------------------------------- private final ChronoElement<Integer> element; //~ Konstruktoren ------------------------------------------------- NonZeroCondition(ChronoElement<Integer> element) { super(); this.element = element; } //~ Methoden ------------------------------------------------------ @Override public boolean test(ChronoDisplay context) { return (context.getInt(this.element) > 0); } ChronoCondition<ChronoDisplay> or(final NonZeroCondition other) { return context -> (NonZeroCondition.this.test(context) || other.test(context)); } } }