/* * ----------------------------------------------------------------------- * Copyright © 2013-2016 Meno Hochschild, <http://www.menodata.de/> * ----------------------------------------------------------------------- * This file (MomentInterval.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.range; import net.time4j.ClockUnit; import net.time4j.Duration; import net.time4j.IsoUnit; import net.time4j.Moment; import net.time4j.PlainDate; import net.time4j.PlainTime; import net.time4j.PlainTimestamp; import net.time4j.SI; import net.time4j.Weekmodel; import net.time4j.engine.AttributeQuery; import net.time4j.engine.ChronoDisplay; import net.time4j.engine.ChronoElement; import net.time4j.engine.ChronoEntity; import net.time4j.format.Attributes; import net.time4j.format.DisplayMode; import net.time4j.format.expert.ChronoFormatter; import net.time4j.format.expert.ChronoParser; import net.time4j.format.expert.ChronoPrinter; import net.time4j.format.expert.ElementPosition; import net.time4j.format.expert.Iso8601Format; import net.time4j.format.expert.IsoDateStyle; import net.time4j.format.expert.IsoDecimalStyle; import net.time4j.format.expert.ParseLog; import net.time4j.format.expert.SignPolicy; import net.time4j.tz.TZID; import net.time4j.tz.Timezone; import net.time4j.tz.ZonalOffset; import java.io.IOException; import java.io.InvalidObjectException; import java.io.ObjectInputStream; import java.io.Serializable; import java.text.ParseException; import java.time.Instant; import java.util.Collections; import java.util.Comparator; import java.util.Locale; import java.util.Set; import java.util.concurrent.TimeUnit; import static net.time4j.PlainDate.*; import static net.time4j.format.Attributes.PROTECTED_CHARACTERS; import static net.time4j.range.IntervalEdge.CLOSED; import static net.time4j.range.IntervalEdge.OPEN; /** * <p>Defines a moment interval on global timeline. </p> * * @author Meno Hochschild * @since 2.0 * @doctags.concurrency {immutable} */ /*[deutsch] * <p>Definiert ein Momentintervall auf dem globalen Zeitstrahl. </p> * * @author Meno Hochschild * @since 2.0 * @doctags.concurrency {immutable} */ public final class MomentInterval extends IsoInterval<Moment, MomentInterval> implements Serializable { //~ Statische Felder/Initialisierungen -------------------------------- private static final long serialVersionUID = -5403584519478162113L; private static final Comparator<ChronoInterval<Moment>> COMPARATOR = new IntervalComparator<>(false, Moment.axis()); //~ Konstruktoren ----------------------------------------------------- // package-private MomentInterval( Boundary<Moment> start, Boundary<Moment> end ) { super(start, end); } //~ Methoden ---------------------------------------------------------- /** * <p>Defines a comparator which sorts intervals first * by start boundary and then by length. </p> * * @return Comparator * @throws IllegalArgumentException if applied on intervals which have * boundaries with extreme values * @since 2.0 */ /*[deutsch] * <p>Definiert ein Vergleichsobjekt, das Intervalle zuerst nach dem * Start und dann nach der Länge sortiert. </p> * * @return Comparator * @throws IllegalArgumentException if applied on intervals which have * boundaries with extreme values * @since 2.0 */ public static Comparator<ChronoInterval<Moment>> comparator() { return COMPARATOR; } /** * <p>Creates a finite half-open interval between given time points. </p> * * @param start moment of lower boundary (inclusive) * @param end moment of upper boundary (exclusive) * @return new moment interval * @throws IllegalArgumentException if start is after end * @since 2.0 */ /*[deutsch] * <p>Erzeugt ein begrenztes halb-offenes Intervall zwischen den * angegebenen Zeitpunkten. </p> * * @param start moment of lower boundary (inclusive) * @param end moment of upper boundary (exclusive) * @return new moment interval * @throws IllegalArgumentException if start is after end * @since 2.0 */ public static MomentInterval between( Moment start, Moment end ) { return new MomentInterval( Boundary.of(CLOSED, start), Boundary.of(OPEN, end)); } /** * <p>Creates a finite half-open interval between given time points. </p> * * @param start moment of lower boundary (inclusive) * @param end moment of upper boundary (exclusive) * @return new moment interval * @throws IllegalArgumentException if start is after end * @see #between(Moment, Moment) * @since 4.11 */ /*[deutsch] * <p>Erzeugt ein begrenztes halb-offenes Intervall zwischen den angegebenen Zeitpunkten. </p> * * @param start moment of lower boundary (inclusive) * @param end moment of upper boundary (exclusive) * @return new moment interval * @throws IllegalArgumentException if start is after end * @see #between(Moment, Moment) * @since 4.11 */ public static MomentInterval between( Instant start, Instant end ) { return MomentInterval.between(Moment.from(start), Moment.from(end)); } /** * <p>Creates an infinite half-open interval since given start. </p> * * @param start moment of lower boundary (inclusive) * @return new moment interval * @since 2.0 */ /*[deutsch] * <p>Erzeugt ein unbegrenztes halb-offenes Intervall ab dem angegebenen * Startzeitpunkt. </p> * * @param start moment of lower boundary (inclusive) * @return new moment interval * @since 2.0 */ public static MomentInterval since(Moment start) { Boundary<Moment> future = Boundary.infiniteFuture(); return new MomentInterval(Boundary.of(CLOSED, start), future); } /** * <p>Creates an infinite half-open interval since given start. </p> * * @param start moment of lower boundary (inclusive) * @return new moment interval * @see #since(Moment) * @since 4.11 */ /*[deutsch] * <p>Erzeugt ein unbegrenztes halb-offenes Intervall ab dem angegebenen Startzeitpunkt. </p> * * @param start moment of lower boundary (inclusive) * @return new moment interval * @see #since(Moment) * @since 4.11 */ public static MomentInterval since(Instant start) { return MomentInterval.since(Moment.from(start)); } /** * <p>Creates an infinite open interval until given end. </p> * * @param end moment of upper boundary (exclusive) * @return new moment interval * @since 2.0 */ /*[deutsch] * <p>Erzeugt ein unbegrenztes offenes Intervall bis zum angegebenen * Endzeitpunkt. </p> * * @param end moment of upper boundary (exclusive) * @return new moment interval * @since 2.0 */ public static MomentInterval until(Moment end) { Boundary<Moment> past = Boundary.infinitePast(); return new MomentInterval(past, Boundary.of(OPEN, end)); } /** * <p>Creates an infinite open interval until given end. </p> * * @param end moment of upper boundary (exclusive) * @return new moment interval * @see #until(Moment) * @since 4.11 */ /*[deutsch] * <p>Erzeugt ein unbegrenztes offenes Intervall bis zum angegebenen Endzeitpunkt. </p> * * @param end moment of upper boundary (exclusive) * @return new moment interval * @see #until(Moment) * @since 4.11 */ public static MomentInterval until(Instant end) { return MomentInterval.until(Moment.from(end)); } /** * <p>Yields the start time point. </p> * * @return start time point or {@code null} if infinite * @see Boundary#isInfinite() * @since 4.11 */ /*[deutsch] * <p>Liefert den Startzeitpunkt. </p> * * @return start time point or {@code null} if infinite * @see Boundary#isInfinite() * @since 4.11 */ public Moment getStartAsMoment() { return this.getStart().getTemporal(); } /** * <p>Yields the start time point. </p> * * <p>Note: Leap seconds will be lost here. </p> * * @return start time point or {@code null} if infinite * @see Boundary#isInfinite() * @since 4.11 */ /*[deutsch] * <p>Liefert den Startzeitpunkt. </p> * * <p>Hinweis: Schaltsekunden gehen hier verloren. </p> * * @return start time point or {@code null} if infinite * @see Boundary#isInfinite() * @since 4.11 */ public Instant getStartAsInstant() { Moment moment = this.getStartAsMoment(); return ((moment == null) ? null : moment.toTemporalAccessor()); } /** * <p>Yields the end time point. </p> * * @return end time point or {@code null} if infinite * @see Boundary#isInfinite() * @since 4.11 */ /*[deutsch] * <p>Liefert den Endzeitpunkt. </p> * * @return end time point or {@code null} if infinite * @see Boundary#isInfinite() * @since 4.11 */ public Moment getEndAsMoment() { return this.getEnd().getTemporal(); } /** * <p>Yields the end time point. </p> * * <p>Note: Leap seconds will be lost here. </p> * * @return end time point or {@code null} if infinite * @see Boundary#isInfinite() * @since 4.11 */ /*[deutsch] * <p>Liefert den Endzeitpunkt. </p> * * <p>Hinweis: Schaltsekunden gehen hier verloren. </p> * * @return end time point or {@code null} if infinite * @see Boundary#isInfinite() * @since 4.11 */ public Instant getEndAsInstant() { Moment moment = this.getEndAsMoment(); return ((moment == null) ? null : moment.toTemporalAccessor()); } /** * <p>Converts this instance to a local timestamp interval in the system * timezone. </p> * * @return local timestamp interval in system timezone (leap seconds will * always be lost) * @since 2.0 * @see Timezone#ofSystem() * @see #toZonalInterval(TZID) * @see #toZonalInterval(String) */ /*[deutsch] * <p>Wandelt diese Instanz in ein lokales Zeitstempelintervall um. </p> * * @return local timestamp interval in system timezone (leap seconds will * always be lost) * @since 2.0 * @see Timezone#ofSystem() * @see #toZonalInterval(TZID) * @see #toZonalInterval(String) */ public TimestampInterval toLocalInterval() { Boundary<PlainTimestamp> b1; Boundary<PlainTimestamp> b2; if (this.getStart().isInfinite()) { b1 = Boundary.infinitePast(); } else { PlainTimestamp t1 = this.getStart().getTemporal().toLocalTimestamp(); b1 = Boundary.of(this.getStart().getEdge(), t1); } if (this.getEnd().isInfinite()) { b2 = Boundary.infiniteFuture(); } else { PlainTimestamp t2 = this.getEnd().getTemporal().toLocalTimestamp(); b2 = Boundary.of(this.getEnd().getEdge(), t2); } return new TimestampInterval(b1, b2); } /** * <p>Converts this instance to a zonal timestamp interval * in given timezone. </p> * * @param tzid timezone id * @return zonal timestamp interval in given timezone (leap seconds will * always be lost) * @throws IllegalArgumentException if given timezone cannot be loaded * @since 2.0 * @see #toLocalInterval() */ /*[deutsch] * <p>Wandelt diese Instanz in ein zonales Zeitstempelintervall um. </p> * * @param tzid timezone id * @return zonal timestamp interval in given timezone (leap seconds will * always be lost) * @throws IllegalArgumentException if given timezone cannot be loaded * @since 2.0 * @see #toLocalInterval() */ public TimestampInterval toZonalInterval(TZID tzid) { Boundary<PlainTimestamp> b1; Boundary<PlainTimestamp> b2; if (this.getStart().isInfinite()) { b1 = Boundary.infinitePast(); } else { PlainTimestamp t1 = this.getStart().getTemporal().toZonalTimestamp(tzid); b1 = Boundary.of(this.getStart().getEdge(), t1); } if (this.getEnd().isInfinite()) { b2 = Boundary.infiniteFuture(); } else { PlainTimestamp t2 = this.getEnd().getTemporal().toZonalTimestamp(tzid); b2 = Boundary.of(this.getEnd().getEdge(), t2); } return new TimestampInterval(b1, b2); } /** * <p>Converts this instance to a zonal timestamp interval * in given timezone. </p> * * @param tzid timezone id * @return zonal timestamp interval in given timezone (leap seconds will * always be lost) * @throws IllegalArgumentException if given timezone cannot be loaded * @since 2.0 * @see #toZonalInterval(TZID) * @see #toLocalInterval() */ /*[deutsch] * <p>Wandelt diese Instanz in ein zonales Zeitstempelintervall um. </p> * * @param tzid timezone id * @return zonal timestamp interval in given timezone (leap seconds will * always be lost) * @throws IllegalArgumentException if given timezone cannot be loaded * @since 2.0 * @see #toZonalInterval(TZID) * @see #toLocalInterval() */ public TimestampInterval toZonalInterval(String tzid) { Boundary<PlainTimestamp> b1; Boundary<PlainTimestamp> b2; if (this.getStart().isInfinite()) { b1 = Boundary.infinitePast(); } else { PlainTimestamp t1 = this.getStart().getTemporal().toZonalTimestamp(tzid); b1 = Boundary.of(this.getStart().getEdge(), t1); } if (this.getEnd().isInfinite()) { b2 = Boundary.infiniteFuture(); } else { PlainTimestamp t2 = this.getEnd().getTemporal().toZonalTimestamp(tzid); b2 = Boundary.of(this.getEnd().getEdge(), t2); } return new TimestampInterval(b1, b2); } /** * <p>Yields the nominal duration of this interval in given timezone and units. </p> * * @param tz timezone * @param units time units to be used in calculation * @return nominal duration * @throws UnsupportedOperationException if this interval is infinite * @since 3.0 * @see #getSimpleDuration() * @see #getRealDuration() */ /*[deutsch] * <p>Liefert die nominelle Dauer dieses Intervalls in der angegebenen Zeitzone * und den angegebenen Zeiteinheiten. </p> * * @param tz timezone * @param units time units to be used in calculation * @return nominal duration * @throws UnsupportedOperationException if this interval is infinite * @since 3.0 * @see #getSimpleDuration() * @see #getRealDuration() */ public Duration<IsoUnit> getNominalDuration( Timezone tz, IsoUnit... units ) { return this.toZonalInterval(tz.getID()).getDuration(tz, units); } /** * <p>Yields the length of this interval on the POSIX-scale. </p> * * @return machine time duration on POSIX-scale * @throws UnsupportedOperationException if this interval is infinite * @since 2.0 * @see #getRealDuration() */ /*[deutsch] * <p>Liefert die Länge dieses Intervalls auf der POSIX-Skala. </p> * * @return machine time duration on POSIX-scale * @throws UnsupportedOperationException if this interval is infinite * @since 2.0 * @see #getRealDuration() */ public MachineTime<TimeUnit> getSimpleDuration() { Moment tsp = this.getTemporalOfOpenEnd(); boolean max = (tsp == null); if (max) { // max reached tsp = this.getEnd().getTemporal(); } MachineTime<TimeUnit> result = MachineTime.ON_POSIX_SCALE.between( this.getTemporalOfClosedStart(), tsp); if (max) { return result.plus(1, TimeUnit.NANOSECONDS); } return result; } /** * <p>Yields the length of this interval on the UTC-scale. </p> * * @return machine time duration on UTC-scale * @throws UnsupportedOperationException if start is before year 1972 * or if this interval is infinite * @since 2.0 * @see #getSimpleDuration() */ /*[deutsch] * <p>Liefert die Länge dieses Intervalls auf der UTC-Skala. </p> * * @return machine time duration on UTC-scale * @throws UnsupportedOperationException if start is before year 1972 * or if this interval is infinite * @since 2.0 * @see #getSimpleDuration() */ public MachineTime<SI> getRealDuration() { Moment tsp = this.getTemporalOfOpenEnd(); boolean max = (tsp == null); if (max) { // max reached tsp = this.getEnd().getTemporal(); } MachineTime<SI> result = MachineTime.ON_UTC_SCALE.between( this.getTemporalOfClosedStart(), tsp); if (max) { return result.plus(1, SI.NANOSECONDS); } return result; } /** * <p>Moves this interval along the POSIX-axis by given time units. </p> * * @param amount amount of units * @param unit time unit for moving * @return moved copy of this interval */ public MomentInterval move( long amount, TimeUnit unit ) { if (amount == 0) { return this; } Boundary<Moment> s; Boundary<Moment> e; if (this.getStart().isInfinite()) { s = Boundary.infinitePast(); } else { s = Boundary.of( this.getStart().getEdge(), this.getStart().getTemporal().plus(amount, unit)); } if (this.getEnd().isInfinite()) { e = Boundary.infiniteFuture(); } else { e = Boundary.of( this.getEnd().getEdge(), this.getEnd().getTemporal().plus(amount, unit)); } return new MomentInterval(s, e); } /** * <p>Moves this interval along the UTC-axis by given SI-units. </p> * * @param amount amount of units * @param unit time unit for moving * @return moved copy of this interval */ public MomentInterval move( long amount, SI unit ) { if (amount == 0) { return this; } Boundary<Moment> s; Boundary<Moment> e; if (this.getStart().isInfinite()) { s = Boundary.infinitePast(); } else { s = Boundary.of( this.getStart().getEdge(), this.getStart().getTemporal().plus(amount, unit)); } if (this.getEnd().isInfinite()) { e = Boundary.infiniteFuture(); } else { e = Boundary.of( this.getEnd().getEdge(), this.getEnd().getTemporal().plus(amount, unit)); } return new MomentInterval(s, e); } /** * <p>Prints the canonical form of this interval in given ISO-8601 style. </p> * * @param dateStyle iso-compatible date style * @param decimalStyle iso-compatible decimal style * @param precision controls the precision of output format with constant length * @param offset timezone offset * @param infinityStyle controlling the format of infinite boundaries * @return String * @throws IllegalStateException if there is no canonical form * or given infinity style prevents infinite intervals * @see #toCanonical() * @since 4.18 */ /*[deutsch] * <p>Formatiert die kanonische Form dieses Intervalls im angegebenen ISO-8601-Stil. </p> * * @param dateStyle iso-compatible date style * @param decimalStyle iso-compatible decimal style * @param precision controls the precision of output format with constant length * @param offset timezone offset * @param infinityStyle controlling the format of infinite boundaries * @return String * @throws IllegalStateException if there is no canonical form * or given infinity style prevents infinite intervals * @see #toCanonical() * @since 4.18 */ public String formatISO( IsoDateStyle dateStyle, IsoDecimalStyle decimalStyle, ClockUnit precision, ZonalOffset offset, InfinityStyle infinityStyle ) { MomentInterval interval = this.toCanonical(); StringBuilder buffer = new StringBuilder(65); ChronoPrinter<Moment> printer = Iso8601Format.ofMoment(dateStyle, decimalStyle, precision, offset); if (interval.getStart().isInfinite()) { buffer.append(infinityStyle.displayPast(printer, Moment.axis())); } else { printer.print(interval.getStartAsMoment(), buffer); } buffer.append('/'); if (interval.getEnd().isInfinite()) { buffer.append(infinityStyle.displayFuture(printer, Moment.axis())); } else { printer.print(interval.getEndAsMoment(), buffer); } return buffer.toString(); } /** * <p>Prints the canonical form of this interval in given reduced ISO-8601 style. </p> * * <p>The term "reduced" means that higher-order elements like the year can be * left out in the end component if it is equal to the value of the start component. * And the offset is never printed in the end component of finite intervals. Example: </p> * * <pre> * MomentInterval interval = * MomentInterval.between( * PlainTimestamp.of(2012, 6, 29, 10, 45), * PlainTimestamp.of(2012, 6, 30, 23, 59, 59).atUTC().plus(1, SI.SECONDS)); * System.out.println( * interval.formatReduced( * IsoDateStyle.EXTENDED_CALENDAR_DATE, * IsoDecimalStyle.DOT, * ClockUnit.SECONDS, * ZonalOffset.UTC, * InfinityStyle.SYMBOL)); * // Output: 2016-02-29T10:45:00Z/30T23:59:60 * </pre> * * @param dateStyle iso-compatible date style * @param decimalStyle iso-compatible decimal style * @param precision controls the precision of output format with constant length * @param offset timezone offset * @param infinityStyle controlling the format of infinite boundaries * @return String * @throws IllegalStateException if there is no canonical form * or given infinity style prevents infinite intervals * @see #toCanonical() * @since 4.18 */ /*[deutsch] * <p>Formatiert die kanonische Form dieses Intervalls im angegebenen reduzierten ISO-8601-Stil. </p> * * <p>Der Begriff "reduziert" bedeutet, daß höherwertige Elemente wie das Jahr * in der Endkomponente weggelassen werden, wenn ihr Wert gleich dem Wert der Startkomponente ist. * Außerdem wird in der Endkomponente der Offset für begrenzte Intervalle immer weggelassen. * Beispiel: </p> * * <pre> * MomentInterval interval = * MomentInterval.between( * PlainTimestamp.of(2012, 6, 29, 10, 45), * PlainTimestamp.of(2012, 6, 30, 23, 59, 59).atUTC().plus(1, SI.SECONDS)); * System.out.println( * interval.formatReduced( * IsoDateStyle.EXTENDED_CALENDAR_DATE, * IsoDecimalStyle.DOT, * ClockUnit.SECONDS, * ZonalOffset.UTC, * InfinityStyle.SYMBOL)); * // Output: 2016-02-29T10:45:00Z/30T23:59:60 * </pre> * * @param dateStyle iso-compatible date style * @param decimalStyle iso-compatible decimal style * @param precision controls the precision of output format with constant length * @param offset timezone offset * @param infinityStyle controlling the format of infinite boundaries * @return String * @throws IllegalStateException if there is no canonical form * or given infinity style prevents infinite intervals * @see #toCanonical() * @since 4.18 */ public String formatReduced( IsoDateStyle dateStyle, IsoDecimalStyle decimalStyle, ClockUnit precision, ZonalOffset offset, InfinityStyle infinityStyle ) { MomentInterval interval = this.toCanonical(); Moment start = interval.getStartAsMoment(); Moment end = interval.getEndAsMoment(); StringBuilder buffer = new StringBuilder(65); ChronoPrinter<Moment> printer = Iso8601Format.ofMoment(dateStyle, decimalStyle, precision, offset); if (interval.getStart().isInfinite()) { ChronoPrinter<Moment> utc = null; if (infinityStyle == InfinityStyle.MIN_MAX) { utc = Iso8601Format.ofMoment(dateStyle, decimalStyle, precision, ZonalOffset.UTC); } buffer.append(infinityStyle.displayPast(utc, Moment.axis())); } else { printer.print(start, buffer); } buffer.append('/'); if (interval.isFinite()) { PlainTimestamp tsp2 = end.toZonalTimestamp(offset); PlainDate d1 = start.toZonalTimestamp(offset).getCalendarDate(); PlainDate d2 = tsp2.getCalendarDate(); if (!d1.equals(d2)) { DateInterval.getEndPrinter(dateStyle, d1, d2).print(d2, buffer); } buffer.append('T'); ChronoPrinter<PlainTime> timePrinter = ( dateStyle.isExtended() ? Iso8601Format.ofExtendedTime(decimalStyle, precision) : Iso8601Format.ofBasicTime(decimalStyle, precision)); Set<ElementPosition> positions = timePrinter.print(tsp2.getWallTime(), buffer); if (end.isLeapSecond()) { for (ElementPosition position : positions) { if (position.getElement() == PlainTime.SECOND_OF_MINUTE) { buffer.replace(position.getStartIndex(), position.getEndIndex(), "60"); break; } } } } else if (interval.getEnd().isInfinite()) { ChronoPrinter<Moment> utc = null; if (infinityStyle == InfinityStyle.MIN_MAX) { utc = Iso8601Format.ofMoment(dateStyle, decimalStyle, precision, ZonalOffset.UTC); } buffer.append(infinityStyle.displayFuture(utc, Moment.axis())); } else { printer.print(end, buffer); } return buffer.toString(); } /** * <p>Interpretes given text as interval using a localized interval pattern. </p> * * <p>If given parser does not contain a reference to a locale then the interval pattern * "{0}/{1}" will be used. </p> * * @param text text to be parsed * @param parser format object for parsing start and end components * @return parsed interval * @throws IndexOutOfBoundsException if given text is empty * @throws ParseException if the text is not parseable * @since 3.9/4.6 * @see #parse(String, ChronoParser, String) * @see net.time4j.format.FormatPatternProvider#getIntervalPattern(Locale) */ /*[deutsch] * <p>Interpretiert den angegebenen Text als Intervall mit Hilfe eines lokalisierten * Intervallmusters. </p> * * <p>Falls der angegebene Formatierer keine Referenz zu einer Sprach- und Ländereinstellung hat, wird * das Intervallmuster "{0}/{1}" verwendet. </p> * * @param text text to be parsed * @param parser format object for parsing start and end components * @return parsed interval * @throws IndexOutOfBoundsException if given text is empty * @throws ParseException if the text is not parseable * @since 3.9/4.6 * @see #parse(String, ChronoParser, String) * @see net.time4j.format.FormatPatternProvider#getIntervalPattern(Locale) */ public static MomentInterval parse( String text, ChronoParser<Moment> parser ) throws ParseException { return parse(text, parser, IsoInterval.getIntervalPattern(parser)); } /** * <p>Interpretes given text as interval using given interval pattern. </p> * * <p>About usage see also {@link DateInterval#parse(String, ChronoParser, String)}. </p> * * @param text text to be parsed * @param parser format object for parsing start and end components * @param intervalPattern interval pattern containing placeholders {0} and {1} (for start and end) * @return parsed interval * @throws IndexOutOfBoundsException if given text is empty * @throws ParseException if the text is not parseable * @since 3.9/4.6 */ /*[deutsch] * <p>Interpretiert den angegebenen Text als Intervall mit Hilfe des angegebenen * Intervallmusters. </p> * * <p>Zur Verwendung siehe auch: {@link DateInterval#parse(String, ChronoParser, String)}. </p> * * @param text text to be parsed * @param parser format object for parsing start and end components * @param intervalPattern interval pattern containing placeholders {0} and {1} (for start and end) * @return parsed interval * @throws IndexOutOfBoundsException if given text is empty * @throws ParseException if the text is not parseable * @since 3.9/4.6 */ public static MomentInterval parse( String text, ChronoParser<Moment> parser, String intervalPattern ) throws ParseException { return IntervalParser.parsePattern(text, MomentIntervalFactory.INSTANCE, parser, intervalPattern); } /** * <p>Interpretes given text as interval. </p> * * <p>This method can also accept a hyphen as alternative to solidus as separator * between start and end component unless the start component is a period. Infinity * symbols are understood. </p> * * @param text text to be parsed * @param parser format object for parsing start and end components * @param policy strategy for parsing interval boundaries * @return result * @throws ParseException if parsing does not work * @throws IndexOutOfBoundsException if the start position is at end of text or even behind * @since 4.18 */ /*[deutsch] * <p>Interpretiert den angegebenen Text als Intervall. </p> * * <p>Diese Methode kann auch einen Bindestrich als Alternative zum Schrägstrich als Trennzeichen * zwischen Start- und Endkomponente interpretieren, es sei denn, die Startkomponente ist eine Periode. * Unendlichkeitssymbole werden verstanden. </p> * * @param text text to be parsed * @param parser format object for parsing start and end components * @param policy strategy for parsing interval boundaries * @return result * @throws ParseException if parsing does not work * @throws IndexOutOfBoundsException if the start position is at end of text or even behind * @since 4.18 */ public static MomentInterval parse( CharSequence text, ChronoParser<Moment> parser, BracketPolicy policy ) throws ParseException { ParseLog plog = new ParseLog(); MomentInterval interval = IntervalParser.of( MomentIntervalFactory.INSTANCE, parser, policy ).parse(text, plog, parser.getAttributes()); if ((interval == null) || plog.isError()) { throw new ParseException(plog.getErrorMessage(), plog.getErrorIndex()); } else if ( (plog.getPosition() < text.length()) && !parser.getAttributes().get(Attributes.TRAILING_CHARACTERS, Boolean.FALSE).booleanValue() ) { throw new ParseException("Trailing characters found: " + text, plog.getPosition()); } else { return interval; } } /** * <p>Interpretes given text as interval. </p> * * <p>Similar to {@link #parse(CharSequence, ChronoParser, char, ChronoParser, BracketPolicy, ParseLog)}. * Since version v3.9/4.6 this method can also accept a hyphen as alternative to solidus as separator * between start and end component unless the start component is a period. </p> * * @param text text to be parsed * @param parser format object for parsing start and end components * @param policy strategy for parsing interval boundaries * @param status parser information (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 * @since 2.0 * @deprecated Use one of other parse methods accepting a bracket policy instead */ /*[deutsch] * <p>Interpretiert den angegebenen Text als Intervall. </p> * * <p>Ähnlich wie {@link #parse(CharSequence, ChronoParser, char, ChronoParser, BracketPolicy, ParseLog)}. * Seit der Version v3.9/4.6 kann diese Methode auch einen Bindestrich als Alternative zum Schrägstrich * als Trennzeichen zwischen Start- und Endkomponente, es sei denn, die Startkomponente ist eine Periode. </p> * * @param text text to be parsed * @param parser format object for parsing start and end components * @param policy strategy for parsing interval boundaries * @param status parser information (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 * @since 2.0 * @deprecated Use one of other parse methods accepting a bracket policy instead */ @Deprecated public static MomentInterval parse( CharSequence text, ChronoParser<Moment> parser, BracketPolicy policy, ParseLog status ) { return IntervalParser.of( MomentIntervalFactory.INSTANCE, parser, policy ).parse(text, status, parser.getAttributes()); } /** * <p>Interpretes given text as interval. </p> * * <p>This method is mainly intended for parsing technical interval formats similar to ISO-8601 * which are not localized. Infinity symbols are understood. </p> * * @param text text to be parsed * @param startFormat format object for parsing start component * @param separator char separating start and end component * @param endFormat format object for parsing end component * @param policy strategy for parsing interval boundaries * @param status parser information (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 * @since 3.9/4.6 */ /*[deutsch] * <p>Interpretiert den angegebenen Text als Intervall. </p> * * <p>Diese Methode ist vor allem für technische nicht-lokalisierte Intervallformate ähnlich * wie in ISO-8601 definiert gedacht. Unendlichkeitssymbole werden verstanden. </p> * * @param text text to be parsed * @param startFormat format object for parsing start component * @param separator char separating start and end component * @param endFormat format object for parsing end component * @param policy strategy for parsing interval boundaries * @param status parser information (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 * @since 3.9/4.6 */ public static MomentInterval parse( CharSequence text, ChronoParser<Moment> startFormat, char separator, ChronoParser<Moment> endFormat, BracketPolicy policy, ParseLog status ) { return IntervalParser.of( MomentIntervalFactory.INSTANCE, startFormat, endFormat, policy, separator ).parse(text, status, startFormat.getAttributes()); } /** * <p>Interpretes given ISO-conforming text as interval. </p> * * <p>All styles are supported, namely calendar dates, ordinal dates * and week dates, either in basic or in extended format. Mixed date * styles for start and end are not allowed however. Furthermore, one * of start or end can also be represented by a period string. If not * then the end component may exist in an abbreviated form as * documented in ISO-8601-paper leaving out higher-order elements * like the calendar year (which will be overtaken from the start * component instead). In latter case, the timezone offset of the * end component is optional, too. Infinity symbols are understood * as extension although strictly spoken ISO-8601 does not know or * specify infinite intervals. Examples for supported formats: </p> * * <pre> * System.out.println( * MomentInterval.parseISO( * "2012-01-01T14:15Z/2014-06-20T16:00Z")); * // output: [2012-01-01T14:15:00Z/2014-06-20T16:00:00Z) * * System.out.println( * MomentInterval.parseISO( * "2012-01-01T14:15Z/08-11T16:00+00:01")); * // output: [2012-01-01T14:15:00Z/2012-08-11T15:00:00Z) * * System.out.println( * MomentInterval.parseISO( * "2012-01-01T14:15Z/16:00")); * // output: [2012-01-01T14:15:00Z/2012-01-01T16:00:00Z) * * System.out.println( * MomentInterval.parseISO( * "2012-01-01T14:15Z/P2DT1H45M")); * // output: [2012-01-01T14:15:00Z/2012-01-03T16:00:00Z) * * System.out.println( * MomentInterval.parseISO( * "2015-01-01T08:45Z/-")); * // output: [2015-01-01T08:45:00Z/+∞) * </pre> * * <p>This method dynamically creates an appropriate interval format for reduced forms. * If performance is more important then a static fixed formatter might be considered. </p> * * @param text text to be parsed * @return parsed interval * @throws IndexOutOfBoundsException if given text is empty * @throws ParseException if the text is not parseable * @since 2.0 * @see BracketPolicy#SHOW_NEVER */ /*[deutsch] * <p>Interpretiert den angegebenen ISO-konformen Text als Intervall. </p> * * <p>Alle Stile werden unterstützt, nämlich Kalendardatum, * Ordinaldatum und Wochendatum, sowohl im Basisformat als auch im * erweiterten Format. Gemischte Datumsstile von Start und Ende * sind jedoch nicht erlaubt. Außerdem darf eine der beiden * Komponenten Start und Ende als P-String vorliegen. Wenn nicht, dann * darf die Endkomponente auch in einer abgekürzten Schreibweise * angegeben werden, in der weniger präzise Elemente wie das * Kalenderjahr ausgelassen und von der Startkomponente übernommen * werden. In letzterem Fall ist auch der Offset der Endkomponente * optional. Unendlichkeitssymbole werden verstanden, obwohl ISO-8601 * keine unendlichen Intervalle kennt. Beispiele für unterstützte * Formate: </p> * * <pre> * System.out.println( * MomentInterval.parseISO( * "2012-01-01T14:15Z/2014-06-20T16:00Z")); * // Ausgabe: [2012-01-01T14:15:00Z/2014-06-20T16:00:00Z) * * System.out.println( * MomentInterval.parseISO( * "2012-01-01T14:15Z/08-11T16:00+00:01")); * // Ausgabe: [2012-01-01T14:15:00Z/2012-08-11T15:00:00Z) * * System.out.println( * MomentInterval.parseISO( * "2012-01-01T14:15Z/16:00")); * // Ausgabe: [2012-01-01T14:15:00Z/2012-01-01T16:00:00Z) * * System.out.println( * MomentInterval.parseISO( * "2012-01-01T14:15Z/P2DT1H45M")); * // Ausgabe: [2012-01-01T14:15:00Z/2012-01-03T16:00:00Z) * * System.out.println( * MomentInterval.parseISO( * "2015-01-01T08:45Z/-")); * // Ausgabe: [2015-01-01T08:45:00Z/+∞) * </pre> * * <p>Intern wird das notwendige Intervallformat für reduzierte Formen dynamisch ermittelt. Ist * das Antwortzeitverhalten wichtiger, sollte einem statisch initialisierten konstanten Format der * Vorzug gegeben werden. </p> * * @param text text to be parsed * @return parsed interval * @throws IndexOutOfBoundsException if given text is empty * @throws ParseException if the text is not parseable * @since 2.0 * @see BracketPolicy#SHOW_NEVER */ public static MomentInterval parseISO(String text) throws ParseException { if (text.isEmpty()) { throw new IndexOutOfBoundsException("Empty text."); } // prescan for format analysis int start = 0; int n = Math.min(text.length(), 117); boolean sameFormat = true; int firstDate = 1; // loop starts one index position later int secondDate = 0; int timeLength = 0; boolean startsWithHyphen = (text.charAt(0) == '-'); if ((text.charAt(0) == 'P') || startsWithHyphen) { for (int i = 1; i < n; i++) { if (text.charAt(i) == '/') { if (i + 1 == n) { throw new ParseException("Missing end component.", n); } else if (startsWithHyphen) { if ((text.charAt(1) == '\u221E') || (i == 1)) { start = i + 1; } } else { start = i + 1; } break; } } } int literals = 0; int literals2 = 0; boolean ordinalStyle = false; boolean weekStyle = false; boolean weekStyle2 = false; boolean secondComponent = false; int slash = -1; for (int i = start + 1; i < n; i++) { char c = text.charAt(i); if (secondComponent) { if ( (c == 'P') || ((c == '-') && (i == n - 1)) || ((c == '+') && (i == n - 2) && (text.charAt(i + 1) == '\u221E')) ) { secondComponent = false; break; } else if ((c == 'T') || (timeLength > 0)) { timeLength++; } else { if (c == 'W') { weekStyle2 = true; } else if ((c == '-') && (i > slash + 1)) { literals2++; } secondDate++; } } else if (c == '/') { if (slash == -1) { slash = i; secondComponent = true; timeLength = 0; } else { throw new ParseException("Interval with two slashes found: " + text, i); } } else if ((c == 'T') || (timeLength > 0)) { timeLength++; } else if (c == '-') { firstDate++; literals++; } else if (c == 'W') { firstDate++; weekStyle = true; } else { firstDate++; } } if (secondComponent && (weekStyle != weekStyle2)) { throw new ParseException("Mixed date styles not allowed.", n); } char c = text.charAt(start); int componentLength = firstDate - 4; if ((c == '+') || (c == '-')) { componentLength -= 2; } if (!weekStyle) { ordinalStyle = ((literals == 1) || ((literals == 0) && (componentLength == 3))); } boolean extended = (literals > 0); boolean hasT = true; if (secondComponent) { if (timeLength == 0) { // no T in end component => no date part hasT = false; timeLength = secondDate; secondDate = 0; } sameFormat = ((firstDate == secondDate) && (literals == literals2) && hasSecondOffset(text, n)); } // prepare component parsers ChronoFormatter<Moment> startFormat = ( extended ? Iso8601Format.EXTENDED_DATE_TIME_OFFSET : Iso8601Format.BASIC_DATE_TIME_OFFSET); ChronoFormatter<Moment> endFormat = (sameFormat ? startFormat : null); // null means reduced iso format // create interval Parser parser = new Parser(startFormat, endFormat, extended, weekStyle, ordinalStyle, timeLength, hasT); return parser.parse(text); } @Override IntervalFactory<Moment, MomentInterval> getFactory() { return MomentIntervalFactory.INSTANCE; } @Override MomentInterval getContext() { return this; } private static boolean hasSecondOffset( String text, int len ) { if (text.charAt(len - 1) == 'Z') { return true; } for (int i = len - 1, n = Math.max(0, len - 6); i >= n; i--) { char test = text.charAt(i); if ((test == 'T') || (test == '/') || (test == '.') || (test == ',')) { break; } else if ((test == '+') || (test == '-')) { return true; } } return false; } /** * @serialData Uses * <a href="../../../serialized-form.html#net.time4j.range.SPX"> * a dedicated serialization form</a> as proxy. The first byte * contains the type-ID {@code 35} in the six most significant * bits. The next bytes represent the start and the end * boundary. * * Schematic algorithm: * * <pre> int header = 35; header <<= 2; out.writeByte(header); writeBoundary(getStart(), out); writeBoundary(getEnd(), out); private static void writeBoundary( Boundary<?> boundary, ObjectOutput out ) throws IOException { if (boundary.equals(Boundary.infinitePast())) { out.writeByte(1); } else if (boundary.equals(Boundary.infiniteFuture())) { out.writeByte(2); } else { out.writeByte(boundary.isOpen() ? 4 : 0); out.writeObject(boundary.getTemporal()); } } </pre> * * @return replacement object in serialization graph */ private Object writeReplace() { return new SPX(this, SPX.MOMENT_TYPE); } /** * @serialData Blocks because a serialization proxy is required. * @param in object input stream * @throws InvalidObjectException (always) */ private void readObject(ObjectInputStream in) throws IOException { throw new InvalidObjectException("Serialization proxy required."); } //~ Innere Klassen ---------------------------------------------------- private static class Parser extends IntervalParser<Moment, MomentInterval> { //~ Instanzvariablen ---------------------------------------------- private final boolean extended; private final boolean weekStyle; private final boolean ordinalStyle; private final int protectedArea; private final boolean hasT; //~ Konstruktoren ------------------------------------------------- Parser( ChronoParser<Moment> startFormat, ChronoParser<Moment> endFormat, // optional boolean extended, boolean weekStyle, boolean ordinalStyle, int protectedArea, boolean hasT ) { super(MomentIntervalFactory.INSTANCE, startFormat, endFormat, BracketPolicy.SHOW_NEVER, '/'); this.extended = extended; this.weekStyle = weekStyle; this.ordinalStyle = ordinalStyle; this.protectedArea = protectedArea; this.hasT = hasT; } //~ Methoden ------------------------------------------------------ @Override protected Moment parseReducedEnd( CharSequence text, Moment t1, ParseLog lowerLog, ParseLog upperLog, AttributeQuery attrs ) { ChronoFormatter<Moment> reducedParser = this.createEndFormat( Moment.axis().preformat(t1, attrs), lowerLog.getRawValues()); return reducedParser.parse(text, upperLog); } private ChronoFormatter<Moment> createEndFormat( ChronoDisplay defaultSupplier, ChronoEntity<?> rawData ) { ChronoFormatter.Builder<Moment> builder = ChronoFormatter.setUp(Moment.class, Locale.ROOT); ChronoElement<Integer> year = (this.weekStyle ? YEAR_OF_WEEKDATE : YEAR); if (this.extended) { int p = (this.ordinalStyle ? 3 : 5) + this.protectedArea; builder.startSection(PROTECTED_CHARACTERS, p); builder.addCustomized( year, NoopPrinter.NOOP, (this.weekStyle ? YearParser.YEAR_OF_WEEKDATE : YearParser.YEAR)); } else { int p = (this.ordinalStyle ? 3 : 4) + this.protectedArea; builder.startSection(PROTECTED_CHARACTERS, p); builder.addInteger(year, 4, 9, SignPolicy.SHOW_WHEN_BIG_NUMBER); } builder.endSection(); if (this.weekStyle) { builder.startSection(PROTECTED_CHARACTERS, 1 + this.protectedArea); builder.addCustomized( Weekmodel.ISO.weekOfYear(), NoopPrinter.NOOP, this.extended ? FixedNumParser.EXTENDED_WEEK_OF_YEAR : FixedNumParser.BASIC_WEEK_OF_YEAR); builder.endSection(); builder.startSection(PROTECTED_CHARACTERS, this.protectedArea); builder.addFixedNumerical(DAY_OF_WEEK, 1); builder.endSection(); } else if (this.ordinalStyle) { builder.startSection(PROTECTED_CHARACTERS, this.protectedArea); builder.addFixedInteger(DAY_OF_YEAR, 3); builder.endSection(); } else { builder.startSection(PROTECTED_CHARACTERS, 2 + this.protectedArea); if (this.extended) { builder.addCustomized( MONTH_AS_NUMBER, NoopPrinter.NOOP, FixedNumParser.CALENDAR_MONTH); } else { builder.addFixedInteger(MONTH_AS_NUMBER, 2); } builder.endSection(); builder.startSection(PROTECTED_CHARACTERS, this.protectedArea); builder.addFixedInteger(DAY_OF_MONTH, 2); builder.endSection(); } if (this.hasT) { builder.addLiteral('T'); } builder.addCustomized( PlainTime.COMPONENT, this.extended ? Iso8601Format.EXTENDED_WALL_TIME : Iso8601Format.BASIC_WALL_TIME); builder.startOptionalSection(); builder.addTimezoneOffset( DisplayMode.SHORT, this.extended, Collections.singletonList("Z")); builder.endSection(); for (ChronoElement<?> key : MomentIntervalFactory.INSTANCE.stdElements(rawData)) { setDefault(builder, key, defaultSupplier); } Attributes attributes = new Attributes.Builder() .setTimezone(rawData.getTimezone()) .build(); return builder.build(attributes); } // wilcard capture private static <V> void setDefault( ChronoFormatter.Builder<Moment> builder, ChronoElement<V> element, ChronoDisplay defaultSupplier ) { builder.setDefault(element, defaultSupplier.get(element)); } } }