package tc.oc.time;
import java.time.Duration;
import java.time.temporal.ChronoUnit;
import java.time.temporal.Temporal;
import java.time.temporal.TemporalAmount;
import java.time.temporal.TemporalUnit;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import tc.oc.commons.core.IterableUtils;
import tc.oc.commons.core.util.Comparables;
import tc.oc.commons.core.util.TimeUtils;
public class TimePeriod implements TemporalAmount {
private static final ImmutableList<TemporalUnit> NORMAL_UNITS = ImmutableList.of(
ChronoUnit.DAYS, ChronoUnit.HOURS, ChronoUnit.MINUTES, ChronoUnit.SECONDS,
ChronoUnit.MILLIS, ChronoUnit.MICROS, ChronoUnit.NANOS
);
private final ImmutableMap<TemporalUnit, Long> values;
private final ImmutableList<TemporalUnit> units;
private TimePeriod(ImmutableMap<TemporalUnit, Long> values) {
this.values = values;
this.units = ImmutableList.copyOf(values.keySet());
}
public static TimePeriod ofUnits(Map<TemporalUnit, Long> values) {
final ImmutableMap.Builder<TemporalUnit, Long> builder = ImmutableMap.builder();
values.keySet().stream().sorted(Collections.reverseOrder())
.forEach(unit -> builder.put(unit, values.get(unit)));
return new TimePeriod(builder.build());
}
public static TimePeriod ofUnit(long amount, TemporalUnit unit) {
return new TimePeriod(ImmutableMap.of(unit, amount));
}
public static TimePeriod inUnits(Duration duration, Collection<TemporalUnit> units) {
final ImmutableMap.Builder<TemporalUnit, Long> builder = ImmutableMap.builder();
for(TemporalUnit unit : IterableUtils.sorted(units, TimeUtils.descendingUnits())) {
if(!TimeUtils.isPrecise(unit)) {
throw new IllegalArgumentException("Imprecise unit " + unit);
}
if(Comparables.greaterOrEqual(duration, unit.getDuration())) {
final long amount = TimeUtils.toUnit(unit, duration);
builder.put(unit, amount);
duration = duration.minus(amount, unit);
}
}
return new TimePeriod(builder.build());
}
public static TimePeriod inUnits(Duration duration, TemporalUnit... units) {
return inUnits(duration, Arrays.asList(units));
}
public static TimePeriod inUnit(Duration duration, TemporalUnit unit) {
return ofUnit(TimeUtils.toUnit(unit, duration), unit);
}
public static TimePeriod normalized(Duration duration) {
return inUnits(duration, NORMAL_UNITS);
}
@Override
public List<TemporalUnit> getUnits() {
return units;
}
@Override
public long get(TemporalUnit unit) {
return values.getOrDefault(unit, 0L);
}
public Duration getDuration() {
return values.entrySet()
.stream()
.reduce(Duration.ZERO, (d, e) -> d.plus(e.getValue(), e.getKey()), Duration::plus);
}
public TimePeriod normalized() {
return normalized(getDuration());
}
@Override
public Temporal addTo(Temporal temporal) {
for(Map.Entry<TemporalUnit, Long> entry : values.entrySet()) {
temporal = temporal.plus(entry.getValue(), entry.getKey());
}
return temporal;
}
@Override
public Temporal subtractFrom(Temporal temporal) {
for(Map.Entry<TemporalUnit, Long> entry : values.entrySet()) {
temporal = temporal.minus(entry.getValue(), entry.getKey());
}
return temporal;
}
}