io.camunda.zeebe.model.bpmn.util.time.Interval Maven / Gradle / Ivy
/*
* Copyright © 2017 camunda services GmbH ([email protected])
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.camunda.zeebe.model.bpmn.util.time;
import java.time.Duration;
import java.time.Instant;
import java.time.Period;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.temporal.ChronoUnit;
import java.time.temporal.Temporal;
import java.time.temporal.TemporalAmount;
import java.time.temporal.TemporalUnit;
import java.time.temporal.UnsupportedTemporalTypeException;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
/** Combines {@link java.time.Period}, and {@link java.time.Duration} */
public class Interval implements TemporalAmount {
private static final Duration ACCURATE_DURATION_UPPER_BOUND = Duration.ofDays(1);
private final List units;
private final Period period;
private final Duration duration;
private final Optional start;
public Interval(final Period period, final Duration duration) {
this(Optional.empty(), period, duration);
}
public Interval(
final Optional start, final Period period, final Duration duration) {
this.period = period;
this.duration = duration;
this.start = start;
units = new ArrayList<>();
units.addAll(period.getUnits());
units.addAll(duration.getUnits());
}
public Interval(final Duration duration) {
this(Period.ZERO, duration);
}
public Interval(final Period period) {
this(period, Duration.ZERO);
}
public Period getPeriod() {
return period;
}
public Duration getDuration() {
return duration;
}
public Optional getStart() {
return start;
}
public long toEpochMilli(final long fromEpochMilli) {
final long epochMilli =
start.map(ZonedDateTime::toInstant).map(Instant::toEpochMilli).orElse(fromEpochMilli);
if (epochMilli <= fromEpochMilli) {
if (!isCalendarBased()) {
return fromEpochMilli + getDuration().toMillis();
}
return ZonedDateTime.ofInstant(Instant.ofEpochMilli(fromEpochMilli), ZoneId.systemDefault())
.plus(this)
.toInstant()
.toEpochMilli();
}
return epochMilli;
}
/**
* Creates a new interval with the specified start instant.
*
* @param start the start instant for the new interval
* @return a new interval from this interval and the specified start
*/
public Interval withStart(final Instant start) {
final ZoneId zoneId = getStart().map(ZonedDateTime::getZone).orElse(ZoneId.systemDefault());
return new Interval(
Optional.of(ZonedDateTime.ofInstant(start, zoneId).plus(this)), getPeriod(), getDuration());
}
/**
* {@link Duration#get(TemporalUnit)} only accepts {@link ChronoUnit#SECONDS} and {@link
* ChronoUnit#NANOS}, so for any other units, this call is delegated to {@link
* Period#get(TemporalUnit)}, though it could easily be the other way around.
*
* @param unit the {@code TemporalUnit} for which to return the value
* @return the long value of the unit
* @throws UnsupportedTemporalTypeException if the unit is not supported
*/
@Override
public long get(final TemporalUnit unit) {
if (unit == ChronoUnit.SECONDS || unit == ChronoUnit.NANOS) {
return duration.get(unit);
}
return period.get(unit);
}
@Override
public List getUnits() {
return units;
}
@Override
public Temporal addTo(final Temporal temporal) {
return temporal.plus(period).plus(duration);
}
@Override
public Temporal subtractFrom(final Temporal temporal) {
return temporal.minus(period).minus(duration);
}
@Override
public int hashCode() {
return Objects.hash(getPeriod(), getDuration());
}
@Override
public boolean equals(final Object o) {
if (this == o) {
return true;
}
if (!(o instanceof Interval)) {
return false;
}
final Interval interval = (Interval) o;
return Objects.equals(getPeriod(), interval.getPeriod())
&& Objects.equals(getDuration(), interval.getDuration())
&& Objects.equals(getStart(), interval.getStart());
}
@Override
public String toString() {
if (period.isZero()) {
return duration.toString();
}
if (duration.isZero()) {
return period.toString();
}
return period.toString() + duration.toString().substring(1);
}
private boolean isCalendarBased() {
return !getPeriod().isZero() || getDuration().compareTo(ACCURATE_DURATION_UPPER_BOUND) >= 0;
}
/**
* Only supports a subset of ISO8601, combining start, period and duration.
*
* @param text ISO8601 conforming interval expression
* @return parsed interval
*/
public static Interval parse(final String text) {
String sign = "";
int startOffset = 0;
final int index = text.lastIndexOf("/");
Optional start = Optional.empty();
if (index > 0) {
start = Optional.ofNullable(ZonedDateTime.parse(text.substring(0, index)));
}
final String intervalExp = text.substring(index + 1);
if (intervalExp.startsWith("-")) {
startOffset = 1;
sign = "-";
} else if (intervalExp.startsWith("+")) {
startOffset = 1;
}
final int durationOffset = intervalExp.indexOf('T');
if (durationOffset == -1) {
return new Interval(start, Period.parse(intervalExp), Duration.ZERO);
} else if (durationOffset == startOffset + 1) {
return new Interval(start, Period.ZERO, Duration.parse(intervalExp));
}
return new Interval(
start,
Period.parse(intervalExp.substring(0, durationOffset)),
Duration.parse(sign + "P" + intervalExp.substring(durationOffset)));
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy