236 lines
7.5 KiB
Java
236 lines
7.5 KiB
Java
package tc.oc.commons.core.util;
|
|
|
|
import java.time.Duration;
|
|
import java.time.Instant;
|
|
import java.time.format.DateTimeParseException;
|
|
import java.time.temporal.ChronoUnit;
|
|
import java.time.temporal.TemporalAmount;
|
|
import java.time.temporal.TemporalUnit;
|
|
import java.util.Comparator;
|
|
import java.util.Date;
|
|
import java.util.Optional;
|
|
|
|
import tc.oc.time.Durations;
|
|
|
|
public class TimeUtils {
|
|
|
|
public static final Duration INF_POSITIVE = ChronoUnit.FOREVER.getDuration();
|
|
public static final Duration INF_NEGATIVE = INF_POSITIVE.negated();
|
|
public static final Instant INF_FUTURE = Instant.MAX;
|
|
public static final Instant INF_PAST = Instant.MIN;
|
|
|
|
public static boolean isFinite(Duration duration) {
|
|
return !isInfPositive(duration) && !isInfNegative(duration);
|
|
}
|
|
|
|
public static boolean isInfPositive(Duration duration) {
|
|
return INF_POSITIVE.equals(duration);
|
|
}
|
|
|
|
public static boolean isInfNegative(Duration duration) {
|
|
return INF_NEGATIVE.equals(duration);
|
|
}
|
|
|
|
public static boolean isInfFuture(Instant instant) {
|
|
return INF_FUTURE.equals(instant);
|
|
}
|
|
|
|
public static boolean isInfPast(Instant instant) {
|
|
return INF_PAST.equals(instant);
|
|
}
|
|
|
|
public static boolean isPrecise(TemporalUnit unit) {
|
|
return !unit.isDurationEstimated() || ChronoUnit.DAYS.equals(unit);
|
|
}
|
|
|
|
public static long toMicros(Duration duration) {
|
|
return Math.addExact(Math.multiplyExact(duration.getSeconds(), 1_000_000),
|
|
duration.getNano() / 1_000);
|
|
}
|
|
|
|
public static long toUnit(TemporalUnit unit, Duration duration) {
|
|
switch((ChronoUnit) unit) {
|
|
case NANOS: return duration.toNanos();
|
|
case MICROS: return toMicros(duration);
|
|
case MILLIS: return duration.toMillis();
|
|
case SECONDS: return duration.getSeconds();
|
|
}
|
|
|
|
if(unit.getDuration().getNano() == 0) {
|
|
return duration.getSeconds() / unit.getDuration().getSeconds();
|
|
}
|
|
|
|
throw new IllegalArgumentException("Unsupported sub-second unit " + unit);
|
|
}
|
|
|
|
public static long getUnitOrDefault(TemporalAmount period, TemporalUnit unit, long def) {
|
|
return period.getUnits().contains(unit) ? period.get(unit) : def;
|
|
}
|
|
|
|
public static long getUnitOrZero(TemporalAmount period, TemporalUnit unit) {
|
|
return getUnitOrDefault(period, unit, 0L);
|
|
}
|
|
|
|
public static Duration duration(Instant start, Instant end) {
|
|
if(isInfPast(start) || isInfFuture(end)) {
|
|
return INF_POSITIVE;
|
|
} else if(start.isBefore(end)) {
|
|
return Duration.between(start, end);
|
|
} else {
|
|
return Duration.ZERO;
|
|
}
|
|
}
|
|
|
|
public static Optional<Duration> positiveDuration(Instant start, Instant end) {
|
|
return Optionals.getIf(start.isBefore(end), () -> duration(start, end));
|
|
}
|
|
|
|
public static Duration durationUntil(Instant end) {
|
|
return duration(Instant.now(), end);
|
|
}
|
|
|
|
public static Duration durationSince(Instant start) {
|
|
return duration(start, Instant.now());
|
|
}
|
|
|
|
public static boolean isInfFuture(Date date) {
|
|
return date.getYear() > 8000; // Hacky, but needs to match Ruby's Time::INF_FUTURE
|
|
}
|
|
|
|
public static boolean isInfPast(Date date) {
|
|
return date.getYear() < -10000;
|
|
}
|
|
|
|
public static Instant toInstant(Date date) {
|
|
if(isInfFuture(date)) {
|
|
return INF_FUTURE;
|
|
} else if(isInfPast(date)) {
|
|
return INF_PAST;
|
|
} else {
|
|
return date.toInstant();
|
|
}
|
|
}
|
|
|
|
public static long daysRoundingUp(Duration duration) {
|
|
final long days = duration.toDays();
|
|
return duration.equals(Duration.ofDays(days)) ? days : days + 1;
|
|
}
|
|
|
|
public static long toTicks(Duration duration) {
|
|
return duration.toMillis() / 50;
|
|
}
|
|
|
|
public static Duration fromTicks(long ticks) {
|
|
return Duration.ofMillis(50 * ticks);
|
|
}
|
|
|
|
public static Duration min(Duration a, Duration b) {
|
|
return a.compareTo(b) <= 0 ? a : b;
|
|
}
|
|
|
|
public static Duration max(Duration a, Duration b) {
|
|
return a.compareTo(b) >= 0 ? a : b;
|
|
}
|
|
|
|
public static Instant min(Instant a, Instant b) {
|
|
return a.compareTo(b) <= 0 ? a : b;
|
|
}
|
|
|
|
public static Instant max(Instant a, Instant b) {
|
|
return a.compareTo(b) >= 0 ? a : b;
|
|
}
|
|
|
|
public static boolean isEqualOrBeforeNow(Instant now, Instant instant) {
|
|
return !instant.isAfter(now);
|
|
}
|
|
|
|
public static boolean isEqualOrBeforeNow(Instant instant) {
|
|
return isEqualOrBeforeNow(Instant.now(), instant);
|
|
}
|
|
|
|
public static boolean isEqualOrAfterNow(Instant now, Instant instant) {
|
|
return !instant.isBefore(now);
|
|
}
|
|
|
|
public static boolean isEqualOrAfterNow(Instant instant) {
|
|
return isEqualOrAfterNow(Instant.now(), instant);
|
|
}
|
|
|
|
public static Duration parseDuration(String text) throws DateTimeParseException {
|
|
if("oo".equals(text)) return INF_POSITIVE;
|
|
|
|
// If text looks like a plain number, try to parse it as seconds,
|
|
// but be fairly strict so we don't accidentally parse a time as
|
|
// a number.
|
|
if(text.matches("^\\s*-?[0-9]+(\\.[0-9]+)?\\s*$")) {
|
|
try {
|
|
return Duration.ofMillis((long) (1000 * Double.parseDouble(text)));
|
|
} catch(NumberFormatException ignored) {}
|
|
}
|
|
|
|
return Durations.parse(text);
|
|
}
|
|
|
|
public static Duration parseDuration(String text, Duration def) throws DateTimeParseException {
|
|
if(text == null || text.length() == 0) {
|
|
return def;
|
|
} else {
|
|
return parseDuration(text);
|
|
}
|
|
}
|
|
|
|
public static Instant plus(Instant instant, Duration add) {
|
|
if(isInfFuture(instant)) {
|
|
return INF_FUTURE;
|
|
} else if(isInfPast(instant)) {
|
|
return INF_PAST;
|
|
} else if(isInfPositive(add)) {
|
|
return INF_FUTURE;
|
|
} else if(isInfNegative(add)) {
|
|
return INF_PAST;
|
|
} else {
|
|
return instant.plus(add);
|
|
}
|
|
}
|
|
|
|
public static Instant minus(Instant instant, Duration sub) {
|
|
if(isInfFuture(instant)) {
|
|
return INF_FUTURE;
|
|
} else if(isInfPast(instant)) {
|
|
return INF_PAST;
|
|
} else if(isInfPositive(sub)) {
|
|
return INF_PAST;
|
|
} else if(isInfNegative(sub)) {
|
|
return INF_FUTURE;
|
|
} else {
|
|
return instant.minus(sub);
|
|
}
|
|
}
|
|
|
|
public static Duration multiply(Duration duration, double factor) {
|
|
final long nanosPerSecond = ChronoUnit.SECONDS.getDuration().toNanos();
|
|
final long nanos = (long) (duration.getNano() * factor);
|
|
return Duration.ofSeconds(Math.addExact((long) (duration.getSeconds() * factor), Math.floorDiv(nanos, nanosPerSecond)),
|
|
Math.floorMod(nanos, nanosPerSecond));
|
|
}
|
|
|
|
public static Duration ceilSeconds(Duration duration) {
|
|
return duration.getNano() == 0 ? duration : Duration.ofSeconds(duration.getSeconds());
|
|
}
|
|
|
|
public static int compareUnits(TemporalUnit a, TemporalUnit b) {
|
|
return a.getDuration().compareTo(b.getDuration());
|
|
}
|
|
|
|
private static final Comparator<TemporalUnit> ASC_UNITS = TimeUtils::compareUnits;
|
|
private static final Comparator<TemporalUnit> DESC_UNITS = ASC_UNITS.reversed();
|
|
|
|
public static Comparator<TemporalUnit> ascendingUnits() {
|
|
return ASC_UNITS;
|
|
}
|
|
|
|
public static Comparator<TemporalUnit> descendingUnits() {
|
|
return DESC_UNITS;
|
|
}
|
|
}
|