For a more general purpose, reusable solution, implement a custom TemporalUnit
to be used with the truncatedTo(TemporalUnit unit)
method. Below is an implementation copied from another answer.
You then simply use it to call truncatedTo()
with a 5 minute "unit". It works with both LocalDateTime
and Instant
.
String[] inputs = { "2021-02-08T19:01:49.594", "2021-02-08T19:02:49.594",
"2021-02-08T19:03:49.594", "2021-02-08T19:04:49.594",
"2021-02-08T19:26:49.594", "2021-02-08T19:27:49.594",
"2021-02-08T19:28:49.594", "2021-02-08T19:29:49.594" };
for (String input : inputs) {
LocalDateTime dateTime = LocalDateTime.parse(input);
Instant instant = Instant.parse(input + "Z");
System.out.printf("%s -> %s %s -> %s%n",
dateTime, dateTime.truncatedTo(DurationUnit.ofMinutes(5)),
instant, instant.truncatedTo(DurationUnit.ofMinutes(5)));
}
Output
2021-02-08T19:01:49.594 -> 2021-02-08T19:00 2021-02-08T19:01:49.594Z -> 2021-02-08T19:00:00Z
2021-02-08T19:02:49.594 -> 2021-02-08T19:00 2021-02-08T19:02:49.594Z -> 2021-02-08T19:00:00Z
2021-02-08T19:03:49.594 -> 2021-02-08T19:00 2021-02-08T19:03:49.594Z -> 2021-02-08T19:00:00Z
2021-02-08T19:04:49.594 -> 2021-02-08T19:00 2021-02-08T19:04:49.594Z -> 2021-02-08T19:00:00Z
2021-02-08T19:26:49.594 -> 2021-02-08T19:25 2021-02-08T19:26:49.594Z -> 2021-02-08T19:25:00Z
2021-02-08T19:27:49.594 -> 2021-02-08T19:25 2021-02-08T19:27:49.594Z -> 2021-02-08T19:25:00Z
2021-02-08T19:28:49.594 -> 2021-02-08T19:25 2021-02-08T19:28:49.594Z -> 2021-02-08T19:25:00Z
2021-02-08T19:29:49.594 -> 2021-02-08T19:25 2021-02-08T19:29:49.594Z -> 2021-02-08T19:25:00Z
Being general purpose, it can actually be used with Instant
, LocalDateTime
, OffsetDateTime
, ZonedDateTime
, LocalTime
, and OffsetTime
, and it can be used with any time period that divides into a full day (e.g. 180 seconds, or 5 minutes, or 2 hours, but not 7 minutes).
System.out.println(Instant.now().truncatedTo(DurationUnit.ofHours(2)));
System.out.println(LocalDateTime.now().truncatedTo(DurationUnit.ofHours(2)));
System.out.println(OffsetDateTime.now().truncatedTo(DurationUnit.ofHours(2)));
System.out.println(ZonedDateTime.now().truncatedTo(DurationUnit.ofHours(2)));
System.out.println(LocalTime.now().truncatedTo(DurationUnit.ofHours(2)));
System.out.println(OffsetTime.now().truncatedTo(DurationUnit.ofHours(2)));
Output
2021-02-15T06:00:00Z
2021-02-15T02:00
2021-02-15T02:00-05:00
2021-02-15T02:00-05:00[America/New_York]
02:00
02:00-05:00
Custom TemporalUnit
public final class DurationUnit implements TemporalUnit {
private static final int SECONDS_PER_DAY = 86400;
private static final long NANOS_PER_SECOND = 1000_000_000L;
private static final long NANOS_PER_DAY = NANOS_PER_SECOND * SECONDS_PER_DAY;
private final Duration duration;
public static DurationUnit of(Duration duration) { return new DurationUnit(duration); }
public static DurationUnit ofDays(long days) { return new DurationUnit(Duration.ofDays(days)); }
public static DurationUnit ofHours(long hours) { return new DurationUnit(Duration.ofHours(hours)); }
public static DurationUnit ofMinutes(long minutes) { return new DurationUnit(Duration.ofMinutes(minutes)); }
public static DurationUnit ofSeconds(long seconds) { return new DurationUnit(Duration.ofSeconds(seconds)); }
public static DurationUnit ofMillis(long millis) { return new DurationUnit(Duration.ofMillis(millis)); }
public static DurationUnit ofNanos(long nanos) { return new DurationUnit(Duration.ofNanos(nanos)); }
private DurationUnit(Duration duration) {
if (duration.isZero() || duration.isNegative())
throw new IllegalArgumentException("Duration may not be zero or negative");
this.duration = duration;
}
@Override
public Duration getDuration() {
return this.duration;
}
@Override
public boolean isDurationEstimated() {
return (this.duration.getSeconds() >= SECONDS_PER_DAY);
}
@Override
public boolean isDateBased() {
return (this.duration.getNano() == 0 && this.duration.getSeconds() % SECONDS_PER_DAY == 0);
}
@Override
public boolean isTimeBased() {
return (this.duration.getSeconds() < SECONDS_PER_DAY && NANOS_PER_DAY % this.duration.toNanos() == 0);
}
@Override
@SuppressWarnings("unchecked")
public <R extends Temporal> R addTo(R temporal, long amount) {
return (R) this.duration.multipliedBy(amount).addTo(temporal);
}
@Override
public long between(Temporal temporal1Inclusive, Temporal temporal2Exclusive) {
return Duration.between(temporal1Inclusive, temporal2Exclusive).dividedBy(this.duration);
}
@Override
public String toString() {
return this.duration.toString();
}
}
d = d.truncatedTo(ChronoUnit.MINUTES).minusMinutes(d.getMinute()%5);
and in case you coming back with another “bit of different requirement” soon, rounding up is here. – Shira