org.eclipse.ditto.base.model.common.DittoDuration Maven / Gradle / Ivy
/*
* Copyright (c) 2021 Contributors to the Eclipse Foundation
*
* See the NOTICE file(s) distributed with this work for additional
* information regarding copyright ownership.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipse.ditto.base.model.common;
import static org.eclipse.ditto.base.model.common.ConditionChecker.checkArgument;
import static org.eclipse.ditto.base.model.common.ConditionChecker.checkNotNull;
import java.text.MessageFormat;
import java.time.Duration;
import java.time.Period;
import java.time.temporal.ChronoUnit;
import java.util.Arrays;
import java.util.Objects;
import java.util.Optional;
import java.util.function.LongFunction;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;
/**
* Representation of a string based duration with a positive amount.
*
* @since 2.0.0
*/
@Immutable
public final class DittoDuration implements CharSequence {
private final long amount;
private final DittoTimeUnit dittoTimeUnit;
private DittoDuration(final long amount, final DittoTimeUnit dittoTimeUnit) {
this.amount = amount;
this.dittoTimeUnit = dittoTimeUnit;
}
/**
* Creates a DittoDuration from the passed Java Duration.
*
* @param duration the positive Java Duration to let the DittoDuration base on.
* @return the created DittoDuration.
* @throws NullPointerException if {@code duration} is {@code null}.
* @throws IllegalArgumentException if {@code duration} is negative.
*/
public static DittoDuration of(final Duration duration) {
checkNotNull(duration, "duration");
checkArgument(duration, d -> !d.isNegative(),
() -> MessageFormat.format("The duration must not be negative but was <{0}>!", duration));
return new DittoDuration(duration.toMillis(), DittoTimeUnit.MILLISECONDS);
}
/**
* Creates a DittoDuration from the passed char sequence that represents a positive amount.
* Suffixes of the duration amount are allowed to specify the time unit:
*
* - {@code "ms":} milliseconds, e. g. {@code "2000ms"} for 2000 milliseconds.
* - {@code "s":} seconds, e. g. {@code "2s"} or {@code "2"} for 2 seconds.
* - {@code "m":} minutes e. g. {@code "3m"} for 3 minutes.
*
* A string representation of a long value without suffix is interpreted as seconds.
*
* @param duration the CharSequence containing the DittoDuration representation to be parsed.
* @return the created DittoDuration.
* @throws NullPointerException if {@code duration} is {@code null}.
* @throws IllegalArgumentException if the duration char sequence does not contain a parsable {@code long} or
* the parsed duration is not negative.
*/
public static DittoDuration parseDuration(final CharSequence duration) {
return parseDuration(checkNotNull(duration, "duration"), DittoTimeUnit.values());
}
private static DittoDuration parseDuration(final CharSequence duration, final DittoTimeUnit[] dittoTimeUnits) {
DittoTimeUnit timeUnit = null;
Long durationValue = null;
int i = 0;
while (null == durationValue && i < dittoTimeUnits.length) {
timeUnit = dittoTimeUnits[i];
durationValue = parseDurationRegexBased(duration, timeUnit);
i++;
}
if (null == durationValue) {
// implicitly interpret duration as seconds if unit was omitted
timeUnit = DittoTimeUnit.SECONDS_IMPLICIT;
durationValue = parseDurationPlain(duration, timeUnit.getSuffix());
}
return new DittoDuration(durationValue, timeUnit);
}
@Nullable
private static Long parseDurationRegexBased(final CharSequence duration, final DittoTimeUnit dittoTimeUnit) {
Long result = null;
final Matcher matcher = dittoTimeUnit.getRegexMatcher(duration);
if (matcher.matches()) {
result = parseDurationPlain(matcher.group("amount"), dittoTimeUnit.getSuffix());
}
return result;
}
private static Long parseDurationPlain(final CharSequence charSequence, final CharSequence timeUnitSuffix) {
// throws NumberFormatException which is a subclass of IllegalArgumentException
final long result = Long.parseLong(charSequence.toString());
checkArgument(result, r -> 0 <= r, () -> {
final String msgPattern = "The duration must not be negative but was <{0}{1}>!";
return MessageFormat.format(msgPattern, charSequence, timeUnitSuffix);
});
return result;
}
/**
* Indicates whether this duration is zero length.
*
* @return {@code true} if this duration has a total length equal to zero.
*/
public boolean isZero() {
return 0 == amount;
}
/**
* Returns this DittoDuration as Java Duration.
*
* @return the Java Duration representation of this DittoDuration.
*/
public Duration getDuration() {
return dittoTimeUnit.getJavaDuration(amount);
}
/**
* Returns the time unit of the duration.
*
* @return the time unit.
*/
public ChronoUnit getChronoUnit() {
return dittoTimeUnit.getChronoUnit();
}
/**
* Set the duration according to a Java duration keeping the time unit.
*
* @param duration the duration.
* @return the new duration with adjusted amount.
*/
public DittoDuration setAmount(final Duration duration) {
final Duration unit = dittoTimeUnit.getChronoUnit().getDuration();
final long seconds = duration.getSeconds();
final long nanoseconds = duration.getNano();
final long unitSeconds = unit.getSeconds();
final long unitNanoseconds = unit.getNano();
final long localAmount;
if (unitSeconds != 0) {
localAmount = Math.max(1L, seconds / unitSeconds);
} else {
final long withOverflow = seconds * (1_000_000_000L / unitNanoseconds) + (nanoseconds / unitNanoseconds);
localAmount = Math.max(1L, withOverflow);
}
return new DittoDuration(localAmount, dittoTimeUnit);
}
@Override
public int length() {
return toString().length();
}
@Override
public char charAt(final int index) {
return toString().charAt(index);
}
@Override
public CharSequence subSequence(final int start, final int end) {
return toString().subSequence(start, end);
}
@Override
public String toString() {
return amount + dittoTimeUnit.getSuffix();
}
@Override
public boolean equals(@Nullable final Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
final DittoDuration that = (DittoDuration) o;
return amount == that.amount && dittoTimeUnit == that.dittoTimeUnit;
}
@Override
public int hashCode() {
return Objects.hash(amount, dittoTimeUnit);
}
/**
* Enumeration providing the supported time units of {@link DittoDuration}, together with their {@link ChronoUnit}.
*
* @since 3.5.0
*/
public enum DittoTimeUnit {
// The order matters as we expect seconds to be the main unit.
// By making it the first constant, parsing a duration from string will be accelerated.
SECONDS("s", Duration::ofSeconds, ChronoUnit.SECONDS),
SECONDS_IMPLICIT("", Duration::ofSeconds, ChronoUnit.SECONDS),
MILLISECONDS("ms", Duration::ofMillis, ChronoUnit.MILLIS),
MINUTES("m", Duration::ofMinutes, ChronoUnit.MINUTES),
HOURS("h", Duration::ofHours, ChronoUnit.HOURS),
DAYS("d", Duration::ofDays, ChronoUnit.DAYS),
WEEKS("w", weeks -> ofPeriodGreaterThanDays(Period.ofWeeks((int) weeks)), ChronoUnit.WEEKS),
MONTHS("mo", months -> ofPeriodGreaterThanDays(Period.ofMonths((int) months)), ChronoUnit.MONTHS),
YEARS("y", years -> ofPeriodGreaterThanDays(Period.ofYears((int) years)), ChronoUnit.YEARS);
private final String suffix;
private final LongFunction toJavaDuration;
private final ChronoUnit chronoUnit;
private final Pattern regexPattern;
DittoTimeUnit(final String suffix, final LongFunction toJavaDuration, final ChronoUnit chronoUnit) {
this.suffix = suffix;
this.toJavaDuration = toJavaDuration;
this.chronoUnit = chronoUnit;
regexPattern = Pattern.compile("(?[+-]?\\d++)(?" + suffix + ")");
}
/**
* Find a DittoTimeUnit option by a provided suffix string.
*
* @param suffix the suffix.
* @return the DittoTimeUnit with the given suffix string if any exists.
*/
public static Optional forSuffix(final String suffix) {
return Arrays.stream(values())
.filter(unit -> unit.getSuffix().equals(suffix))
.findAny();
}
private static Duration ofPeriodGreaterThanDays(final Period period) {
final Duration years = ChronoUnit.YEARS.getDuration().multipliedBy(period.getYears());
final Duration months = ChronoUnit.MONTHS.getDuration().multipliedBy(period.getMonths());
final Duration days = ChronoUnit.DAYS.getDuration().multipliedBy(period.getDays());
return years.plus(months).plus(days);
}
public Matcher getRegexMatcher(final CharSequence duration) {
return regexPattern.matcher(duration);
}
public String getSuffix() {
return suffix;
}
public Duration getJavaDuration(final long amount) {
return toJavaDuration.apply(amount);
}
public ChronoUnit getChronoUnit() {
return chronoUnit;
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy