/*
* -----------------------------------------------------------------------
* Copyright © 2013-2016 Meno Hochschild, <http://www.menodata.de/>
* -----------------------------------------------------------------------
* This file (TimestampInterval.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.Weekmodel;
import net.time4j.engine.AttributeQuery;
import net.time4j.engine.ChronoDisplay;
import net.time4j.engine.ChronoElement;
import net.time4j.engine.ChronoEntity;
import net.time4j.engine.TimeSpan;
import net.time4j.format.Attributes;
import net.time4j.format.expert.ChronoFormatter;
import net.time4j.format.expert.ChronoParser;
import net.time4j.format.expert.ChronoPrinter;
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.GapResolver;
import net.time4j.tz.OverlapResolver;
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.LocalDateTime;
import java.util.Comparator;
import java.util.Locale;
import java.util.stream.IntStream;
import java.util.stream.Stream;
import static net.time4j.PlainDate.*;
import static net.time4j.range.IntervalEdge.CLOSED;
import static net.time4j.range.IntervalEdge.OPEN;
/**
* <p>Defines a timestamp interval on local timeline. </p>
*
* @author Meno Hochschild
* @since 2.0
* @doctags.concurrency {immutable}
*/
/*[deutsch]
* <p>Definiert ein Zeitstempelintervall auf dem lokalen Zeitstrahl. </p>
*
* @author Meno Hochschild
* @since 2.0
* @doctags.concurrency {immutable}
*/
public final class TimestampInterval
extends IsoInterval<PlainTimestamp, TimestampInterval>
implements Serializable {
//~ Statische Felder/Initialisierungen --------------------------------
private static final long serialVersionUID = -3965530927182499606L;
private static final Comparator<ChronoInterval<PlainTimestamp>> COMPARATOR =
new IntervalComparator<>(false, PlainTimestamp.axis());
//~ Konstruktoren -----------------------------------------------------
// package-private
TimestampInterval(
Boundary<PlainTimestamp> start,
Boundary<PlainTimestamp> 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<PlainTimestamp>> comparator() {
return COMPARATOR;
}
/**
* <p>Creates a finite half-open interval between given time points. </p>
*
* @param start timestamp of lower boundary (inclusive)
* @param end timestamp of upper boundary (exclusive)
* @return new timestamp 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 timestamp of lower boundary (inclusive)
* @param end timestamp of upper boundary (exclusive)
* @return new timestamp interval
* @throws IllegalArgumentException if start is after end
* @since 2.0
*/
public static TimestampInterval between(
PlainTimestamp start,
PlainTimestamp end
) {
return new TimestampInterval(
Boundary.of(CLOSED, start),
Boundary.of(OPEN, end));
}
/**
* <p>Creates a finite half-open interval between given time points. </p>
*
* @param start timestamp of lower boundary (inclusive)
* @param end timestamp of upper boundary (exclusive)
* @return new timestamp interval
* @throws IllegalArgumentException if start is after end
* @see #between(PlainTimestamp, PlainTimestamp)
* @since 4.11
*/
/*[deutsch]
* <p>Erzeugt ein begrenztes halb-offenes Intervall zwischen den
* angegebenen Zeitpunkten. </p>
*
* @param start timestamp of lower boundary (inclusive)
* @param end timestamp of upper boundary (exclusive)
* @return new timestamp interval
* @throws IllegalArgumentException if start is after end
* @see #between(PlainTimestamp, PlainTimestamp)
* @since 4.11
*/
public static TimestampInterval between(
LocalDateTime start,
LocalDateTime end
) {
return TimestampInterval.between(PlainTimestamp.from(start), PlainTimestamp.from(end));
}
/**
* <p>Creates an infinite half-open interval since given start
* timestamp. </p>
*
* @param start timestamp of lower boundary (inclusive)
* @return new timestamp interval
* @since 2.0
*/
/*[deutsch]
* <p>Erzeugt ein unbegrenztes halb-offenes Intervall ab dem angegebenen
* Startzeitpunkt. </p>
*
* @param start timestamp of lower boundary (inclusive)
* @return new timestamp interval
* @since 2.0
*/
public static TimestampInterval since(PlainTimestamp start) {
Boundary<PlainTimestamp> future = Boundary.infiniteFuture();
return new TimestampInterval(Boundary.of(CLOSED, start), future);
}
/**
* <p>Creates an infinite half-open interval since given start
* timestamp. </p>
*
* @param start timestamp of lower boundary (inclusive)
* @return new timestamp interval
* @see #since(PlainTimestamp)
* @since 4.11
*/
/*[deutsch]
* <p>Erzeugt ein unbegrenztes halb-offenes Intervall ab dem angegebenen
* Startzeitpunkt. </p>
*
* @param start timestamp of lower boundary (inclusive)
* @return new timestamp interval
* @see #since(PlainTimestamp)
* @since 4.11
*/
public static TimestampInterval since(LocalDateTime start) {
return TimestampInterval.since(PlainTimestamp.from(start));
}
/**
* <p>Creates an infinite open interval until given end timestamp. </p>
*
* @param end timestamp of upper boundary (exclusive)
* @return new timestamp interval
* @since 2.0
*/
/*[deutsch]
* <p>Erzeugt ein unbegrenztes offenes Intervall bis zum angegebenen
* Endzeitpunkt. </p>
*
* @param end timestamp of upper boundary (exclusive)
* @return new timestamp interval
* @since 2.0
*/
public static TimestampInterval until(PlainTimestamp end) {
Boundary<PlainTimestamp> past = Boundary.infinitePast();
return new TimestampInterval(past, Boundary.of(OPEN, end));
}
/**
* <p>Creates an infinite open interval until given end timestamp. </p>
*
* @param end timestamp of upper boundary (exclusive)
* @return new timestamp interval
* @see #until(PlainTimestamp)
* @since 4.11
*/
/*[deutsch]
* <p>Erzeugt ein unbegrenztes offenes Intervall bis zum angegebenen
* Endzeitpunkt. </p>
*
* @param end timestamp of upper boundary (exclusive)
* @return new timestamp interval
* @see #until(PlainTimestamp)
* @since 4.11
*/
public static TimestampInterval until(LocalDateTime end) {
return TimestampInterval.until(PlainTimestamp.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 PlainTimestamp getStartAsTimestamp() {
return this.getStart().getTemporal();
}
/**
* <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 LocalDateTime getStartAsLocalDateTime() {
PlainTimestamp tsp = this.getStartAsTimestamp();
return ((tsp == null) ? null : tsp.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 PlainTimestamp getEndAsTimestamp() {
return this.getEnd().getTemporal();
}
/**
* <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 LocalDateTime getEndAsLocalDateTime() {
PlainTimestamp tsp = this.getEndAsTimestamp();
return ((tsp == null) ? null : tsp.toTemporalAccessor());
}
/**
* <p>Combines this local timestamp interval with the timezone offset
* UTC+00:00 to a global UTC-interval. </p>
*
* @return global timestamp interval interpreted at offset UTC+00:00
* @since 2.0
* @see #at(ZonalOffset)
*/
/*[deutsch]
* <p>Kombiniert dieses lokale Zeitstempelintervall mit UTC+00:00 zu
* einem globalen UTC-Intervall. </p>
*
* @return global timestamp interval interpreted at offset UTC+00:00
* @since 2.0
* @see #at(ZonalOffset)
*/
public MomentInterval atUTC() {
return this.at(ZonalOffset.UTC);
}
/**
* <p>Combines this local timestamp interval with given timezone offset
* to a global UTC-interval. </p>
*
* @param offset timezone offset
* @return global timestamp interval interpreted at given offset
* @since 2.0
* @see #atUTC()
* @see #inTimezone(TZID)
*/
/*[deutsch]
* <p>Kombiniert dieses lokale Zeitstempelintervall mit dem angegebenen
* Zeitzonen-Offset zu einem globalen UTC-Intervall. </p>
*
* @param offset timezone offset
* @return global timestamp interval interpreted at given offset
* @since 2.0
* @see #atUTC()
* @see #inTimezone(TZID)
*/
public MomentInterval at(ZonalOffset offset) {
Boundary<Moment> b1;
Boundary<Moment> b2;
if (this.getStart().isInfinite()) {
b1 = Boundary.infinitePast();
} else {
Moment m1 = this.getStart().getTemporal().at(offset);
b1 = Boundary.of(this.getStart().getEdge(), m1);
}
if (this.getEnd().isInfinite()) {
b2 = Boundary.infiniteFuture();
} else {
Moment m2 = this.getEnd().getTemporal().at(offset);
b2 = Boundary.of(this.getEnd().getEdge(), m2);
}
return new MomentInterval(b1, b2);
}
/**
* <p>Combines this local timestamp interval with the system timezone
* to a global UTC-interval. </p>
*
* @return global timestamp interval interpreted in system timezone
* @since 2.0
* @see Timezone#ofSystem()
*/
/*[deutsch]
* <p>Kombiniert dieses lokale Zeitstempelintervall mit der System-Zeitzone
* zu einem globalen UTC-Intervall. </p>
*
* @return global timestamp interval interpreted in system timezone
* @since 2.0
* @see Timezone#ofSystem()
*/
public MomentInterval inStdTimezone() {
return this.in(SystemTimezoneHolder.get());
}
/**
* <p>Combines this local timestamp interval with given timezone
* to a global UTC-interval. </p>
*
* @param tzid timezone id
* @return global timestamp interval interpreted in given timezone
* @throws IllegalArgumentException if given timezone cannot be loaded
* @since 2.0
* @see Timezone#of(TZID)
* @see #inStdTimezone()
* @see GapResolver#NEXT_VALID_TIME
* @see OverlapResolver#EARLIER_OFFSET
*/
/*[deutsch]
* <p>Kombiniert dieses lokale Zeitstempelintervall mit der angegebenen
* Zeitzone zu einem globalen UTC-Intervall. </p>
*
* @param tzid timezone id
* @return global timestamp interval interpreted in given timezone
* @throws IllegalArgumentException if given timezone cannot be loaded
* @since 2.0
* @see Timezone#of(TZID)
* @see #inStdTimezone()
* @see GapResolver#NEXT_VALID_TIME
* @see OverlapResolver#EARLIER_OFFSET
*/
public MomentInterval inTimezone(TZID tzid) {
return this.in(Timezone.of(tzid).with(GapResolver.NEXT_VALID_TIME.and(OverlapResolver.EARLIER_OFFSET)));
}
/**
* <p>Combines this local timestamp interval with given timezone
* to a global UTC-interval. </p>
*
* @param tz timezone
* @return global timestamp intervall interpreted in given timezone
* @since 2.0
* @deprecated Will become an internal private method starting with v5.0, use {@link #inTimezone(TZID)} instead
*/
/*[deutsch]
* <p>Kombiniert dieses lokale Zeitstempelintervall mit der angegebenen
* Zeitzone zu einem globalen UTC-Intervall. </p>
*
* @param tz timezone
* @return global timestamp intervall interpreted in given timezone
* @since 2.0
* @deprecated Will become an internal private method starting with v5.0, use {@link #inTimezone(TZID)} instead
*/
@Deprecated
// TODO: make the method package-private in v5.0
public MomentInterval in(Timezone tz) {
Boundary<Moment> b1;
Boundary<Moment> b2;
if (this.getStart().isInfinite()) {
b1 = Boundary.infinitePast();
} else {
Moment m1 = this.getStart().getTemporal().in(tz);
b1 = Boundary.of(this.getStart().getEdge(), m1);
}
if (this.getEnd().isInfinite()) {
b2 = Boundary.infiniteFuture();
} else {
Moment m2 = this.getEnd().getTemporal().in(tz);
b2 = Boundary.of(this.getEnd().getEdge(), m2);
}
return new MomentInterval(b1, b2);
}
/**
* <p>Yields the length of this interval in given units. </p>
*
* @param <U> generic unit type
* @param units time units to be used in calculation
* @return duration in given units
* @throws UnsupportedOperationException if this interval is infinite
* @since 2.0
*/
/*[deutsch]
* <p>Liefert die Länge dieses Intervalls in den angegebenen
* Zeiteinheiten. </p>
*
* @param <U> generic unit type
* @param units time units to be used in calculation
* @return duration in given units
* @throws UnsupportedOperationException if this interval is infinite
* @since 2.0
*/
@SafeVarargs
public final <U extends IsoUnit> Duration<U> getDuration(U... units) {
PlainTimestamp tsp = this.getTemporalOfOpenEnd();
boolean max = (tsp == null);
if (max) { // max reached
tsp = this.getEnd().getTemporal();
}
Duration<U> result =
Duration.in(units).between(this.getTemporalOfClosedStart(), tsp);
if (max) {
for (U unit : units) {
if (unit.equals(ClockUnit.NANOS)) {
return result.plus(1, unit);
}
}
}
return result;
}
/**
* <p>Yields the length of this interval in given units and applies
* a timezone offset correction . </p>
*
* @param tz timezone
* @param units time units to be used in calculation
* @return duration in given units including a zonal correction
* @throws UnsupportedOperationException if this interval is infinite
* @since 2.0
*/
/*[deutsch]
* <p>Liefert die Länge dieses Intervalls in den angegebenen
* Zeiteinheiten und wendet eine Zeitzonenkorrektur an. </p>
*
* @param tz timezone
* @param units time units to be used in calculation
* @return duration in given units including a zonal correction
* @throws UnsupportedOperationException if this interval is infinite
* @since 2.0
*/
public Duration<IsoUnit> getDuration(
Timezone tz,
IsoUnit... units
) {
PlainTimestamp tsp = this.getTemporalOfOpenEnd();
boolean max = (tsp == null);
if (max) { // max reached
tsp = this.getEnd().getTemporal();
}
Duration<IsoUnit> result =
Duration.in(tz, units).between(
this.getTemporalOfClosedStart(),
tsp);
if (max) {
for (IsoUnit unit : units) {
if (unit.equals(ClockUnit.NANOS)) {
return result.plus(1, unit);
}
}
}
return result;
}
/**
* <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 TimestampInterval move(
long amount,
IsoUnit unit
) {
if (amount == 0) {
return this;
}
Boundary<PlainTimestamp> s;
Boundary<PlainTimestamp> 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 TimestampInterval(s, e);
}
/**
* <p>Obtains a stream iterating over every timestamp 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 is infinite or if there is no canonical form
* @return stream consisting of distinct timestamps which are the result of adding the duration to the start
* @see #toCanonical()
* @see #stream(Duration, PlainTimestamp, PlainTimestamp)
* @since 4.18
*/
/*[deutsch]
* <p>Erzeugt einen {@code Stream}, der jeweils einen Zeitstempel 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 is infinite or if there is no canonical form
* @return stream consisting of distinct timestamps which are the result of adding the duration to the start
* @see #toCanonical()
* @see #stream(Duration, PlainTimestamp, PlainTimestamp)
* @since 4.18
*/
public Stream<PlainTimestamp> stream(Duration<?> duration) {
TimestampInterval interval = this.toCanonical();
PlainTimestamp start = interval.getStartAsTimestamp();
PlainTimestamp end = interval.getEndAsTimestamp();
if ((start == null) || (end == null)) {
throw new IllegalStateException("Streaming is not supported for infinite intervals.");
}
return TimestampInterval.stream(duration, start, end);
}
/**
* <p>Obtains a stream iterating over every timestamp 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 TimestampInterval}.
* 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 timestamps which are the result of adding the duration to the start
* @since 4.18
*/
/*[deutsch]
* <p>Erzeugt einen {@code Stream}, der jeweils einen Zeitstempel 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 timestamps which are the result of adding the duration to the start
* @since 4.18
*/
public static Stream<PlainTimestamp> stream(
Duration<?> duration,
PlainTimestamp start,
PlainTimestamp 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<? extends IsoUnit> 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 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 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 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,
InfinityStyle infinityStyle
) {
TimestampInterval interval = this.toCanonical();
StringBuilder buffer = new StringBuilder(60);
ChronoPrinter<PlainTimestamp> printer = Iso8601Format.ofTimestamp(dateStyle, decimalStyle, precision);
if (interval.getStart().isInfinite()) {
buffer.append(infinityStyle.displayPast(printer, PlainTimestamp.axis()));
} else {
printer.print(interval.getStartAsTimestamp(), buffer);
}
buffer.append('/');
if (interval.getEnd().isInfinite()) {
buffer.append(infinityStyle.displayFuture(printer, PlainTimestamp.axis()));
} else {
printer.print(interval.getEndAsTimestamp(), 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. Example: </p>
*
* <pre>
* TimestampInterval interval =
* TimestampInterval.between(
* PlainTimestamp.of(2016, 2, 29, 10, 45, 53),
* PlainTimestamp.of(2016, 2, 29, 16, 30));
* System.out.println(
* interval.formatReduced(
* IsoDateStyle.EXTENDED_CALENDAR_DATE, IsoDecimalStyle.DOT, ClockUnit.MINUTES, InfinityStyle.SYMBOL));
* // Output: 2016-02-29T10:45/T16:30
* </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 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.
* Beispiel: </p>
*
* <pre>
* TimestampInterval interval =
* TimestampInterval.between(
* PlainTimestamp.of(2016, 2, 29, 10, 45, 53),
* PlainTimestamp.of(2016, 2, 29, 16, 30));
* System.out.println(
* interval.formatReduced(
* IsoDateStyle.EXTENDED_CALENDAR_DATE, IsoDecimalStyle.DOT, ClockUnit.MINUTES, InfinityStyle.SYMBOL));
* // Output: 2016-02-29T10:45/T16:30
* </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 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,
InfinityStyle infinityStyle
) {
TimestampInterval interval = this.toCanonical();
PlainTimestamp start = interval.getStartAsTimestamp();
PlainTimestamp end = interval.getEndAsTimestamp();
StringBuilder buffer = new StringBuilder(60);
ChronoPrinter<PlainTime> timePrinter = (
dateStyle.isExtended()
? Iso8601Format.ofExtendedTime(decimalStyle, precision)
: Iso8601Format.ofBasicTime(decimalStyle, precision));
ChronoPrinter<PlainTimestamp> printer = null;
if (interval.getStart().isInfinite()) {
printer = Iso8601Format.ofTimestamp(dateStyle, decimalStyle, precision);
buffer.append(infinityStyle.displayPast(printer, PlainTimestamp.axis()));
} else {
Iso8601Format.ofDate(dateStyle).print(start.getCalendarDate(), buffer);
buffer.append('T');
timePrinter.print(start.getWallTime(), buffer);
}
buffer.append('/');
if (interval.isFinite()) {
PlainDate d1 = start.getCalendarDate();
PlainDate d2 = end.getCalendarDate();
if (!d1.equals(d2)) {
DateInterval.getEndPrinter(dateStyle, d1, d2).print(d2, buffer);
}
buffer.append('T');
timePrinter.print(end.getWallTime(), buffer);
} else if (interval.getEnd().isInfinite()) {
if (printer == null) {
printer = Iso8601Format.ofTimestamp(dateStyle, decimalStyle, precision);
}
buffer.append(infinityStyle.displayFuture(printer, PlainTimestamp.axis()));
} else {
if (printer == null) {
printer = Iso8601Format.ofTimestamp(dateStyle, decimalStyle, precision);
}
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 TimestampInterval parse(
String text,
ChronoParser<PlainTimestamp> 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 TimestampInterval parse(
String text,
ChronoParser<PlainTimestamp> parser,
String intervalPattern
) throws ParseException {
return IntervalParser.parsePattern(text, TimestampIntervalFactory.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 TimestampInterval parse(
CharSequence text,
ChronoParser<PlainTimestamp> parser,
BracketPolicy policy
) throws ParseException {
ParseLog plog = new ParseLog();
TimestampInterval interval =
IntervalParser.of(
TimestampIntervalFactory.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 TimestampInterval parse(
CharSequence text,
ChronoParser<PlainTimestamp> parser,
BracketPolicy policy,
ParseLog status
) {
return IntervalParser.of(
TimestampIntervalFactory.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 TimestampInterval parse(
CharSequence text,
ChronoParser<PlainTimestamp> startFormat,
char separator,
ChronoParser<PlainTimestamp> endFormat,
BracketPolicy policy,
ParseLog status
) {
return IntervalParser.of(
TimestampIntervalFactory.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). 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(
* TimestampInterval.parseISO(
* "2012-01-01T14:15/2014-06-20T16:00"));
* // output: [2012-01-01T14:15/2014-06-20T16:00)
*
* System.out.println(
* TimestampInterval.parseISO(
* "2012-01-01T14:15/08-11T16:00"));
* // output: [2012-01-01T14:15/2012-08-11T16:00)
*
* System.out.println(
* TimestampInterval.parseISO(
* "2012-01-01T14:15/16:00"));
* // output: [2012-01-01T14:15/2012-01-01T16:00)
*
* System.out.println(
* TimestampInterval.parseISO(
* "2012-01-01T14:15/P2DT1H45M"));
* // output: [2012-01-01T14:15/2012-01-03T16:00)
*
* System.out.println(
* TimestampInterval.parseISO(
* "2015-01-01T08:45/-"));
* // output: [2015-01-01T08:45:00/+∞)
* </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. Unendlichkeitssymbole werden verstanden, obwohl ISO-8601 keine
* unendlichen Intervalle kennt. Beispiele für unterstützte Formate: </p>
*
* <pre>
* System.out.println(
* TimestampInterval.parseISO(
* "2012-01-01T14:15/2014-06-20T16:00"));
* // Ausgabe: [2012-01-01T14:15/2014-06-20T16:00)
*
* System.out.println(
* TimestampInterval.parseISO(
* "2012-01-01T14:15/08-11T16:00"));
* // Ausgabe: [2012-01-01T14:15/2012-08-11T16:00)
*
* System.out.println(
* TimestampInterval.parseISO(
* "2012-01-01T14:15/16:00"));
* // Ausgabe: [2012-01-01T14:15/2012-01-01T16:00)
*
* System.out.println(
* TimestampInterval.parseISO(
* "2012-01-01T14:15/P2DT1H45M"));
* // Ausgabe: [2012-01-01T14:15/2012-01-03T16:00)
*
* System.out.println(
* TimestampInterval.parseISO(
* "2015-01-01T08:45/-"));
* // Ausgabe: [2015-01-01T08:45:00/+∞)
* </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 TimestampInterval 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(), 107);
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));
}
// prepare component parsers
ChronoFormatter<PlainTimestamp> startFormat = (
extended ? Iso8601Format.EXTENDED_DATE_TIME : Iso8601Format.BASIC_DATE_TIME);
ChronoFormatter<PlainTimestamp> 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<PlainTimestamp, TimestampInterval> getFactory() {
return TimestampIntervalFactory.INSTANCE;
}
@Override
TimestampInterval 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 34} in the six most significant
* bits. The next bytes represent the start and the end
* boundary.
*
* Schematic algorithm:
*
* <pre>
int header = 34;
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.TIMESTAMP_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<PlainTimestamp, TimestampInterval> {
//~ Instanzvariablen ----------------------------------------------
private final boolean extended;
private final boolean weekStyle;
private final boolean ordinalStyle;
private final int protectedArea;
private final boolean hasT;
//~ Konstruktoren -------------------------------------------------
Parser(
ChronoParser<PlainTimestamp> startFormat,
ChronoParser<PlainTimestamp> endFormat, // optional
boolean extended,
boolean weekStyle,
boolean ordinalStyle,
int protectedArea,
boolean hasT
) {
super(TimestampIntervalFactory.INSTANCE, startFormat, endFormat, BracketPolicy.SHOW_NEVER, '/');
this.extended = extended;
this.weekStyle = weekStyle;
this.ordinalStyle = ordinalStyle;
this.protectedArea = protectedArea;
this.hasT = hasT;
}
//~ Methoden ------------------------------------------------------
@Override
protected PlainTimestamp parseReducedEnd(
CharSequence text,
PlainTimestamp start,
ParseLog lowerLog,
ParseLog upperLog,
AttributeQuery attrs
) {
ChronoFormatter<PlainTimestamp> reducedParser =
this.createEndFormat(
PlainTimestamp.axis().preformat(start, attrs),
lowerLog.getRawValues());
return reducedParser.parse(text, upperLog);
}
private ChronoFormatter<PlainTimestamp> createEndFormat(
ChronoDisplay defaultSupplier,
ChronoEntity<?> rawData
) {
ChronoFormatter.Builder<PlainTimestamp> builder =
ChronoFormatter.setUp(PlainTimestamp.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(Attributes.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(Attributes.PROTECTED_CHARACTERS, p);
builder.addInteger(year, 4, 9, SignPolicy.SHOW_WHEN_BIG_NUMBER);
}
builder.endSection();
if (this.weekStyle) {
builder.startSection(
Attributes.PROTECTED_CHARACTERS,
1 + this.protectedArea);
builder.addCustomized(
Weekmodel.ISO.weekOfYear(),
NoopPrinter.NOOP,
extended
? FixedNumParser.EXTENDED_WEEK_OF_YEAR
: FixedNumParser.BASIC_WEEK_OF_YEAR);
builder.endSection();
builder.startSection(Attributes.PROTECTED_CHARACTERS, this.protectedArea);
builder.addFixedNumerical(DAY_OF_WEEK, 1);
builder.endSection();
} else if (this.ordinalStyle) {
builder.startSection(Attributes.PROTECTED_CHARACTERS, this.protectedArea);
builder.addFixedInteger(DAY_OF_YEAR, 3);
builder.endSection();
} else {
builder.startSection(
Attributes.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(Attributes.PROTECTED_CHARACTERS, this.protectedArea);
builder.addFixedInteger(DAY_OF_MONTH, 2);
builder.endSection();
}
if (this.hasT) {
builder.addLiteral('T');
}
builder.addCustomized(
PlainTime.COMPONENT,
extended
? Iso8601Format.EXTENDED_WALL_TIME
: Iso8601Format.BASIC_WALL_TIME);
for (ChronoElement<?> key : TimestampIntervalFactory.INSTANCE.stdElements(rawData)) {
setDefault(builder, key, defaultSupplier);
}
return builder.build();
}
// wilcard capture
private static <V> void setDefault(
ChronoFormatter.Builder<PlainTimestamp> builder,
ChronoElement<V> element,
ChronoDisplay defaultSupplier
) {
builder.setDefault(element, defaultSupplier.get(element));
}
}
private static class SystemTimezoneHolder {
//~ Statische Felder/Initialisierungen ----------------------------
private static final Timezone SYS_TZ;
static {
if (Boolean.getBoolean("net.time4j.allow.system.tz.override")) {
SYS_TZ = null;
} else {
SYS_TZ = create();
}
}
//~ Methoden ------------------------------------------------------
static Timezone get() {
return ((SYS_TZ == null) ? create() : SYS_TZ);
}
private static Timezone create() {
return Timezone.ofSystem().with(GapResolver.NEXT_VALID_TIME.and(OverlapResolver.EARLIER_OFFSET));
}
}
}