/*
* -----------------------------------------------------------------------
* Copyright © 2013-2016 Meno Hochschild, <http://www.menodata.de/>
* -----------------------------------------------------------------------
* This file (ClockInterval.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.PlainTime;
import net.time4j.engine.TimeSpan;
import net.time4j.format.Attributes;
import net.time4j.format.expert.ChronoParser;
import net.time4j.format.expert.ChronoPrinter;
import net.time4j.format.expert.Iso8601Format;
import net.time4j.format.expert.IsoDecimalStyle;
import net.time4j.format.expert.ParseLog;
import java.io.IOException;
import java.io.InvalidObjectException;
import java.io.ObjectInputStream;
import java.io.Serializable;
import java.text.ParseException;
import java.time.LocalTime;
import java.util.Comparator;
import java.util.Locale;
import java.util.stream.IntStream;
import java.util.stream.Stream;
import static net.time4j.ClockUnit.HOURS;
import static net.time4j.ClockUnit.NANOS;
import static net.time4j.range.IntervalEdge.CLOSED;
import static net.time4j.range.IntervalEdge.OPEN;
/**
* <p>Defines a finite wall time interval on the local timeline. </p>
*
* @author Meno Hochschild
* @since 2.0
* @doctags.concurrency {immutable}
*/
/*[deutsch]
* <p>Definiert ein endliches Uhrzeitintervall auf dem lokalen Zeitstrahl. </p>
*
* @author Meno Hochschild
* @since 2.0
* @doctags.concurrency {immutable}
*/
public final class ClockInterval
extends IsoInterval<PlainTime, ClockInterval>
implements Serializable {
//~ Statische Felder/Initialisierungen --------------------------------
private static final long serialVersionUID = -6020908050362634577L;
private static final Comparator<ChronoInterval<PlainTime>> COMPARATOR =
new IntervalComparator<>(false, PlainTime.axis());
//~ Konstruktoren -----------------------------------------------------
// package-private
ClockInterval(
Boundary<PlainTime> start,
Boundary<PlainTime> end
) {
super(start, end);
if (start.isInfinite() || end.isInfinite()) {
throw new IllegalArgumentException(
"Clock (time) intervals must be finite.");
}
}
//~ Methoden ----------------------------------------------------------
/**
* <p>Defines a comparator which sorts intervals first
* by start boundary and then by length. </p>
*
* @return Comparator
* @since 2.0
*/
/*[deutsch]
* <p>Definiert ein Vergleichsobjekt, das Intervalle zuerst nach dem
* Start und dann nach der Länge sortiert. </p>
*
* @return Comparator
* @since 2.0
*/
public static Comparator<ChronoInterval<PlainTime>> comparator() {
return COMPARATOR;
}
/**
* <p>Creates a finite half-open interval between given wall times. </p>
*
* @param start time of lower boundary (inclusive)
* @param end time of upper boundary (exclusive)
* @return new time interval
* @throws IllegalArgumentException if start is after end
* @since 2.0
*/
/*[deutsch]
* <p>Erzeugt ein begrenztes halb-offenes Intervall zwischen den
* angegebenen Uhrzeiten. </p>
*
* @param start time of lower boundary (inclusive)
* @param end time of upper boundary (exclusive)
* @return new time interval
* @throws IllegalArgumentException if start is after end
* @since 2.0
*/
public static ClockInterval between(
PlainTime start,
PlainTime end
) {
return new ClockInterval(
Boundary.of(CLOSED, start),
Boundary.of(OPEN, end));
}
/**
* <p>Creates a finite half-open interval between given wall times. </p>
*
* <p>For better handling of time 24:00, it is recommended to directly use the overloaded variant with
* arguments of type {@code PlainTime}. </p>
*
* @param start time of lower boundary (inclusive)
* @param end time of upper boundary (exclusive)
* @return new time interval
* @throws IllegalArgumentException if start is after end
* @see #between(PlainTime, PlainTime)
* @since 4.11
*/
/*[deutsch]
* <p>Erzeugt ein begrenztes halb-offenes Intervall zwischen den
* angegebenen Uhrzeiten. </p>
*
* <p>Für die spezielle Uhrzeit 24:00 wird empfohlen, die überladene Variante mit Argumenten
* des Typs {@code PlainTime} zu verwenden. </p>
*
* @param start time of lower boundary (inclusive)
* @param end time of upper boundary (exclusive)
* @return new time interval
* @throws IllegalArgumentException if start is after end
* @see #between(PlainTime, PlainTime)
* @since 4.11
*/
public static ClockInterval between(
LocalTime start,
LocalTime end
) {
return ClockInterval.between(PlainTime.from(start), PlainTime.from(end));
}
/**
* <p>Creates a finite half-open interval between given start time and
* midnight at end of day (exclusive). </p>
*
* <p>Note: The special wall time 24:00 does not belong to the created
* interval. </p>
*
* @param start time of lower boundary (inclusive)
* @return new time interval
* @since 2.0
*/
/*[deutsch]
* <p>Erzeugt ein begrenztes halb-offenes Intervall zwischen der
* angegebenen Startzeit und Mitternacht zu Ende des Tages (exklusive). </p>
*
* <p>Zu beachten: Die spezielle Uhrzeit 24:00 gehört nicht zum
* erzeugten Intervall. </p>
*
* @param start time of lower boundary (inclusive)
* @return new time interval
* @since 2.0
*/
public static ClockInterval since(PlainTime start) {
return between(start, PlainTime.midnightAtEndOfDay());
}
/**
* <p>Creates a finite half-open interval between given start time and
* midnight at end of day (exclusive). </p>
*
* @param start time of lower boundary (inclusive)
* @return new time interval
* @see #since(PlainTime)
* @since 4.11
*/
/*[deutsch]
* <p>Erzeugt ein begrenztes halb-offenes Intervall zwischen der
* angegebenen Startzeit und Mitternacht zu Ende des Tages (exklusive). </p>
*
* @param start time of lower boundary (inclusive)
* @return new time interval
* @see #since(PlainTime)
* @since 4.11
*/
public static ClockInterval since(LocalTime start) {
return ClockInterval.since(PlainTime.from(start));
}
/**
* <p>Creates a finite half-open interval between midnight at start of day
* and given end time. </p>
*
* @param end time of upper boundary (exclusive)
* @return new time interval
* @since 2.0
*/
/*[deutsch]
* <p>Erzeugt ein begrenztes halb-offenes Intervall zwischen Mitternacht
* zu Beginn des Tages und der angegebenen Endzeit. </p>
*
* @param end time of upper boundary (exclusive)
* @return new time interval
* @since 2.0
*/
public static ClockInterval until(PlainTime end) {
return between(PlainTime.midnightAtStartOfDay(), end);
}
/**
* <p>Creates a finite half-open interval between midnight at start of day
* and given end time. </p>
*
* @param end time of upper boundary (exclusive)
* @return new time interval
* @see #until(PlainTime)
* @since 4.11
*/
/*[deutsch]
* <p>Erzeugt ein begrenztes halb-offenes Intervall zwischen Mitternacht
* zu Beginn des Tages und der angegebenen Endzeit. </p>
*
* @param end time of upper boundary (exclusive)
* @return new time interval
* @see #until(PlainTime)
* @since 4.11
*/
public static ClockInterval until(LocalTime end) {
return ClockInterval.until(PlainTime.from(end));
}
/**
* <p>Yields the start time point. </p>
*
* @return start time point
* @since 4.11
*/
/*[deutsch]
* <p>Liefert den Startzeitpunkt. </p>
*
* @return start time point
* @since 4.11
*/
public PlainTime getStartAsClockTime() {
return this.getStart().getTemporal();
}
/**
* <p>Yields the start time point. </p>
*
* @return start time point
* @since 4.11
*/
/*[deutsch]
* <p>Liefert den Startzeitpunkt. </p>
*
* @return start time point
* @since 4.11
*/
public LocalTime getStartAsLocalTime() {
return this.getStartAsClockTime().toTemporalAccessor();
}
/**
* <p>Yields the end time point. </p>
*
* @return end time point
* @since 4.11
*/
/*[deutsch]
* <p>Liefert den Endzeitpunkt. </p>
*
* @return end time point
* @since 4.11
*/
public PlainTime getEndAsClockTime() {
return this.getEnd().getTemporal();
}
/**
* <p>Yields the end time point. </p>
*
* <p>The end time 24:00 (midnight at end of day) will be mapped to midnight at start of next day (00:00). </p>
*
* @return end time point
* @since 4.11
*/
/*[deutsch]
* <p>Liefert den Endzeitpunkt. </p>
*
* <p>Die Zeit 24:00 (Mitternacht am Ende des Tages) wird auf Mitternacht zu Beginn des nächsten
* Tages abgebildet (00:00). </p>
*
* @return end time point
* @since 4.11
*/
public LocalTime getEndAsLocalTime() {
return this.getEndAsClockTime().toTemporalAccessor();
}
/**
* <p>Yields the length of this interval. </p>
*
* @return duration in hours, minutes, seconds and nanoseconds
* @since 2.0
*/
/*[deutsch]
* <p>Liefert die Länge dieses Intervalls. </p>
*
* @return duration in hours, minutes, seconds and nanoseconds
* @since 2.0
*/
public Duration<ClockUnit> getDuration() {
PlainTime t1 = this.getTemporalOfClosedStart();
PlainTime t2 = this.getEnd().getTemporal();
if (this.getEnd().isClosed()) {
if (t2.getHour() == 24) {
if (t1.equals(PlainTime.midnightAtStartOfDay())) {
return Duration.of(24, HOURS).plus(1, NANOS);
} else {
t1 = t1.minus(1, NANOS);
}
} else {
t2 = t2.plus(1, NANOS);
}
}
return Duration.inClockUnits().between(t1, t2);
}
/**
* <p>Moves this interval along the time axis by given units. </p>
*
* @param amount amount of units
* @param unit time unit for moving
* @return moved copy of this interval
*/
public ClockInterval move(
long amount,
ClockUnit unit
) {
if (amount == 0) {
return this;
}
Boundary<PlainTime> s;
Boundary<PlainTime> 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 ClockInterval(s, e);
}
/**
* <p>Obtains a stream iterating over every clock time which is the result of addition of given duration
* to start until the end of this interval is reached. </p>
*
* <p>The stream size is limited to {@code Integer.MAX_VALUE - 1} else an {@code ArithmeticException}
* will be thrown. </p>
*
* @param duration duration which has to be added to the start multiple times
* @throws IllegalArgumentException if the duration is not positive
* @throws IllegalStateException if this interval has no canonical form
* @return stream consisting of distinct clock times which are the result of adding the duration to the start
* @see #toCanonical()
* @see #stream(Duration, PlainTime, PlainTime)
* @since 4.18
*/
/*[deutsch]
* <p>Erzeugt einen {@code Stream}, der jeweils eine Uhrzeit als Vielfaches der Dauer angewandt auf
* den Start und bis zum Ende dieses Intervalls geht. </p>
*
* <p>Die Größe des {@code Stream} ist maximal {@code Integer.MAX_VALUE - 1}, ansonsten wird
* eine {@code ArithmeticException} geworfen. </p>
*
* @param duration duration which has to be added to the start multiple times
* @throws IllegalArgumentException if the duration is not positive
* @throws IllegalStateException if this interval has no canonical form
* @return stream consisting of distinct clock times which are the result of adding the duration to the start
* @see #toCanonical()
* @see #stream(Duration, PlainTime, PlainTime)
* @since 4.18
*/
public Stream<PlainTime> stream(Duration<ClockUnit> duration) {
ClockInterval interval = this.toCanonical();
return ClockInterval.stream(duration, interval.getStartAsClockTime(), interval.getEndAsClockTime());
}
/**
* <p>Obtains a stream iterating over every clock time which is the result of addition of given duration
* to start until the end is reached. </p>
*
* <p>This static method avoids the costs of constructing an instance of {@code ClockInterval}.
* The stream size is limited to {@code Integer.MAX_VALUE - 1} else an {@code ArithmeticException}
* will be thrown. </p>
*
* @param duration duration which has to be added to the start multiple times
* @param start start boundary - inclusive
* @param end end boundary - exclusive
* @throws IllegalArgumentException if start is after end or if the duration is not positive
* @return stream consisting of distinct clock times which are the result of adding the duration to the start
* @since 4.18
*/
/*[deutsch]
* <p>Erzeugt einen {@code Stream}, der jeweils eine Uhrzeit als Vielfaches der Dauer angewandt auf
* den Start und bis zum Ende geht. </p>
*
* <p>Diese statische Methode vermeidet die Kosten der Intervallerzeugung. Die Größe des
* {@code Stream} ist maximal {@code Integer.MAX_VALUE - 1}, ansonsten wird eine {@code ArithmeticException}
* geworfen. </p>
*
* @param duration duration which has to be added to the start multiple times
* @param start start boundary - inclusive
* @param end end boundary - exclusive
* @throws IllegalArgumentException if start is after end or if the duration is not positive
* @return stream consisting of distinct clock times which are the result of adding the duration to the start
* @since 4.18
*/
public static Stream<PlainTime> stream(
Duration<ClockUnit> duration,
PlainTime start,
PlainTime end
) {
if (!duration.isPositive()) {
throw new IllegalArgumentException("Duration must be positive: " + duration);
}
int comp = start.compareTo(end);
if (comp > 0) {
throw new IllegalArgumentException("Start after end: " + start + "/" + end);
} else if (comp == 0) {
return Stream.empty();
}
double secs = 0.0;
for (TimeSpan.Item<ClockUnit> item : duration.getTotalLength()) {
secs += item.getUnit().getLength() * item.getAmount();
}
double est; // first estimate
if (secs < 1.0) {
est = (ClockUnit.NANOS.between(start, end) / (secs * 1_000_000_000));
} else {
est = (ClockUnit.SECONDS.between(start, end) / secs);
}
if (Double.compare(est, Integer.MAX_VALUE) >= 0) {
throw new ArithmeticException();
}
int n = (int) Math.floor(est);
boolean backwards = false;
while ((n > 0) && !start.plus(duration.multipliedBy(n)).isBefore(end)) {
n--;
backwards = true;
}
int size = n + 1;
if (!backwards) {
do {
size = Math.addExact(n, 1);
n++;
} while (start.plus(duration.multipliedBy(n)).isBefore(end));
}
if (size == 1) {
return Stream.of(start); // short-cut
}
return IntStream.range(0, size).mapToObj(index -> start.plus(duration.multipliedBy(index)));
}
/**
* <p>Prints the canonical form of this interval in given basic ISO-8601 style. </p>
*
* @param decimalStyle iso-compatible decimal style
* @param precision controls the precision of output format with constant length
* @return String
* @throws IllegalStateException if there is no canonical form (for example for [00:00/24:00])
* @see #toCanonical()
* @since 4.18
*/
/*[deutsch]
* <p>Formatiert die kanonische Form dieses Intervalls im angegebenen <i>basic</i> ISO-8601-Stil. </p>
*
* @param decimalStyle iso-compatible decimal style
* @param precision controls the precision of output format with constant length
* @return String
* @throws IllegalStateException if there is no canonical form (for example for [00:00/24:00])
* @see #toCanonical()
* @since 4.18
*/
public String formatBasicISO(
IsoDecimalStyle decimalStyle,
ClockUnit precision
) {
ClockInterval interval = this.toCanonical();
StringBuilder buffer = new StringBuilder();
ChronoPrinter<PlainTime> printer = Iso8601Format.ofBasicTime(decimalStyle, precision);
printer.print(interval.getStartAsClockTime(), buffer);
buffer.append('/');
printer.print(interval.getEndAsClockTime(), buffer);
return buffer.toString();
}
/**
* <p>Prints the canonical form of this interval in given extended ISO-8601 style. </p>
*
* @param decimalStyle iso-compatible decimal style
* @param precision controls the precision of output format with constant length
* @return String
* @throws IllegalStateException if there is no canonical form (for example for [00:00/24:00])
* @see #toCanonical()
* @since 4.18
*/
/*[deutsch]
* <p>Formatiert die kanonische Form dieses Intervalls im angegebenen <i>extended</i> ISO-8601-Stil. </p>
*
* @param decimalStyle iso-compatible decimal style
* @param precision controls the precision of output format with constant length
* @return String
* @throws IllegalStateException if there is no canonical form (for example for [00:00/24:00])
* @see #toCanonical()
* @since 4.18
*/
public String formatExtendedISO(
IsoDecimalStyle decimalStyle,
ClockUnit precision
) {
ClockInterval interval = this.toCanonical();
StringBuilder buffer = new StringBuilder();
ChronoPrinter<PlainTime> printer = Iso8601Format.ofExtendedTime(decimalStyle, precision);
printer.print(interval.getStartAsClockTime(), buffer);
buffer.append('/');
printer.print(interval.getEndAsClockTime(), 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 ClockInterval parse(
String text,
ChronoParser<PlainTime> 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 ClockInterval parse(
String text,
ChronoParser<PlainTime> parser,
String intervalPattern
) throws ParseException {
return IntervalParser.parsePattern(text, ClockIntervalFactory.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. </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. </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 ClockInterval parse(
CharSequence text,
ChronoParser<PlainTime> parser,
BracketPolicy policy
) throws ParseException {
ParseLog plog = new ParseLog();
ClockInterval interval =
IntervalParser.of(
ClockIntervalFactory.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 ClockInterval parse(
CharSequence text,
ChronoParser<PlainTime> parser,
BracketPolicy policy,
ParseLog status
) {
return IntervalParser.of(
ClockIntervalFactory.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. </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. </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 ClockInterval parse(
CharSequence text,
ChronoParser<PlainTime> startFormat,
char separator,
ChronoParser<PlainTime> endFormat,
BracketPolicy policy,
ParseLog status
) {
return IntervalParser.of(
ClockIntervalFactory.INSTANCE,
startFormat,
endFormat,
policy,
separator
).parse(text, status, startFormat.getAttributes());
}
/**
* <p>Interpretes given ISO-conforming text as interval. </p>
*
* <p>Examples for supported formats: </p>
*
* <ul>
* <li>09:45/PT5H</li>
* <li>PT5H/14:45</li>
* <li>0945/PT5H</li>
* <li>PT5H/1445</li>
* <li>PT01:55:30/14:15:30</li>
* <li>04:01:30.123/24:00:00.000</li>
* <li>04:01:30,123/24:00:00,000</li>
* </ul>
*
* @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.1
* @see BracketPolicy#SHOW_NEVER
*/
/*[deutsch]
* <p>Interpretiert den angegebenen ISO-konformen Text als Intervall. </p>
*
* <p>Beispiele für unterstützte Formate: </p>
*
* <ul>
* <li>09:45/PT5H</li>
* <li>PT5H/14:45</li>
* <li>0945/PT5H</li>
* <li>PT5H/1445</li>
* <li>PT01:55:30/14:15:30</li>
* <li>04:01:30.123/24:00:00.000</li>
* <li>04:01:30,123/24:00:00,000</li>
* </ul>
*
* @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.1
* @see BracketPolicy#SHOW_NEVER
*/
public static ClockInterval parseISO(String text) throws ParseException {
if (text.isEmpty()) {
throw new IndexOutOfBoundsException("Empty text.");
}
ChronoParser<PlainTime> parser = (
(text.indexOf(':') == -1) ? Iso8601Format.BASIC_WALL_TIME : Iso8601Format.EXTENDED_WALL_TIME);
ParseLog plog = new ParseLog();
ClockInterval result =
new IntervalParser<>(
ClockIntervalFactory.INSTANCE,
parser,
parser,
BracketPolicy.SHOW_NEVER,
'/'
).parse(text, plog, parser.getAttributes());
if ((result == null) || plog.isError()) {
throw new ParseException(plog.getErrorMessage(), plog.getErrorIndex());
} else if (plog.getPosition() < text.length()) {
throw new ParseException("Trailing characters found: " + text, plog.getPosition());
} else {
return result;
}
}
@Override
IntervalFactory<PlainTime, ClockInterval> getFactory() {
return ClockIntervalFactory.INSTANCE;
}
@Override
ClockInterval getContext() {
return this;
}
/**
* @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 33} in the six most significant
* bits. The next bytes represent the start and the end
* boundary.
*
* Schematic algorithm:
*
* <pre>
int header = 33;
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.TIME_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.");
}
}