Is there a way to create a Period from an amount of a particular TemporalUnit?

Viewed 123

It's trivial to create a Duration as a given amount of given units:

Duration duration = Duration.of(3, ChronoUnit.HOURS);

But there is no such method for Period. What is the best way to do this?

I have a situation where i have a unit and a count as variables, so can't simply hardcode a call to Period::ofYear or similar.

This is the cleanest thing i came up with:

Period period = Period.from(new TemporalAmount() {
    @Override
    public long get(TemporalUnit unitToGet) { return unitToGet.equals(unit) ? amount : 0; }

    @Override
    public List<TemporalUnit> getUnits() { return List.of(unit); }

    @Override
    public Temporal addTo(Temporal temporal) { throw new UnsupportedOperationException(); }

    @Override
    public Temporal subtractFrom(Temporal temporal) { throw new UnsupportedOperationException(); }
});

This is clean in the sense that it avoids having to embed the knowledge of what units a Period can have, although this is probably excessively purist. It seems verbose for such a simple operation, though!

2 Answers

The issue is that Period does not support all temporal units, so a general method is not applicable there. It can be simulated, but only relative to a specific date:

LocalDate base = LocalDate.of(2000, 1, 1);
LocalDate added = base.plus(amount, unit);
Period period = Period.between(base, added);

Make sure you consider what happens with variable length months and February 29th, as this approach may not have the behaviour you desire.

The simplest way to create factory for Period

import java.time.Period;
import java.time.temporal.ChronoUnit;
import java.util.EnumMap;
import java.util.Map;

import java.util.Optional;
import java.util.function.IntFunction;

public class PeriodFactory {
    private static final Map<ChronoUnit, IntFunction<Period>> map = new EnumMap<>(ChronoUnit.class);
    static {
        map.put(ChronoUnit.YEARS, Period::ofYears);
        map.put(ChronoUnit.MONTHS, Period::ofMonths);
        map.put(ChronoUnit.DAYS, Period::ofDays);
        map.put(ChronoUnit.WEEKS, Period::ofWeeks);
    }

    private PeriodFactory() { }

    public static Period create(int amount, ChronoUnit unit) {
        return Optional.ofNullable(map.get(unit)).map(function -> function.apply(amount)).orElse(null);
    }
}

Your implementation is great but should be moved to a separate class instead of defining anonymous implementation each time.

public class UnitAmount implements TemporalAmount {
    private final TemporalUnit unit;
    private final long amount;

    private UnitAmount(long amount, TemporalUnit unit) {
        this.amount = amount;
        this.unit = unit;
    }

    public static TemporalAmount of(long amount, TemporalUnit unit) {
        return new UnitAmount(amount, unit);
    }

    @Override
    public long get(TemporalUnit unit) {
        return this.unit.equals(unit) ? amount : 0;
    }

    @Override
    public List<TemporalUnit> getUnits() {
        return Collections.singletonList(unit);
    }

    @Override
    public Temporal addTo(Temporal temporal) {
        throw new UnsupportedOperationException();
    }

    @Override
    public Temporal subtractFrom(Temporal temporal) {
        throw new UnsupportedOperationException();
    }
}

Usage will be clear and simple:

        Period periodFactory = PeriodFactory.create(2, ChronoUnit.YEARS);
        Period periodUnitAmout = Period.from(UnitAmount.of(2, ChronoUnit.YEARS));

IMHO: In case of complex conversion or validation logic, if we will need to convert for example HOURS -> Period.DAYS factory is more flexible and extensible.

Related