/* * ----------------------------------------------------------------------- * Copyright © 2013-2016 Meno Hochschild, <http://www.menodata.de/> * ----------------------------------------------------------------------- * This file (IsoInterval.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.engine.AttributeQuery; import net.time4j.engine.Temporal; import net.time4j.engine.TimeLine; import net.time4j.format.Attributes; import net.time4j.format.CalendarText; import net.time4j.format.FormatPatternProvider; import net.time4j.format.expert.ChronoParser; import net.time4j.format.expert.ChronoPrinter; import java.io.IOException; import java.util.Locale; import java.util.Optional; import java.util.function.UnaryOperator; /** * <p>Represents an abstract temporal interval on a timeline for * ISO-8601-types. </p> * * <p>Note that the start of an interval is (almost) always included (with * the exception of intervals with infinite past). The end is open for * intervals with infinite future and else included for date intervals * by default and excluded for other interval types. This default setting * can be overwritten however (although potentially harmful for the * performance). </p> * * @param <T> temporal type of time points within a given interval * @param <I> generic self-referencing interval type * @author Meno Hochschild * @since 2.0 */ /*[deutsch] * <p>Repräsentiert ein abstraktes Zeitintervall auf einem * Zeitstrahl für ISO-8601-Typen. </p> * * <p>Hinweis: Der Start eines Intervalls ist außer bei Intervallen * mit unbegrenzter Vergangenheit (fast) immer inklusive. Das Ende eines * Intervalls ist bei unbegrenzter Zukunft offen, für Datumsintervalle * inklusive und sonst exklusive per Vorgabe. Diese Standardeinstellung kann * jedoch überschrieben werden (obwohl potentiell schädlich für * das Antwortzeitverhalten). </p> * * @param <T> temporal type of time points within a given interval * @param <I> generic self-referencing interval type * @author Meno Hochschild * @since 2.0 */ public abstract class IsoInterval<T extends Temporal<? super T>, I extends IsoInterval<T, I>> implements ChronoInterval<T> { //~ Instanzvariablen -------------------------------------------------- private final Boundary<T> start; private final Boundary<T> end; //~ Konstruktoren ----------------------------------------------------- /** * <p>Paket-privater Standardkonstruktor für Subklassen. </p> * * @param start untere Intervallgrenze * @param end obere Intervallgrenze * @throws IllegalArgumentException if start is after end */ IsoInterval( Boundary<T> start, Boundary<T> end ) { super(); if (Boundary.isAfter(start, end)) { // NPE-check throw new IllegalArgumentException( "Start after end: " + start + "/" + end); } else if ( end.isOpen() // NPE-check && start.isOpen() && Boundary.isSimultaneous(start, end) ) { if (start.isInfinite()) { throw new IllegalArgumentException( "Infinite boundaries must not be equal."); } else { throw new IllegalArgumentException( "Open start equal to open end: " + start + "/" + end); } } this.start = start; this.end = end; } //~ Methoden ---------------------------------------------------------- @Override public final Boundary<T> getStart() { return this.start; } @Override public final Boundary<T> getEnd() { return this.end; } /** * <p>Yields a copy of this interval with given start time. </p> * * @param temporal new start timepoint * @return changed copy of this interval * @throws IllegalArgumentException if new start is after end * @since 2.0 */ /*[deutsch] * <p>Liefert eine Kopie dieses Intervalls mit der angegebenen * Startzeit. </p> * * @param temporal new start timepoint * @return changed copy of this interval * @throws IllegalArgumentException if new start is after end * @since 2.0 */ public I withStart(T temporal) { IntervalEdge edge = this.start.getEdge(); Boundary<T> b = Boundary.of(edge, temporal); return this.getFactory().between(b, this.end); } /** * <p>Yields a copy of this interval with given end time. </p> * * @param temporal new end timepoint * @return changed copy of this interval * @throws IllegalArgumentException if new end is before start * @since 2.0 */ /*[deutsch] * <p>Liefert eine Kopie dieses Intervalls mit der angegebenen Endzeit. </p> * * @param temporal new end timepoint * @return changed copy of this interval * @throws IllegalArgumentException if new end is before start * @since 2.0 */ public I withEnd(T temporal) { IntervalEdge edge = this.end.getEdge(); Boundary<T> b = Boundary.of(edge, temporal); return this.getFactory().between(this.start, b); } /** * <p>Yields a copy of this interval with given operator applied on start time. </p> * * @param operator operator to be applied on the start * @return changed copy of this interval * @throws IllegalStateException if the start boundary is infinite * @throws IllegalArgumentException if new start is after end * @see #withEnd(UnaryOperator) * @since 4.18 */ /*[deutsch] * <p>Liefert eine Kopie dieses Intervalls mit dem angegebenen Operator angewandt * auf die Startzeit. </p> * * @param operator operator to be applied on the start * @return changed copy of this interval * @throws IllegalStateException if the start boundary is infinite * @throws IllegalArgumentException if new start is after end * @see #withEnd(UnaryOperator) * @since 4.18 */ public I withStart(UnaryOperator<T> operator) { if (this.start.isInfinite()) { throw new IllegalStateException("Operator cannot be applied on an infinite interval boundary."); } IntervalEdge edge = this.start.getEdge(); Boundary<T> b = Boundary.of(edge, operator.apply(this.start.getTemporal())); return this.getFactory().between(b, this.end); } /** * <p>Yields a copy of this interval with given operator applied on end time. </p> * * <p>Example: </p> * * <pre> * PlainDate start = PlainDate.of(2014, 2, 27); * PlainDate end = PlainDate.of(2014, 5, 20); * DateInterval interval = DateInterval.between(start, end).withEnd(PlainDate.DAY_OF_MONTH.maximized()); * System.out.println(interval); // [2014-02-27/2014-05-31] * </pre> * * @param operator operator to be applied on the end * @return changed copy of this interval * @throws IllegalStateException if the end boundary is infinite * @throws IllegalArgumentException if new end is before start * @see #withStart(UnaryOperator) * @since 4.18 */ /*[deutsch] * <p>Liefert eine Kopie dieses Intervalls mit dem angegebenen Operator angewandt * auf die Endzeit. </p> * * <p>Beispiel: </p> * * <pre> * PlainDate start = PlainDate.of(2014, 2, 27); * PlainDate end = PlainDate.of(2014, 5, 20); * DateInterval interval = DateInterval.between(start, end).withEnd(PlainDate.DAY_OF_MONTH.maximized()); * System.out.println(interval); // [2014-02-27/2014-05-31] * </pre> * * @param operator operator to be applied on the end * @return changed copy of this interval * @throws IllegalStateException if the end boundary is infinite * @throws IllegalArgumentException if new end is before start * @see #withStart(UnaryOperator) * @since 4.18 */ public I withEnd(UnaryOperator<T> operator) { if (this.end.isInfinite()) { throw new IllegalStateException("Operator cannot be applied on an infinite interval boundary."); } IntervalEdge edge = this.end.getEdge(); Boundary<T> b = Boundary.of(edge, operator.apply(this.end.getTemporal())); return this.getFactory().between(this.start, b); } /** * <p>Excludes the lower boundary from this interval. </p> * * @return changed copy of this interval excluding lower boundary * @since 4.21 */ /*[deutsch] * <p>Nimmt die untere Grenze von diesem Intervall aus. </p> * * @return changed copy of this interval excluding lower boundary * @since 4.21 */ public I withOpenStart() { if (this.start.isOpen()) { return this.getContext(); } else { Boundary<T> b = Boundary.of(IntervalEdge.OPEN, this.start.getTemporal()); return this.getFactory().between(b, this.end); } } /** * <p>Includes the lower boundary of this interval. </p> * * @return changed copy of this interval including lower boundary * @throws IllegalStateException if the start is infinite past * @since 4.21 */ /*[deutsch] * <p>Schließt die untere Grenze dieses Intervall ein. </p> * * @return changed copy of this interval including lower boundary * @throws IllegalStateException if the start is infinite past * @since 4.21 */ public I withClosedStart() { if (this.start.isInfinite()) { throw new IllegalStateException( "Infinite past cannot be included."); } else if (this.start.isClosed()) { return this.getContext(); } else { Boundary<T> b = Boundary.of(IntervalEdge.CLOSED, this.start.getTemporal()); return this.getFactory().between(b, this.end); } } /** * <p>Excludes the upper boundary from this interval. </p> * * @return changed copy of this interval excluding upper boundary * @since 2.0 */ /*[deutsch] * <p>Nimmt die obere Grenze von diesem Intervall aus. </p> * * @return changed copy of this interval excluding upper boundary * @since 2.0 */ public I withOpenEnd() { if (this.end.isOpen()) { return this.getContext(); } else { Boundary<T> b = Boundary.of(IntervalEdge.OPEN, this.end.getTemporal()); return this.getFactory().between(this.start, b); } } /** * <p>Includes the upper boundary of this interval. </p> * * @return changed copy of this interval including upper boundary * @throws IllegalStateException if the end is infinite future * @since 2.0 */ /*[deutsch] * <p>Schließt die obere Grenze dieses Intervall ein. </p> * * @return changed copy of this interval including upper boundary * @throws IllegalStateException if the end is infinite future * @since 2.0 */ public I withClosedEnd() { if (this.end.isInfinite()) { throw new IllegalStateException( "Infinite future cannot be included."); } else if (this.end.isClosed()) { return this.getContext(); } else { Boundary<T> b = Boundary.of(IntervalEdge.CLOSED, this.end.getTemporal()); return this.getFactory().between(this.start, b); } } @Override public boolean isEmpty() { if (!this.isFinite()) { return false; } T s = this.start.getTemporal(); T e = this.end.getTemporal(); if (this.start.isOpen()) { if (this.end.isClosed()) { return s.isSimultaneous(e); } s = this.getTimeLine().stepForward(s); if (s == null) { return false; } } return (this.end.isOpen() && s.isSimultaneous(e)); } @Override public boolean isBefore(T temporal) { if (temporal == null) { throw new NullPointerException(); } else if (this.end.isInfinite()) { return false; } T endA = this.end.getTemporal(); if (this.end.isOpen()) { return !endA.isAfter(temporal); } else { return endA.isBefore(temporal); } } /** * <p>Is this interval before the other one? </p> * * <p>Equivalent to the expression {@code (precedes(other) || meets(other))}. </p> * * @param other another interval whose relation to this interval is to be investigated * @return {@code true} if this interval is before the other one else {@code false} * @since 2.0 */ /*[deutsch] * <p>Liegt dieses Intervall vor dem anderen? </p> * * <p>Äquivalent zum Ausdruck {@code (precedes(other) || meets(other))}. </p> * * @param other another interval whose relation to this interval is to be investigated * @return {@code true} if this interval is before the other one else {@code false} * @since 2.0 */ @Override public boolean isBefore(ChronoInterval<T> other) { if (other.getStart().isInfinite() || this.end.isInfinite()) { return false; } T endA = this.end.getTemporal(); T startB = getClosedFiniteStart(other.getStart(), this.getTimeLine()); if (startB == null) { // exotic case: start in infinite future return true; } else if (this.end.isOpen()) { return !endA.isAfter(startB); } else { return endA.isBefore(startB); } } @Override public boolean isAfter(T temporal) { if (temporal == null) { throw new NullPointerException(); } else if (this.start.isInfinite()) { return false; } return this.getClosedFiniteStart().isAfter(temporal); } /** * <p>Is this interval after the other one? </p> * * <p>Equivalent to the expression {@code (precededBy(other) || metBy(other))}. </p> * * @param other another interval whose relation to this interval is to be investigated * @return {@code true} if this interval is after the other one else {@code false} * @since 2.0 */ /*[deutsch] * <p>Liegt dieses Intervall nach dem anderen? </p> * * <p>Äquivalent zum Ausdruck {@code (precededBy(other) || metBy(other))}. </p> * * @param other another interval whose relation to this interval is to be investigated * @return {@code true} if this interval is after the other one else {@code false} * @since 2.0 */ @Override public boolean isAfter(ChronoInterval<T> other) { return other.isBefore(this); } @Override public boolean contains(T temporal) { if (temporal == null) { throw new NullPointerException(); } boolean startCondition; if (this.start.isInfinite()) { startCondition = true; } else if (this.start.isOpen()) { startCondition = this.start.getTemporal().isBefore(temporal); } else { // closed startCondition = !this.start.getTemporal().isAfter(temporal); } if (!startCondition) { return false; // short-cut } boolean endCondition; if (this.end.isInfinite()) { endCondition = true; } else if (this.end.isOpen()) { endCondition = this.end.getTemporal().isAfter(temporal); } else { // closed endCondition = !this.end.getTemporal().isBefore(temporal); } return endCondition; } /** * <p>Does this interval contain the other one? </p> * * <p>In contrast to {@link #encloses} the interval boundaries may also be equal. </p> * * @param other another interval whose relation to this interval is to be investigated * @return {@code true} if this interval contains the other one else {@code false} * @since 2.0 */ /*[deutsch] * <p>Enthält dieses Intervall das andere Intervall? </p> * * <p>Im Unterschied zu {@link #encloses} dürfen die Grenzen der * Intervalle auch gleich sein. </p> * * @param other another interval whose relation to this interval is to be investigated * @return {@code true} if this interval contains the other one else {@code false} * @since 2.0 */ @Override public boolean contains(ChronoInterval<T> other) { if (!other.isFinite()) { return false; } T startA = this.getClosedFiniteStart(); T startB = getClosedFiniteStart(other.getStart(), this.getTimeLine()); if ((startB == null) || ((startA != null) && startA.isAfter(startB))) { return false; } T endA = this.end.getTemporal(); T endB = other.getEnd().getTemporal(); if (endA == null) { return true; } if ( other.getEnd().isOpen() && startB.isSimultaneous(endB) ) { if (this.end.isOpen()) { endA = this.getTimeLine().stepBackwards(endA); } if ((endA == null) || startB.isAfter(endA)) { return false; } } else if (this.getFactory().isCalendrical()) { if (this.end.isOpen()) { endA = this.getTimeLine().stepBackwards(endA); } if (other.getEnd().isOpen()) { endB = this.getTimeLine().stepBackwards(endB); } if ( (endA == null) || (endB == null) // dann startB = infinite_past || endA.isBefore(endB) ) { return false; } } else { if (this.end.isClosed()) { endA = this.getTimeLine().stepForward(endA); if (endA == null) { return true; } } if (other.getEnd().isClosed()) { endB = this.getTimeLine().stepForward(endB); if (endB == null) { return false; } } return !endA.isBefore(endB); } return true; } /** * <p>Changes this interval to an empty interval with the same * start anchor. </p> * * @return new empty interval with same start (anchor always inclusive) * @throws IllegalStateException if the start is infinite * @since 2.0 */ /*[deutsch] * <p>Wandelt dieses Intervall in ein leeres Intervall mit dem gleichen * Startanker um. </p> * * @return new empty interval with same start (anchor always inclusive) * @throws IllegalStateException if the start is infinite * @since 2.0 */ public I collapse() { if (this.start.isInfinite()) { throw new IllegalStateException( "An interval with infinite past cannot be collapsed."); } T t = this.getClosedFiniteStart(); Boundary<T> s = Boundary.ofClosed(t); Boundary<T> e = Boundary.ofOpen(t); return this.getFactory().between(s, e); } /** * <p>Changes this interval to an interval such that calendrical intervals become closed intervals * and other intervals become half-open. </p> * * <p>The temporal space will not be changed. Infinite boundaries also remain unchanged. </p> * * @return new interval with canonical boundaries * @throws IllegalStateException if there is no canonical form (for example for [00:00/24:00]) * @since 3.9/4.6 */ /*[deutsch] * <p>Wandelt dieses Intervall so um, daß kalendarische Intervalle geschlossen und andere * Intervalle halb-offen werden. </p> * * <p>Der temporale Zeitraum wird nicht geändert. Unendliche Intervallgrenzen bleiben * ebenfalls unverändert. </p> * * @return new interval with canonical boundaries * @throws IllegalStateException if there is no canonical form (for example for [00:00/24:00]) * @since 3.9/4.6 */ public I toCanonical() { boolean change = false; Boundary<T> s = this.start; Boundary<T> e = this.end; if (!this.start.isInfinite() && this.start.isOpen()) { T t = this.getTimeLine().stepForward(this.start.getTemporal()); if (t == null) { throw new IllegalStateException("Cannot canonicalize this interval: " + this); } s = Boundary.ofClosed(t); change = true; } if (!this.end.isInfinite()) { if (this.getFactory().isCalendrical()) { if (this.end.isOpen()) { T t = this.getTimeLine().stepBackwards(this.end.getTemporal()); if (t == null) { throw new IllegalStateException("Cannot canonicalize this interval: " + this); } e = Boundary.ofClosed(t); change = true; } } else { if (this.end.isClosed()) { T t = this.getTimeLine().stepForward(this.end.getTemporal()); if (t == null) { throw new IllegalStateException("Cannot canonicalize this interval: " + this); } e = Boundary.ofOpen(t); change = true; } } } return (change ? this.getFactory().between(s, e) : this.getContext()); } /** * <p>Compares the boundaries (start and end) and also the time axis * of this and the other interval. </p> * * <p>Note: Two intervals which are {@link #equivalentTo} to each other * are not always equal to each other. For example a half-open date interval * whose end is one day later than the end of a closed date interval with * same start is considered <i>equivalent</i>, but not <i>equal</i>. </p> * * @param obj object to be compared with this * @return {@code true} if given object is also an interval on the * same time axis and has the same boundaries as this interval * else {@code false} */ /*[deutsch] * <p>Vergleicht die Intervallgrenzen und die assoziierte Zeitachse * dieses Intervalls mit denen des angegebenen Objekts. </p> * * <p>Hinweis: Zwei Intervalle, die {@link #equivalentTo} zueinander sind, * sind nicht immer gleich im Sinne dieser Methode. Zum Beispiel ist ein * halb-offenes Datumsintervall, dessen Ende einen Tag später als * das Ende eines geschlossenen Datumsintervalls mit gleichem Start liegt, * <i>äquivalent</i>, aber nicht <i>gleich</i>. </p> * * @param obj object to be compared with this * @return {@code true} if given object is also an interval on the * same time axis and has the same boundaries as this interval * else {@code false} */ @Override public final boolean equals(Object obj) { if (this == obj) { return true; } else if (obj instanceof IsoInterval) { IsoInterval<?, ?> that = IsoInterval.class.cast(obj); return ( this.start.equals(that.start) && this.end.equals(that.end) && this.getTimeLine().equals(that.getTimeLine()) ); } else { return false; } } @Override public final int hashCode() { return (17 * this.start.hashCode() + 37 * this.end.hashCode()); } /** * <p>Yields a descriptive string using the standard output * of the method {@code toString()} of start and end. </p> * * @return String */ /*[deutsch] * <p>Liefert eine Beschreibung, die auf der Standardausgabe von * {@code toString()} angewandt auf Start und Ende beruht.. </p> * * @return String */ @Override public final String toString() { StringBuilder sb = new StringBuilder(); sb.append(this.start.isOpen() ? '(' : '['); sb.append( this.start.isInfinite() ? "-\u221E" : this.start.getTemporal()); sb.append('/'); sb.append( this.end.isInfinite() ? "+\u221E" : this.end.getTemporal()); sb.append(this.end.isOpen() ? ')' : ']'); return sb.toString(); } /** * <p>Prints the canonical form of this interval using a localized interval pattern. </p> * * <p>If given printer does not contain a reference to a locale then the interval pattern * "{0}/{1}" will be used. Note: Starting with version v2.0 and before v3.9/4.6, * this method had a different behaviour and just delegated to * {@code print(printer, BracketPolicy.SHOW_WHEN_NON_STANDARD)}. </p> * * @param printer format object for printing start and end * @return localized formatted string * @throws IllegalStateException if the canonicalization of this interval fails * @throws IllegalArgumentException if an interval boundary is not formattable * @see #toCanonical() * @see #print(ChronoPrinter, String) * @see FormatPatternProvider#getIntervalPattern(Locale) * @since 3.9/4.6 */ /*[deutsch] * <p>Formatiert die kanonische Form dieses Intervalls 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. Hinweis: Beginnend mit Version v2.0 und vor v3.9/4.6 * hatte diese Methode ein anderes Verhalten und delegierte einfach an * {@code print(printer, BracketPolicy.SHOW_WHEN_NON_STANDARD)}. </p> * * @param printer format object for printing start and end * @return localized formatted string * @throws IllegalStateException if the canonicalization of this interval fails * @throws IllegalArgumentException if an interval boundary is not formattable * @see #toCanonical() * @see #print(ChronoPrinter, String) * @see FormatPatternProvider#getIntervalPattern(Locale) * @since 3.9/4.6 */ public String print(ChronoPrinter<T> printer) { return this.print(printer, getIntervalPattern(printer)); } /** * <p>Prints the canonical form of this interval in a custom format. </p> * * <p>Example: </p> * * <pre> * DateInterval interval = DateInterval.since(PlainDate.of(2015, 1, 1)); * ChronoFormatter<PlainDate> formatter = * ChronoFormatter.ofDatePattern("MMM d, uuuu", PatternType.CLDR, Locale.US); * System.out.println(interval.print(formatter, "since {0}")); * // output: since Jan 1, 2015 * </pre> * * @param printer format object for printing start and end components * @param intervalPattern interval pattern containing placeholders {0} and {1} (for start and end) * @throws IllegalStateException if the canonicalization of this interval fails * @throws IllegalArgumentException if an interval boundary is not formattable * @return formatted string in given pattern format * @see #toCanonical() * @since 3.9/4.6 */ /*[deutsch] * <p>Formatiert die kanonische Form dieses Intervalls in einem benutzerdefinierten Format. </p> * * <p>Beispiel: </p> * * <pre> * DateInterval interval = DateInterval.since(PlainDate.of(2015, 1, 1)); * ChronoFormatter<PlainDate> formatter = * ChronoFormatter.ofDatePattern("d. MMMM uuuu", PatternType.CLDR, Locale.GERMANY); * System.out.println(interval.print(formatter, "seit {0}")); * // Ausgabe: seit 1. Januar 2015 * </pre> * * @param printer format object for printing start and end components * @param intervalPattern interval pattern containing placeholders {0} and {1} (for start and end) * @return formatted string in given pattern format * @throws IllegalStateException if the canonicalization of this interval fails * @throws IllegalArgumentException if an interval boundary is not formattable * @see #toCanonical() * @since 3.9/4.6 */ public String print( ChronoPrinter<T> printer, String intervalPattern ) { I interval = this.toCanonical(); AttributeQuery attrs = printer.getAttributes(); StringBuilder sb = new StringBuilder(32); int i = 0; int n = intervalPattern.length(); while (i < n) { char c = intervalPattern.charAt(i); if ((c == '{') && (i + 2 < n) && (intervalPattern.charAt(i + 2) == '}')) { char next = intervalPattern.charAt(i + 1); if (next == '0') { if (interval.getStart().isInfinite()) { sb.append("-\u221E"); } else { printer.print(interval.getStart().getTemporal(), sb, attrs); } i += 3; continue; } else if (next == '1') { if (interval.getEnd().isInfinite()) { sb.append("+\u221E"); } else { printer.print(interval.getEnd().getTemporal(), sb, attrs); } i += 3; continue; } } sb.append(c); i++; } return sb.toString(); } /** * <p>Prints the start and end separated by a slash using given formatter (technical format). </p> * * <p>Note: Infinite boundaries are printed either as "-∞" * or "+∞". If given bracket policy is specified as {@code SHOW_NEVER} * then the canonical form of this interval will be printed. Example for an ISO-like representation: </p> * * <pre> * DateInterval interval = DateInterval.since(PlainDate.of(2015, 1, 1)); * System.out.println( * interval.print( * Iso8601Format.BASIC_CALENDAR_DATE, * BracketPolicy.SHOW_ALWAYS)); * // output: [20150101/+∞) * </pre> * * @param printer format object for printing start and end * @param policy strategy for printing interval boundaries * @return formatted string in format {start}/{end} * @throws IllegalStateException if the canonicalization of this interval fails * @throws IllegalArgumentException if an interval boundary is not formattable * @since 2.0 */ /*[deutsch] * <p>Formatiert den Start und das Ende getrennt mit einem Schrägstrich * unter Benutzung des angegebenen Formatierers (technisches Format). </p> * * <p>Hinweis: Unendliche Intervallgrenzen werden entweder als * "-∞" oder "+∞" ausgegeben. * Wenn die angegebene {@code BracketPolicy} gleich {@code SHOW_NEVER} * ist, dann wird die kanonische Form dieses Intervalls ausgegeben. * Beispiel für eine ISO-ähnliche Darstellung: </p> * * <pre> * DateInterval interval = DateInterval.since(PlainDate.of(2015, 1, 1)); * System.out.println( * interval.print( * Iso8601Format.BASIC_CALENDAR_DATE, * BracketPolicy.SHOW_ALWAYS)); * // output: [20150101/+∞) * </pre> * * @param printer format object for printing start and end * @param policy strategy for printing interval boundaries * @return formatted string in format {start}/{end} * @throws IllegalStateException if the canonicalization of this interval fails * @throws IllegalArgumentException if an interval boundary is not formattable * @since 2.0 */ public String print( ChronoPrinter<T> printer, BracketPolicy policy ) { try { StringBuilder sb = new StringBuilder(64); this.print(printer, '/', printer, policy, InfinityStyle.SYMBOL, sb); return sb.toString(); } catch (IOException ioe) { throw new AssertionError(ioe); } } /** * <p>Prints this interval in a technical format using given formatters and separator. </p> * * <p>Note: Infinite boundaries are printed either as "-∞" or "+∞". * If given bracket policy is specified as {@code SHOW_NEVER} then the canonical form of this * interval will be printed. </p> * * @param startFormat format object for printing start component * @param separator char separating start and end component * @param endFormat format object for printing end component * @param policy strategy for printing interval boundaries * @param buffer writing buffer * @throws IllegalStateException if the canonicalization of this interval fails * @throws IllegalArgumentException if an interval boundary is not formattable * @throws IOException if writing to the buffer fails * @since 3.9/4.6 * @deprecated Use the variant with explicit infinity style argument instead */ /*[deutsch] * <p>Formatiert dieses Intervall in einem technischen Format unter Benutzung der angegebenen Formatierer * und des angegebenen Trennzeichens. </p> * * <p>Hinweis: Unendliche Intervallgrenzen werden entweder als "-∞" oder * "+∞" ausgegeben. Wenn die angegebene {@code BracketPolicy} gleich {@code SHOW_NEVER} * ist, dann wird die kanonische Form dieses Intervalls ausgegeben. </p> * * @param startFormat format object for printing start component * @param separator char separating start and end component * @param endFormat format object for printing end component * @param policy strategy for printing interval boundaries * @param buffer writing buffer * @throws IllegalStateException if the canonicalization of this interval fails * @throws IllegalArgumentException if an interval boundary is not formattable * @throws IOException if writing to the buffer fails * @since 3.9/4.6 * @deprecated Use the variant with explicit infinity style argument instead */ @Deprecated public void print( ChronoPrinter<T> startFormat, char separator, ChronoPrinter<T> endFormat, BracketPolicy policy, Appendable buffer ) throws IOException { this.print(startFormat, separator, endFormat, policy, InfinityStyle.SYMBOL, buffer); } /** * <p>Prints this interval in a technical format using given formatters and separator. </p> * * <p>Note: If given bracket policy is specified as {@code SHOW_NEVER} then the canonical form of this * interval will be printed. </p> * * @param startFormat format object for printing start component * @param separator char separating start and end component * @param endFormat format object for printing end component * @param policy strategy for printing interval boundaries * @param infinityStyle style to be used for printing infinite interval boundaries * @param buffer writing buffer * @throws IllegalStateException if the canonicalization of this interval fails * or given infinity style prevents printing infinite intervals * @throws IllegalArgumentException if an interval boundary is not formattable * @throws IOException if writing to the buffer fails * @since 3.9/4.6 */ /*[deutsch] * <p>Formatiert dieses Intervall in einem technischen Format unter Benutzung der angegebenen Formatierer * und des angegebenen Trennzeichens. </p> * * <p>Hinweis: Wenn die angegebene {@code BracketPolicy} gleich {@code SHOW_NEVER} * ist, dann wird die kanonische Form dieses Intervalls ausgegeben. </p> * * @param startFormat format object for printing start component * @param separator char separating start and end component * @param endFormat format object for printing end component * @param policy strategy for printing interval boundaries * @param infinityStyle style to be used for printing infinite interval boundaries * @param buffer writing buffer * @throws IllegalStateException if the canonicalization of this interval fails * or given infinity style prevents printing infinite intervals * @throws IllegalArgumentException if an interval boundary is not formattable * @throws IOException if writing to the buffer fails * @since 3.9/4.6 */ public void print( ChronoPrinter<T> startFormat, char separator, ChronoPrinter<T> endFormat, BracketPolicy policy, InfinityStyle infinityStyle, Appendable buffer ) throws IOException { I interval = this.getContext(); if (policy == BracketPolicy.SHOW_NEVER) { interval = this.toCanonical(); } AttributeQuery attrs = startFormat.getAttributes(); boolean showBoundaries = policy.display(this); StringBuilder sb = new StringBuilder(50); if (showBoundaries) { sb.append(interval.getStart().isOpen() ? '(' : '['); } if (interval.getStart().isInfinite()) { sb.append(infinityStyle.displayPast(startFormat, this.getFactory().getTimeLine())); } else { startFormat.print(interval.getStart().getTemporal(), sb, attrs); } sb.append(separator); if (interval.getEnd().isInfinite()) { // intentionally using start format to avoid reduced formats sb.append(infinityStyle.displayFuture(startFormat, this.getFactory().getTimeLine())); } else { endFormat.print(interval.getEnd().getTemporal(), sb, attrs); } if (showBoundaries) { sb.append(interval.getEnd().isOpen() ? ')' : ']'); } buffer.append(sb.toString()); } /** * <p>ALLEN-relation: Does this interval equal the other one taking into * account the open or closed state of the boundaries? </p> * * <p>Relation diagram: </p> * * <p><img src="doc-files/equivalent.jpg" alt="equivalent"></p> * * @param other another interval whose relation to this interval * is to be investigated * @return {@code true} if this interval is temporally equivalent to * the other one else {@code false} * @since 2.0 */ /*[deutsch] * <p>ALLEN-Relation: Ist dieses Intervall gleich dem anderen Intervall * unter Berücksichtigung des offen/geschlossen-Zustands der * Intervallgrenzen? </p> * * <p>Relation diagram: </p> * * <p><img src="doc-files/equivalent.jpg" alt="equivalent"></p> * * @param other another interval whose relation to this interval * is to be investigated * @return {@code true} if this interval is temporally equivalent to * the other one else {@code false} * @since 2.0 */ public boolean equivalentTo(I other) { if (this.getContext() == other) { return true; } T startA = this.getClosedFiniteStart(); T startB = other.getClosedFiniteStart(); if (startA == null) { if (startB != null) { return false; } } else if (startB == null) { return false; } else if (!startA.isSimultaneous(startB)) { return false; } T endA = this.end.getTemporal(); T endB = other.getEnd().getTemporal(); if (endA == null) { return (endB == null); } else if (endB == null) { return false; } if (this.getFactory().isCalendrical()) { if (this.end.isOpen()) { endA = this.getTimeLine().stepBackwards(endA); } if (other.getEnd().isOpen()) { endB = this.getTimeLine().stepBackwards(endB); } } else { if (this.end.isClosed()) { endA = this.getTimeLine().stepForward(endA); } if (other.getEnd().isClosed()) { endB = this.getTimeLine().stepForward(endB); } } if (endA == null) { return (endB == null); } else if (endB == null) { return false; } else { return endA.isSimultaneous(endB); } } /** * <p>ALLEN-relation: Does this interval precede the other one such that * there is a gap between? </p> * * <p>Relation diagram: </p> * * <p><img src="doc-files/precedes.jpg" alt="precedes"></p> * * @param other another interval whose relation to this interval * is to be investigated * @return {@code true} if this interval is before the other such * that there is a gap between else {@code false} * @since 2.0 * @see #precededBy(IsoInterval) */ /*[deutsch] * <p>ALLEN-Relation: Liegt dieses Intervall so vor dem anderen, daß * dazwischen eine Lücke existiert? </p> * * <p>Relation diagram: </p> * * <p><img src="doc-files/precedes.jpg" alt="precedes"></p> * * @param other another interval whose relation to this interval * is to be investigated * @return {@code true} if this interval is before the other such * that there is a gap between else {@code false} * @since 2.0 * @see #precededBy(IsoInterval) */ public boolean precedes(I other) { if ( other.getStart().isInfinite() || this.end.isInfinite() ) { return false; } T endA = this.end.getTemporal(); if (this.end.isClosed()) { endA = this.getTimeLine().stepForward(endA); if (endA == null) { return false; } } return endA.isBefore(other.getClosedFiniteStart()); } /** * <p>ALLEN-relation: Equivalent to {@code other.precedes(this)}. </p> * * <p>Relation diagram: </p> * * <p><img src="doc-files/precededBy.jpg" alt="precededBy"></p> * * @param other another interval whose relation to this interval * is to be investigated * @return {@code true} if this interval is after the other such * that there is a gap between else {@code false} * @since 2.0 * @see #precedes(IsoInterval) */ /*[deutsch] * <p>ALLEN-Relation: Äquivalent to {@code other.precedes(this)}. </p> * * <p>Relation diagram: </p> * * <p><img src="doc-files/precededBy.jpg" alt="precededBy"></p> * * @param other another interval whose relation to this interval * is to be investigated * @return {@code true} if this interval is after the other such * that there is a gap between else {@code false} * @since 2.0 * @see #precedes(IsoInterval) */ public boolean precededBy(I other) { return other.precedes(this.getContext()); } /** * <p>ALLEN-relation: Does this interval precede the other one such that * there is no gap between? </p> * * <p>Relation diagram: </p> * * <p><img src="doc-files/meets.jpg" alt="meets"></p> * * @param other another interval whose relation to this interval * is to be investigated * @return {@code true} if this interval is before the other such * that there is no gap between else {@code false} * @since 2.0 * @see #metBy(IsoInterval) */ /*[deutsch] * <p>ALLEN-Relation: Liegt dieses Intervall so vor dem anderen, daß * dazwischen keine Lücke existiert? </p> * * <p>Relation diagram: </p> * * <p><img src="doc-files/meets.jpg" alt="meets"></p> * * @param other another interval whose relation to this interval * is to be investigated * @return {@code true} if this interval is before the other such * that there is no gap between else {@code false} * @since 2.0 * @see #metBy(IsoInterval) */ public boolean meets(I other) { if ( other.getStart().isInfinite() || this.end.isInfinite() ) { return false; } T endA = this.end.getTemporal(); if (this.end.isClosed()) { endA = this.getTimeLine().stepForward(endA); if (endA == null) { return false; } } if (endA.isSimultaneous(other.getClosedFiniteStart())) { T startA = this.getClosedFiniteStart(); T endB = other.getEnd().getTemporal(); if ((startA == null) || (endB == null)) { return true; } else { return startA.isBefore(endB); // excludes empty.meets(empty) } } return false; } /** * <p>ALLEN-relation: Equivalent to {@code other.meets(this)}. </p> * * <p>Relation diagram: </p> * * <p><img src="doc-files/metBy.jpg" alt="metBy"></p> * * @param other another interval whose relation to this interval * is to be investigated * @return {@code true} if this interval is after the other such * that there is no gap between else {@code false} * @since 2.0 * @see #meets(IsoInterval) */ /*[deutsch] * <p>ALLEN-Relation: Äquivalent to {@code other.meets(this)}. </p> * * <p>Relation diagram: </p> * * <p><img src="doc-files/metBy.jpg" alt="metBy"></p> * * @param other another interval whose relation to this interval * is to be investigated * @return {@code true} if this interval is after the other such * that there is no gap between else {@code false} * @since 2.0 * @see #meets(IsoInterval) */ public boolean metBy(I other) { return other.meets(this.getContext()); } /** * <p>ALLEN-relation: Does this interval overlaps the other one such that * the start of this interval is still before the start of the other * one? </p> * * <p>Relation diagram: </p> * * <p><img src="doc-files/overlaps.jpg" alt="overlaps"></p> * * @param other another interval whose relation to this interval * is to be investigated * @return {@code true} if this interval overlaps the other such * that the start of this interval is still before the start * of the other one else {@code false} * @since 2.0 * @see #overlappedBy(IsoInterval) * @see #intersects(ChronoInterval) */ /*[deutsch] * <p>ALLEN-Relation: Überlappt dieses Intervall so das andere, * daß der Start dieses Intervalls noch vor dem Start des anderen * liegt? </p> * * <p>Relation diagram: </p> * * <p><img src="doc-files/overlaps.jpg" alt="overlaps"></p> * * @param other another interval whose relation to this interval * is to be investigated * @return {@code true} if this interval overlaps the other such * that the start of this interval is still before the start * of the other one else {@code false} * @since 2.0 * @see #overlappedBy(IsoInterval) * @see #intersects(ChronoInterval) */ public boolean overlaps(I other) { if ( other.getStart().isInfinite() || this.end.isInfinite() ) { return false; } T startA = this.getClosedFiniteStart(); T startB = other.getClosedFiniteStart(); if ((startA != null) && !startA.isBefore(startB)) { return false; } T endA = this.end.getTemporal(); T endB = other.getEnd().getTemporal(); if (this.getFactory().isCalendrical()) { if (this.end.isOpen()) { endA = this.getTimeLine().stepBackwards(endA); } if ((endA == null) || endA.isBefore(startB)) { return false; } else if (endB == null) { return true; } if (other.getEnd().isOpen()) { endB = this.getTimeLine().stepBackwards(endB); } } else { if (this.end.isClosed()) { endA = this.getTimeLine().stepForward(endA); if (endA == null) { return (endB == null); } } if (!endA.isAfter(startB)) { return false; } if (other.getEnd().isClosed()) { endB = this.getTimeLine().stepForward(endB); } } return ((endB == null) || endA.isBefore(endB)); } /** * <p>ALLEN-relation: Equivalent to {@code other.overlaps(this)}. </p> * * <p>Relation diagram: </p> * * <p><img src="doc-files/overlappedBy.jpg" alt="overlappedBy"></p> * * @param other another interval whose relation to this interval * is to be investigated * @return {@code true} if the other interval overlaps this such * that the start of the other one is still before the start * of this interval else {@code false} * @since 2.0 * @see #overlaps(IsoInterval) * @see #intersects(ChronoInterval) */ /*[deutsch] * <p>ALLEN-Relation: Äquivalent to {@code other.overlaps(this)}. </p> * * <p>Relation diagram: </p> * * <p><img src="doc-files/overlappedBy.jpg" alt="overlappedBy"></p> * * @param other another interval whose relation to this interval * is to be investigated * @return {@code true} if the other interval overlaps this such * that the start of the other one is still before the start * of this interval else {@code false} * @since 2.0 * @see #overlaps(IsoInterval) * @see #intersects(ChronoInterval) */ public boolean overlappedBy(I other) { return other.overlaps(this.getContext()); } /** * <p>ALLEN-relation: Does this interval finish the other one such that * both end time points are equal and the start of this interval is after * the start of the other one? </p> * * <p>Relation diagram: </p> * * <p><img src="doc-files/finishes.jpg" alt="finishes"></p> * * @param other another interval whose relation to this interval * is to be investigated * @return {@code true} if this interval has the same end point as * the other one and a later start else {@code false} * @since 2.0 * @see #finishedBy(IsoInterval) */ /*[deutsch] * <p>ALLEN-Relation: Beendet dieses Intervall so das andere, daß bei * gleichen Endzeitpunkten der Start dieses Intervalls nach dem Start des * anderen liegt? </p> * * <p>Relation diagram: </p> * * <p><img src="doc-files/finishes.jpg" alt="finishes"></p> * * @param other another interval whose relation to this interval * is to be investigated * @return {@code true} if this interval has the same end point as * the other one and a later start else {@code false} * @since 2.0 * @see #finishedBy(IsoInterval) */ public boolean finishes(I other) { if (this.start.isInfinite()) { return false; } T startA = this.getClosedFiniteStart(); T startB = other.getClosedFiniteStart(); T endA = this.end.getTemporal(); T endB = other.getEnd().getTemporal(); boolean empty = ( this.end.isOpen() && (endA != null) && startA.isSimultaneous(endA) ); if ( empty || ((startB != null) && !startB.isBefore(startA)) ) { return false; } if (endB == null) { return (endA == null); } if (endA == null) { return (endB == null); } if (this.getFactory().isCalendrical()) { if (this.end.isOpen()) { endA = this.getTimeLine().stepBackwards(endA); } if (other.getEnd().isOpen()) { endB = this.getTimeLine().stepBackwards(endB); } if ((endA == null) || (endB == null) || startA.isAfter(endB)) { return false; } } else { if (this.end.isClosed()) { endA = this.getTimeLine().stepForward(endA); } if (other.getEnd().isClosed()) { endB = this.getTimeLine().stepForward(endB); } if ((endB != null) && !startA.isBefore(endB)) { return false; } if (endA == null) { return (endB == null); } else if (endB == null) { return false; } } return endA.isSimultaneous(endB); } /** * <p>ALLEN-relation: Equivalent to {@code other.finishes(this)}. </p> * * <p>Relation diagram: </p> * * <p><img src="doc-files/finishedBy.jpg" alt="finishedBy"></p> * * @param other another interval whose relation to this interval * is to be investigated * @return {@code true} if this interval has the same end point as * the other one and an earlier start else {@code false} * @since 2.0 * @see #finishes(IsoInterval) */ /*[deutsch] * <p>ALLEN-Relation: Äquivalent to {@code other.finishes(this)}. </p> * * <p>Relation diagram: </p> * * <p><img src="doc-files/finishedBy.jpg" alt="finishedBy"></p> * * @param other another interval whose relation to this interval * is to be investigated * @return {@code true} if this interval has the same end point as * the other one and an earlier start else {@code false} * @since 2.0 * @see #finishes(IsoInterval) */ public boolean finishedBy(I other) { return other.finishes(this.getContext()); } /** * <p>ALLEN-relation: Does this interval start the other one such that both * start time points are equal and the end of this interval is before the * end of the other one? </p> * * <p>Relation diagram: </p> * * <p><img src="doc-files/starts.jpg" alt="starts"></p> * * @param other another interval whose relation to this interval * is to be investigated * @return {@code true} if this interval has the same start point as * the other one and an earlier end else {@code false} * @since 2.0 * @see #startedBy(IsoInterval) */ /*[deutsch] * <p>ALLEN-Relation: Beginnt dieses Intervall so das andere, daß * bei gleichen Beginnzeitpunkten das Ende dieses Intervalls vor dem Ende * des anderen liegt? </p> * * <p>Relation diagram: </p> * * <p><img src="doc-files/starts.jpg" alt="starts"></p> * * @param other another interval whose relation to this interval * is to be investigated * @return {@code true} if this interval has the same start point as * the other one and an earlier end else {@code false} * @since 2.0 * @see #startedBy(IsoInterval) */ public boolean starts(I other) { if (this.end.isInfinite()) { return false; } T startA = this.getClosedFiniteStart(); T startB = other.getClosedFiniteStart(); if (startB == null) { if (startA != null) { return false; } } else if (startA == null) { if (startB != null) { return false; } } else if (!startA.isSimultaneous(startB)) { return false; } T endA = this.end.getTemporal(); T endB = other.getEnd().getTemporal(); if ( this.end.isOpen() && (startA != null) && startA.isSimultaneous(endA) ) { return true; } if (endB == null) { if (this.end.isClosed()) { return true; } else if (startB == null) { return (this.getTimeLine().stepBackwards(endA) != null); } else { return endA.isAfter(startB); } } if (this.getFactory().isCalendrical()) { if (this.end.isOpen()) { endA = this.getTimeLine().stepBackwards(endA); } if (other.getEnd().isOpen()) { endB = this.getTimeLine().stepBackwards(endB); } if ((endA == null) || (endB == null) || !endA.isBefore(endB)) { return false; } } else { if (this.end.isClosed()) { endA = this.getTimeLine().stepForward(endA); if (endA == null) { return false; } } if (other.getEnd().isClosed()) { endB = this.getTimeLine().stepForward(endB); } if ((endB != null) && !endA.isBefore(endB)) { return false; } } if (this.end.isClosed()) { return true; } else if (startB == null) { return (this.getTimeLine().stepBackwards(endA) != null); } else { return endA.isAfter(startB); } } /** * <p>ALLEN-relation: Equivalent to {@code other.starts(this)}. </p> * * <p>Relation diagram: </p> * * <p><img src="doc-files/startedBy.jpg" alt="startedBy"></p> * * @param other another interval whose relation to this interval * is to be investigated * @return {@code true} if this interval has the same start point as * the other one and a later end else {@code false} * @since 2.0 * @see #starts(IsoInterval) */ /*[deutsch] * <p>ALLEN-Relation: Äquivalent to {@code other.starts(this)}. </p> * * <p>Relation diagram: </p> * * <p><img src="doc-files/startedBy.jpg" alt="startedBy"></p> * * @param other another interval whose relation to this interval * is to be investigated * @return {@code true} if this interval has the same start point as * the other one and a later end else {@code false} * @since 2.0 * @see #starts(IsoInterval) */ public boolean startedBy(I other) { return other.starts(this.getContext()); } /** * <p>ALLEN-relation: Does this interval enclose the other one such that * this start is before the start of the other one and this end is after * the end of the other one? </p> * * <p>Relation diagram: </p> * * <p><img src="doc-files/encloses.jpg" alt="encloses"></p> * * @param other another interval whose relation to this interval * is to be investigated * @return {@code true} if this interval has the earlier start point and * later end compared to the other one else {@code false} * @since 2.0 * @see #enclosedBy(IsoInterval) */ /*[deutsch] * <p>ALLEN-Relation: Umfasst dieses Intervall so das andere, daß * der Start dieses Intervalls vor dem Start des anderen und das Ende * dieses Intervalls nach dem Ende des anderen Intervalls liegt? </p> * * <p>Relation diagram: </p> * * <p><img src="doc-files/encloses.jpg" alt="encloses"></p> * * @param other another interval whose relation to this interval * is to be investigated * @return {@code true} if this interval has the earlier start point and * later end compared to the other one else {@code false} * @since 2.0 * @see #enclosedBy(IsoInterval) */ public boolean encloses(I other) { if (!other.isFinite()) { return false; } T startA = this.getClosedFiniteStart(); T startB = other.getClosedFiniteStart(); if ((startA != null) && !startA.isBefore(startB)) { return false; } T endA = this.end.getTemporal(); T endB = other.getEnd().getTemporal(); if (endA == null) { return true; } if ( other.getEnd().isOpen() && startB.isSimultaneous(endB) ) { if (this.end.isOpen()) { endA = this.getTimeLine().stepBackwards(endA); } if ((endA == null) || startB.isAfter(endA)) { return false; // if startB == endA: interval B has zero duration } } else if (this.getFactory().isCalendrical()) { if (this.end.isOpen()) { endA = this.getTimeLine().stepBackwards(endA); } if (other.getEnd().isOpen()) { endB = this.getTimeLine().stepBackwards(endB); } if ( (endA == null) || (endB == null) // dann startB = infinite_past || !endA.isAfter(endB) ) { return false; } } else { if (this.end.isClosed()) { endA = this.getTimeLine().stepForward(endA); } if (other.getEnd().isClosed()) { endB = this.getTimeLine().stepForward(endB); if (endB == null) { return false; } } if ((endA != null) && !endA.isAfter(endB)) { return false; } } return true; } /** * <p>ALLEN-relation: Equivalent to {@code other.encloses(this)}. </p> * * <p>Relation diagram: </p> * * <p><img src="doc-files/enclosedBy.jpg" alt="enclosedBy"></p> * * @param other another interval whose relation to this interval * is to be investigated * @return {@code true} if this interval has the later start point and * earlier end compared to the other one else {@code false} * @since 2.0 * @see #encloses(IsoInterval) */ /*[deutsch] * <p>ALLEN-Relation: Äquivalent to {@code other.encloses(this)}. </p> * * <p>Relation diagram: </p> * * <p><img src="doc-files/enclosedBy.jpg" alt="enclosedBy"></p> * * @param other another interval whose relation to this interval * is to be investigated * @return {@code true} if this interval has the later start point and * earlier end compared to the other one else {@code false} * @since 2.0 * @see #encloses(IsoInterval) */ public boolean enclosedBy(I other) { return other.encloses(this.getContext()); } /** * <p>Queries if this interval intersects the other one such that there is at least one common time point. </p> * * @param other another interval which might have an intersection with this interval * @return {@code true} if there is an non-empty intersection of this interval and the other one else {@code false} * @since 3.19/4.15 * @see #findIntersection(ChronoInterval) * @see #overlaps(IsoInterval) * @see #isBefore(ChronoInterval) * @see #isAfter(ChronoInterval) */ /*[deutsch] * <p>Ermittelt, ob dieses Intervall sich mit dem angegebenen Intervall so überschneidet, daß * mindestens ein gemeinsamer Zeitpunkt existiert. </p> * * @param other another interval which might have an intersection with this interval * @return {@code true} if there is an non-empty intersection of this interval and the other one else {@code false} * @since 3.19/4.15 * @see #findIntersection(ChronoInterval) * @see #overlaps(IsoInterval) * @see #isBefore(ChronoInterval) * @see #isAfter(ChronoInterval) */ public boolean intersects(ChronoInterval<T> other) { if (this.isEmpty() || other.isEmpty()) { return false; } boolean startALessEqEndB = (this.getStart().isInfinite() || other.getEnd().isInfinite()); if (!startALessEqEndB) { T startA = this.getClosedFiniteStart(); if (other.getEnd().isOpen()) { startALessEqEndB = startA.isBefore(other.getEnd().getTemporal()); } else { startALessEqEndB = !startA.isAfter(other.getEnd().getTemporal()); } } if (!startALessEqEndB) { return false; } boolean startBLessEqEndA = (other.getStart().isInfinite() || this.getEnd().isInfinite()); if (!startBLessEqEndA) { T startB = getClosedFiniteStart(other.getStart(), this.getTimeLine()); if (this.getEnd().isOpen()) { startBLessEqEndA = startB.isBefore(this.getEnd().getTemporal()); } else { startBLessEqEndA = !startB.isAfter(this.getEnd().getTemporal()); } } return startBLessEqEndA; } /** * <p>Obtains the intersection of this interval and other one if present. </p> * * @param other another interval which might have an intersection with this interval * @return a wrapper around the found intersection or an empty wrapper * @see Optional#isPresent() * @see #intersects(ChronoInterval) * @since 4.21 */ /*[deutsch] * <p>Ermittelt die Schnittmenge dieses Intervalls mit dem angegebenen anderen Intervall, falls vorhanden. </p> * * @param other another interval which might have an intersection with this interval * @return a wrapper around the found intersection or an empty wrapper * @see Optional#isPresent() * @see #intersects(ChronoInterval) * @since 4.21 */ public Optional<I> findIntersection(ChronoInterval<T> other) { if (this.isEmpty() || other.isEmpty()) { return Optional.empty(); } Boundary<T> s; Boundary<T> e; if (this.start.isInfinite()) { s = other.getStart(); } else if (other.getStart().isInfinite()) { s = this.start; } else { T t1 = this.getClosedFiniteStart(); T t2 = other.getStart().getTemporal(); if (other.getStart().isOpen()) { t2 = this.getTimeLine().stepForward(t2); } if ((t1 == null) || (t2 == null)) { return Optional.empty(); } s = (t1.isBefore(t2) ? Boundary.ofClosed(t2) : Boundary.ofClosed(t1)); } if (this.end.isInfinite()) { e = other.getEnd(); } else if (other.getEnd().isInfinite()) { e = this.end; } else if (this.getFactory().isCalendrical()) { T t1 = this.getClosedFiniteEnd(); T t2 = other.getEnd().getTemporal(); if (other.getEnd().isOpen()) { t2 = this.getTimeLine().stepBackwards(t2); } if ((t1 == null) || (t2 == null)) { return Optional.empty(); } e = (t1.isBefore(t2) ? Boundary.ofClosed(t1) : Boundary.ofClosed(t2)); } else { T t1 = this.getOpenFiniteEnd(); T t2 = other.getEnd().getTemporal(); if (other.getEnd().isClosed()) { t2 = this.getTimeLine().stepForward(t2); } if (t1 == null) { e = ((t2 == null) ? Boundary.infiniteFuture() : Boundary.ofOpen(t2)); } else if (t2 == null) { e = ((t1 == null) ? Boundary.infiniteFuture() : Boundary.ofOpen(t1)); } else { e = (t1.isBefore(t2) ? Boundary.ofOpen(t1) : Boundary.ofOpen(t2)); } } if (Boundary.isAfter(s, e)) { return Optional.empty(); } else { I intersection = this.getFactory().between(s, e); return (intersection.isEmpty() ? Optional.empty() : Optional.of(intersection)); } } /** * <p>Queries if this interval abuts the other one such that there is neither any overlap nor any gap between. </p> * * <p>Equivalent to the expression {@code this.meets(other) || this.metBy(other)}. Empty intervals never abut. </p> * * @param other another interval which might abut this interval * @return {@code true} if there is no intersection and no gap between else {@code false} * @since 3.19/4.15 * @see #meets(IsoInterval) * @see #metBy(IsoInterval) */ /*[deutsch] * <p>Ermittelt, ob dieses Intervall das angegebene Intervall so berührt, daß * weder eine Überlappung noch eine Lücke dazwischen existieren. </p> * * <p>Äquivalent zum Ausdruck {@code this.meets(other) || this.metBy(other)}. Leere Intervalle * berühren sich nie. </p> * * @param other another interval which might abut this interval * @return {@code true} if there is no intersection and no gap between else {@code false} * @since 3.19/4.15 * @see #meets(IsoInterval) * @see #metBy(IsoInterval) */ public boolean abuts(ChronoInterval<T> other) { if (this.isEmpty() || other.isEmpty()) { return false; } T startA = this.getClosedFiniteStart(); T startB = getClosedFiniteStart(other.getStart(), this.getTimeLine()); T endA = this.getOpenFiniteEnd(); T endB = getOpenFiniteEnd(other.getEnd(), this.getTimeLine()); if ((endA == null) || (startB == null)) { return ((startA != null) && (endB != null) && startA.isSimultaneous(endB)); } else if ((startA == null) || (endB == null)) { return endA.isSimultaneous(startB); } return (endA.isSimultaneous(startB) ^ startA.isSimultaneous(endB)); } /** * <p>Liefert die zugehörige Fabrik. </p> * * @return IntervalFactory */ abstract IntervalFactory<T, I> getFactory(); /** * <p>Liefert die Rechenbasis zur Ermittlung einer Dauer. </p> * * @return äquivalenter Zeitpunkt bei geschlossener unterer Grenze * @throws UnsupportedOperationException wenn unendlich */ T getTemporalOfClosedStart() { T temporal = this.start.getTemporal(); if (temporal == null) { throw new UnsupportedOperationException( "An infinite interval has no finite duration."); } else if (this.start.isOpen()) { return this.getTimeLine().stepForward(temporal); } else { return temporal; } } /** * <p>Liefert die Rechenbasis zur Ermittlung einer Dauer. </p> * * @return äquivalenter Zeitpunkt bei offener oberer Grenze oder * {@code null} wenn angewandt auf das geschlossene Maximum * @throws UnsupportedOperationException wenn unendlich */ T getTemporalOfOpenEnd() { T temporal = this.end.getTemporal(); if (temporal == null) { throw new UnsupportedOperationException( "An infinite interval has no finite duration."); } else if (this.end.isClosed()) { return this.getTimeLine().stepForward(temporal); } else { return temporal; } } /** * <p>Yields the best available format pattern. </p> * * @param printer chronological component printer * @return localized format pattern for intervals * @since 3.9/4.6 */ static String getIntervalPattern(ChronoPrinter<?> printer) { AttributeQuery attrs = printer.getAttributes(); if (attrs.contains(Attributes.LANGUAGE)) { Locale locale = attrs.get(Attributes.LANGUAGE); return CalendarText.patternForInterval(locale); } return "{0}/{1}"; } /** * <p>Yields the best available format pattern. </p> * * @param parser chronological component parser * @return localized format pattern for intervals * @since 3.9/4.6 */ static String getIntervalPattern(ChronoParser<?> parser) { AttributeQuery attrs = parser.getAttributes(); if (attrs.contains(Attributes.LANGUAGE)) { Locale locale = attrs.get(Attributes.LANGUAGE); return CalendarText.patternForInterval(locale); } return "{0}/{1}"; } /** * <p>Liefert den Selbstbezug. </p> * * @return this instance */ abstract I getContext(); /** * <p>Liefert die Startbasis für Vergleiche. </p> * * @return äquivalenter Zeitpunkt bei geschlossener unterer Grenze */ T getClosedFiniteStart() { T temporal = this.start.getTemporal(); if ((temporal != null) && this.start.isOpen()) { return this.getTimeLine().stepForward(temporal); } else { return temporal; } } /** * <p>Liefert die Endbasis für Vergleiche. </p> * * @return äquivalenter Zeitpunkt bei geschlossener oberer Grenze oder * {@code null} wenn angewandt auf das offene Minimum */ T getClosedFiniteEnd() { T temporal = this.end.getTemporal(); if ((temporal != null) && this.end.isOpen()) { return this.getTimeLine().stepBackwards(temporal); } else { return temporal; } } /** * <p>Liefert die Endbasis für Vergleiche. </p> * * @return äquivalenter Zeitpunkt bei offener oberer Grenze oder * {@code null} wenn angewandt auf das geschlossene Maximum */ T getOpenFiniteEnd() { T temporal = this.end.getTemporal(); if ((temporal != null) && this.end.isClosed()) { return this.getTimeLine().stepForward(temporal); } else { return temporal; } } private static <T extends Temporal<? super T>> T getClosedFiniteStart( Boundary<T> start, TimeLine<T> timeLine ) { T temporal = start.getTemporal(); if ((temporal != null) && start.isOpen()) { return timeLine.stepForward(temporal); } else { return temporal; } } private static <T extends Temporal<? super T>> T getOpenFiniteEnd( Boundary<T> end, TimeLine<T> timeLine ) { T temporal = end.getTemporal(); if ((temporal != null) && end.isClosed()) { return timeLine.stepForward(temporal); } else { return temporal; } } private TimeLine<T> getTimeLine() { return this.getFactory().getTimeLine(); } }