How to round it down to the closest 5 minute interval if it is 1,2,3,4 minutes ahead?
Asked Answered
F

2

6

I want to round down an Instant / LocalDateTime to its closest 5 minutes interval in Java.

Examples: Suppose the time is:

2021-02-08T19:01:49.594 

or

2021-02-08T19:02:49.594

or

2021-02-08T19:03:49.594

or

2021-02-08T19:04:49.594

Expected result:

2021-02-08T19:00:00.000
Frenzied answered 15/2, 2021 at 6:10 Comment(5)
You could convert to seconds, subtract that value modulo 5, and convert back to a date.Valedictory
Souma - I think you jave already got this answer from https://mcmap.net/q/1265993/-how-to-round-off-to-the-closest-hour-if-its-only-5-minutes-ahead-or-behind/10819573 and https://mcmap.net/q/1632129/-how-to-round-off-to-the-closest-5-minute-interval-if-it-is-1-or-2-minutes-ahead-or-behind. It will be helpful if you can describe any further doubt you may have.Collection
that was a bit of different requirement.Frenzied
We were rounding off 19:01 and 19:02 to 19:00 and 19:03 and 19:04 to 19:05, here what I want is to round down to 19:00 for all 4 of the above times.Frenzied
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
C
3

You can truncate it to ChronoUnit.MINUTES and then check the minute-of-hour as per the requirement i.e. if it is not a multiple of 5 subtract the remainder when divided by 5. Use LocalDate#withMinute to return a copy of this LocalDateTime with the minute-of-hour altered.

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;

class Main {
    public static void main(String[] args) {
        // Test
        String[] arr = { "2021-02-08T19:02:49.594", "2021-02-08T19:56:49.594", "2021-02-08T19:54:49.594",
                "2021-02-08T19:06:49.594" };

        DateTimeFormatter dtf = DateTimeFormatter.ofPattern("uuuu-MM-dd'T'HH:mm:ss.SSS");

        for (String s : arr) {
            System.out.println(roundToNearestHour(s).format(dtf));
        }
    }

    static LocalDateTime roundToNearestHour(String str) {
        LocalDateTime ldt = LocalDateTime.parse(str).truncatedTo(ChronoUnit.MINUTES);
        int minute = ldt.getMinute();
        int remainder = minute % 5;
        if (remainder != 0) {
            ldt = ldt.withMinute(minute - remainder);
        }
        return ldt;
    }
}

Output:

2021-02-08T19:00:00.000
2021-02-08T19:55:00.000
2021-02-08T19:50:00.000
2021-02-08T19:05:00.000
Collection answered 15/2, 2021 at 6:58 Comment(0)
A
14

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();
    }

}
Andy answered 15/2, 2021 at 7:11 Comment(2)
Thanks for the great answer @andreas. I found a bug that causes compile error and wanted to edit the answer, but the "Suggested edit queue is full". I fixed the compile error in the DurationUnit#between(Temporal, Temporal) method by replacing the method contents with: return Duration.between(temporal1Inclusive, temporal2Exclusive).dividedBy(this.duration.toMillis()).toMillis(); I've tested it and it's working as expected.Disseminate
This is a great solution and something that should be provided by the core Java runtime, not something we need to roll ourselves :(Debase
C
3

You can truncate it to ChronoUnit.MINUTES and then check the minute-of-hour as per the requirement i.e. if it is not a multiple of 5 subtract the remainder when divided by 5. Use LocalDate#withMinute to return a copy of this LocalDateTime with the minute-of-hour altered.

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;

class Main {
    public static void main(String[] args) {
        // Test
        String[] arr = { "2021-02-08T19:02:49.594", "2021-02-08T19:56:49.594", "2021-02-08T19:54:49.594",
                "2021-02-08T19:06:49.594" };

        DateTimeFormatter dtf = DateTimeFormatter.ofPattern("uuuu-MM-dd'T'HH:mm:ss.SSS");

        for (String s : arr) {
            System.out.println(roundToNearestHour(s).format(dtf));
        }
    }

    static LocalDateTime roundToNearestHour(String str) {
        LocalDateTime ldt = LocalDateTime.parse(str).truncatedTo(ChronoUnit.MINUTES);
        int minute = ldt.getMinute();
        int remainder = minute % 5;
        if (remainder != 0) {
            ldt = ldt.withMinute(minute - remainder);
        }
        return ldt;
    }
}

Output:

2021-02-08T19:00:00.000
2021-02-08T19:55:00.000
2021-02-08T19:50:00.000
2021-02-08T19:05:00.000
Collection answered 15/2, 2021 at 6:58 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.