package de.invesdwin.util.time.duration; import java.util.Arrays; import java.util.List; import javax.annotation.concurrent.GuardedBy; import javax.annotation.concurrent.ThreadSafe; import de.invesdwin.util.error.UnknownArgumentException; import de.invesdwin.util.lang.ADelegateComparator; import de.invesdwin.util.lang.Objects; import de.invesdwin.util.lang.Strings; import de.invesdwin.util.math.decimal.Decimal; import de.invesdwin.util.time.Instant; import de.invesdwin.util.time.fdate.FDate; import de.invesdwin.util.time.fdate.FTimeUnit; @ThreadSafe public class Duration extends Number implements Comparable<Object> { public static final ADelegateComparator<Duration> COMPARATOR = new ADelegateComparator<Duration>() { @Override protected Comparable<?> getCompareCriteria(final Duration e) { return e; } }; public static final Duration ZERO = new Duration(0, FTimeUnit.NANOSECONDS); public static final Duration ONE_NANOSECOND = new Duration(1, FTimeUnit.NANOSECONDS); public static final Duration ONE_MICROSECOND = new Duration(1, FTimeUnit.MICROSECONDS); public static final Duration ONE_MILLISECOND = new Duration(1, FTimeUnit.MILLISECONDS); public static final Duration ONE_SECOND = new Duration(1, FTimeUnit.SECONDS); public static final Duration ONE_MINUTE = new Duration(1, FTimeUnit.MINUTES); public static final Duration ONE_HOUR = new Duration(1, FTimeUnit.HOURS); public static final Duration ONE_DAY = new Duration(1, FTimeUnit.DAYS); public static final Duration ONE_WEEK = new Duration(1, FTimeUnit.WEEKS); public static final Duration ONE_MONTH = new Duration(1, FTimeUnit.MONTHS); public static final Duration ONE_YEAR = new Duration(1, FTimeUnit.YEARS); private static final long serialVersionUID = 1L; private final long duration; private final FTimeUnit timeUnit; @GuardedBy("none for performance") private Integer cachedHashCode; public Duration(final Instant start) { this(start, FTimeUnit.NANOSECONDS); } public Duration(final Instant start, final FTimeUnit timeUnit) { this(start, new Instant(), timeUnit); } public Duration(final Instant start, final Instant end) { this(start, end, FTimeUnit.NANOSECONDS); } public Duration(final Instant start, final Instant end, final FTimeUnit timeUnit) { this(end.longValue(timeUnit) - start.longValue(timeUnit), timeUnit); } public Duration(final Duration start, final Duration end, final FTimeUnit timeUnit) { this(end.longValue(timeUnit) - start.longValue(timeUnit), timeUnit); } public Duration(final FDate start) { this(start, new FDate(), FTimeUnit.MILLISECONDS); } public Duration(final FDate start, final FTimeUnit timeUnit) { this(start, new FDate(), timeUnit); } public Duration(final FDate start, final FDate end) { this(start, end, FTimeUnit.MILLISECONDS); } public Duration(final FDate start, final FDate end, final FTimeUnit timeUnit) { this(end.longValue(timeUnit) - start.longValue(timeUnit), timeUnit); } public Duration(final long duration, final FTimeUnit timeUnit) { this.duration = Long.valueOf(duration); if (timeUnit == null) { throw new NullPointerException("timeUnit should not be null"); } this.timeUnit = timeUnit; } /** * Creates a new duration derived from this one by multipliying with the given factor. */ public Duration multiply(final double factor) { return new Duration((long) (longValue(FTimeUnit.NANOSECONDS) * factor), FTimeUnit.NANOSECONDS); } public boolean isGreaterThan(final long duration, final FTimeUnit timeUnit) { final long comparableDuration = FTimeUnit.NANOSECONDS.convert(duration, timeUnit); return longValue(FTimeUnit.NANOSECONDS) > comparableDuration; } public boolean isGreaterThan(final Duration duration) { return isGreaterThan(duration.duration, duration.timeUnit); } public boolean isGreaterThanOrEqualTo(final Duration duration) { return isGreaterThanOrEqualTo(duration.duration, duration.timeUnit); } public boolean isGreaterThanOrEqualTo(final long duration, final FTimeUnit timeUnit) { return !isLessThan(duration, timeUnit); } public boolean isLessThan(final long duration, final FTimeUnit timeUnit) { final long comparableDuration = FTimeUnit.NANOSECONDS.convert(duration, timeUnit); return longValue(FTimeUnit.NANOSECONDS) < comparableDuration; } public boolean isLessThan(final Duration duration) { return isLessThan(duration.duration, duration.timeUnit); } public boolean isLessThanOrEqualTo(final long duration, final FTimeUnit timeUnit) { return !isGreaterThan(duration, timeUnit); } public boolean isLessThanOrEqualTo(final Duration duration) { return isLessThanOrEqualTo(duration.duration, duration.timeUnit); } public FTimeUnit getTimeUnit() { return timeUnit; } public void sleep() throws InterruptedException { timeUnit.sleep(duration); } @Override public int intValue() { return intValue(timeUnit); } public int intValue(final FTimeUnit timeUnit) { return Long.valueOf(timeUnit.convert(duration, this.timeUnit)).intValue(); } @Override public long longValue() { return longValue(timeUnit); } public long longValue(final FTimeUnit timeUnit) { return Long.valueOf(timeUnit.convert(duration, this.timeUnit)).longValue(); } @Override public float floatValue() { return floatValue(timeUnit); } public float floatValue(final FTimeUnit timeUnit) { return Long.valueOf(timeUnit.convert(duration, this.timeUnit)).floatValue(); } @Override public double doubleValue() { return doubleValue(timeUnit); } public double doubleValue(final FTimeUnit timeUnit) { return Long.valueOf(timeUnit.convert(duration, this.timeUnit)).doubleValue(); } public Decimal decimalValue() { return decimalValue(timeUnit); } public Decimal decimalValue(final FTimeUnit timeUnit) { return new Decimal(timeUnit.convert(duration, this.timeUnit)); } @Override public String toString() { return toString(timeUnit); } /** * Returns the duration in the following format: * * P[JY][MM][WW][TD][T[hH][mM][s[.f]S]] * * The precision gets cut at the end. * * @see <a href="http://de.wikipedia.org/wiki/ISO_8601">ISO_8601</a> */ //CHECKSTYLE:OFF NPath public String toString(final FTimeUnit smallestTimeUnit) { //CHECKSTYLE:ON final long nanos = Math.abs(FTimeUnit.NANOSECONDS.convert(duration, this.timeUnit)); final long nanosAsMicros = FTimeUnit.NANOSECONDS.toMicros(nanos); final long nanosAsMillis = FTimeUnit.NANOSECONDS.toMillis(nanos); final long nanosAsSeconds = FTimeUnit.NANOSECONDS.toSeconds(nanos); final long nanosAsMinutes = FTimeUnit.NANOSECONDS.toMinutes(nanos); final long nanosAsHours = FTimeUnit.NANOSECONDS.toHours(nanos); final long nanosAsDays = FTimeUnit.NANOSECONDS.toDays(nanos); final long nanosAsWeeks = FTimeUnit.NANOSECONDS.toWeeks(nanos); final long nanosAsMonths = FTimeUnit.NANOSECONDS.toMonths(nanos); final long nanosAsYears = FTimeUnit.NANOSECONDS.toYears(nanos); final StringBuilder sb = new StringBuilder(); long nanoseconds = 0; long microseconds = 0; long milliseconds = 0; long seconds = 0; switch (smallestTimeUnit) { case NANOSECONDS: nanoseconds = nanos - nanosAsMicros * FTimeUnit.NANOSECONDS_IN_MICROSECOND; if (nanoseconds > 0) { sb.insert(0, Strings.leftPad(nanoseconds, 3, "0")); sb.insert(0, "."); } case MICROSECONDS: microseconds = nanosAsMicros - nanosAsMillis * FTimeUnit.MICROSECONDS_IN_MILLISECOND; if (microseconds + nanoseconds > 0) { sb.insert(0, Strings.leftPad(microseconds, 3, "0")); sb.insert(0, "."); } case MILLISECONDS: milliseconds = nanosAsMillis - nanosAsSeconds * FTimeUnit.MILLISECONDS_IN_SECOND; if (milliseconds + microseconds + nanoseconds > 0) { sb.insert(0, Strings.leftPad(milliseconds, 3, "0")); sb.insert(0, "."); } case SECONDS: seconds = nanosAsSeconds - nanosAsMinutes * FTimeUnit.SECONDS_IN_MINUTE; if (seconds + milliseconds + microseconds + nanoseconds > 0) { sb.insert(0, seconds); sb.append("S"); } case MINUTES: final long minutes = nanosAsMinutes - nanosAsHours * FTimeUnit.MINUTES_IN_HOUR; if (minutes > 0) { sb.insert(0, "M"); sb.insert(0, minutes); } case HOURS: final long hours = nanosAsHours - nanosAsDays * FTimeUnit.HOURS_IN_DAY; if (hours > 0) { sb.insert(0, "H"); sb.insert(0, hours); } if (sb.length() > 0) { sb.insert(0, "T"); } case DAYS: final long days = nanosAsDays - nanosAsWeeks * FTimeUnit.DAYS_IN_WEEK; if (days > 0) { sb.insert(0, "D"); sb.insert(0, days); } case WEEKS: final long weeks = nanosAsWeeks - nanosAsMonths * FTimeUnit.WEEKS_IN_MONTH; if (weeks > 0) { sb.insert(0, "W"); sb.insert(0, weeks); } case MONTHS: final long months = nanosAsMonths - nanosAsYears * FTimeUnit.MONTHS_IN_YEAR; if (months > 0) { sb.insert(0, "M"); sb.insert(0, months); } case YEARS: final long years = nanosAsYears; if (years > 0) { sb.insert(0, "Y"); sb.insert(0, years); } break; default: throw UnknownArgumentException.newInstance(FTimeUnit.class, smallestTimeUnit); } if (sb.length() == 0) { sb.append("0"); } sb.insert(0, "P"); if (duration < 0 && !"P0".equals(sb.toString())) { sb.insert(0, "-"); } return sb.toString(); } /** * Creates a new duration derived from this one with the added duration in the same unit as previously. */ public Duration add(final long duration) { return add(duration, timeUnit); } /** * Creates a new duration derived from this one with the added duration in nanoseconds as timeunit. */ public Duration add(final long duration, final FTimeUnit timeUnit) { final long comparableDuration = FTimeUnit.NANOSECONDS.convert(duration, timeUnit); return new Duration(Math.addExact(this.longValue(FTimeUnit.NANOSECONDS), comparableDuration), FTimeUnit.NANOSECONDS); } public Duration subtract(final long duration, final FTimeUnit timeUnit) { final long comparableDuration = FTimeUnit.NANOSECONDS.convert(duration, timeUnit); return new Duration(Math.subtractExact(this.longValue(FTimeUnit.NANOSECONDS), comparableDuration), FTimeUnit.NANOSECONDS); } public Duration divide(final Number dividend) { final long divided = (long) (longValue(FTimeUnit.NANOSECONDS) / dividend.doubleValue()); return new Duration(divided, FTimeUnit.NANOSECONDS); } public Duration multiply(final Number multiplicant) { final long multiplied = (long) (longValue(FTimeUnit.NANOSECONDS) * multiplicant.doubleValue()); return new Duration(multiplied, FTimeUnit.NANOSECONDS); } /** * Creates a new duration derived from this one with the added duration in nanoseconds as timeunit. */ public Duration add(final Duration duration) { if (duration == null) { return this; } else { return add(duration.duration, duration.timeUnit); } } public Duration subtract(final Duration duration) { if (duration == null) { return this; } else { return subtract(duration.duration, duration.timeUnit); } } @Override public boolean equals(final Object obj) { if (obj instanceof Duration) { final Duration zObj = (Duration) obj; return zObj.longValue(FTimeUnit.NANOSECONDS) == this.longValue(FTimeUnit.NANOSECONDS); } else { return false; } } @Override public int hashCode() { if (cachedHashCode == null) { cachedHashCode = Objects.hashCode(getClass(), longValue(FTimeUnit.NANOSECONDS)); } return cachedHashCode; } public FDate subtractFrom(final FDate date) { return new FDate(date.millisValue() - longValue(FTimeUnit.MILLISECONDS)); } public FDate addTo(final FDate date) { return new FDate(date.millisValue() + longValue(FTimeUnit.MILLISECONDS)); } public Duration abs() { return new Duration(Math.abs(duration), timeUnit); } public boolean isExactMultipleOfPeriod(final Duration period) { return !isLessThan(period) && longValue(FTimeUnit.NANOSECONDS) % period.longValue(FTimeUnit.NANOSECONDS) == 0; } public double getNumMultipleOfPeriod(final Duration period) { return doubleValue(FTimeUnit.NANOSECONDS) / period.doubleValue(FTimeUnit.NANOSECONDS); } @Override public int compareTo(final Object o) { if (o instanceof Duration) { final Duration cO = (Duration) o; if (isGreaterThan(cO)) { return 1; } else if (isLessThan(cO)) { return -1; } else { return 0; } } return -1; } public static IDurationAggregate valueOf(final Duration... values) { return valueOf(Arrays.asList(values)); } public static IDurationAggregate valueOf(final List<? extends Duration> values) { if (values == null || values.size() == 0) { return DummyDurationAggregate.INSTANCE; } else { return new DurationAggregate(values); } } public Duration orHigher(final Duration other) { if (other == null) { return this; } if (compareTo(other) > 0) { return this; } else { return other; } } public Duration orLower(final Duration other) { if (other == null) { return this; } if (compareTo(other) < 0) { return this; } else { return other; } } public static Duration sum(final Duration value1, final Duration value2) { if (value1 == null) { return value2; } else { return value1.add(value2); } } public static Duration max(final Duration value1, final Duration value2) { if (value1 == null) { return value2; } else { return value1.orHigher(value2); } } public static Duration min(final Duration value1, final Duration value2) { if (value1 == null) { return value2; } else { return value1.orLower(value2); } } public boolean isZero() { return duration == 0; } public final boolean isNotZero() { return !isZero(); } /** * 0 is counted as positive as well here to make things simpler. */ public boolean isPositive() { return duration >= 0; } /** * This one excludes 0 from positive. */ public boolean isPositiveNonZero() { return isPositive() && !isZero(); } public boolean isNegative() { return !isPositive(); } public boolean isNegativeOrZero() { return !isPositiveNonZero(); } public static Duration zeroToNull(final Duration duration) { if (duration == null) { return null; } else if (duration.isZero()) { return null; } else { return duration; } } public static Duration nullToZero(final Duration duration) { if (duration == null) { return Duration.ZERO; } else { return duration; } } }