Search code examples
javajava-8localdatetime

How to round off to the closest 5 minute interval if it is 1 or 2 minutes ahead or behind?


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

Examples: Suppose the time is: 2021-02-08T19:02:49.594 Expected result: 2021-02-08T19:00:00.000

Suppose the time is: 2021-02-08T19:03:49.594 Expected result: 2021-02-08T19:05:00.000

Similarly, if the time is: 2021-02-08T19:56:49.594 Expected result: 2021-02-08T19:55:00.000

Similarly, if the time is: 2021-02-08T19:58:49.594 Expected result: 2021-02-08T20:00:00.000

But if the time is 2021-02-08T19:55:00.000 or 2021-02-08T19:05:00.000 or 2021-02-08T19:00:00.000 then do nothing.


Solution

  • Here is a general-purpose solution (not just 5 minutes):

    public static Instant toNearest(Duration interval, Instant instant) {
        long intervalMillis = interval.toMillis();
        long adjustedInstantMillis = (instant.toEpochMilli() + (intervalMillis / 2)) / intervalMillis * intervalMillis;
        return Instant.ofEpochMilli(adjustedInstantMillis);
    }
    
    public static LocalDateTime toNearest(Duration interval, LocalDateTime dateTime, ZoneId zoneId) {
        ZoneRules zoneRules = zoneId.getRules();
        Instant instant = toNearest(interval, dateTime.toInstant(zoneRules.getOffset(dateTime)));
        return LocalDateTime.ofInstant(instant, zoneRules.getOffset(instant));
    }
    
    public static LocalDateTime toNearest(Duration interval, LocalDateTime dateTime) {
        return toNearest(interval, dateTime, ZoneId.systemDefault());
    }
    
    @Test
    public void toNearestRoundsCorrectly() {
        assertThat(toNearest(Duration.ofMinutes(5), LocalDateTime.of(2021, 2, 8, 19, 0, 0)))
                .isEqualTo(LocalDateTime.of(2021, 2, 8, 19, 0, 0));
        assertThat(toNearest(Duration.ofMinutes(5), LocalDateTime.of(2021, 2, 8, 19, 2, 29, 999999999)))
                .isEqualTo(LocalDateTime.of(2021, 2, 8, 19, 0, 0));
        assertThat(toNearest(Duration.ofMinutes(5), LocalDateTime.of(2021, 2, 8, 19, 2, 30)))
                .isEqualTo(LocalDateTime.of(2021, 2, 8, 19, 5, 0));
        assertThat(toNearest(Duration.ofMinutes(5), LocalDateTime.of(2021, 2, 8, 19, 5, 0)))
                .isEqualTo(LocalDateTime.of(2021, 2, 8, 19, 5, 0));
    }
    
    @Test
    public void toNearestTreatsDaylightSavingChangesCorrectly() {
        assertThat(toNearest(Duration.ofMinutes(5), LocalDateTime.of(2021,3,27, 23,57,30), ZoneId.of("Europe/London")))
                .isEqualTo(LocalDateTime.of(2021,3,28, 0,0,0));
        assertThat(toNearest(Duration.ofMinutes(5), LocalDateTime.of(2021,3,28, 0,57,29,999999999), ZoneId.of("Europe/London")))
                .isEqualTo(LocalDateTime.of(2021,3,28, 0,55,0));
        assertThat(toNearest(Duration.ofMinutes(5), LocalDateTime.of(2021,3,28, 0,57,30), ZoneId.of("Europe/London")))
                .isEqualTo(LocalDateTime.of(2021,3,28, 2,0,0));
        assertThat(toNearest(Duration.ofMinutes(5), LocalDateTime.of(2021,3,28, 2,5,0), ZoneId.of("Europe/London")))
                .isEqualTo(LocalDateTime.of(2021,3,28, 2,5,0));
    }