11

I want to convert Java 8 LocalDateTime to nearest 5 minutes. E.g.

1601  ->  1605
1602  ->  1605
1603  ->  1605
1604  ->  1605
1605  ->  1605
1606  ->  1610
1607  ->  1610
1608  ->  1610
1609  ->  1610
1610  ->  1610

I would like to use existing functionality of LocalDateTime or Math api. Any suggestions?

Misha
  • 27,433
  • 6
  • 62
  • 78
King Khan
  • 143
  • 1
  • 2
  • 8
  • 1
    [Relevant](https://stackoverflow.com/questions/25552023/round-minutes-to-ceiling-using-java-8) although not duplicate. In short, truncate the minutes then add the correct number back. – Boris the Spider Jun 03 '16 at 11:26
  • 1
    Also relevant http://stackoverflow.com/questions/3553964/how-to-round-time-to-the-nearest-quarter-hour-in-java – Tunaki Jun 03 '16 at 11:37
  • 1
    You should decide, do you want to round to *next*, as your title and example suggest, or to *nearest*, as stated in the body of your question. – Holger Jun 03 '16 at 11:40
  • next nearest. Up not Down. – King Khan Jun 03 '16 at 11:49
  • For comparison: You don't need to apply any hand-made arithmetic with my lib Time4J, see also this [short and elegant example](https://gist.github.com/MenoData/c491cddb45c96d3b3ae6003c9dfc00a6) which demonstrates one of many built-in manipulations missing in java.time-package. – Meno Hochschild Jun 03 '16 at 13:56

2 Answers2

23

You can round towards the next multiple of five minutes using:

LocalDateTime dt = …
dt = dt.withSecond(0).withNano(0).plusMinutes((65-dt.getMinute())%5);

You can reproduce your example using

LocalDateTime dt=LocalDateTime.now().withHour(16).withSecond(0).withNano(0);
for(int i=1; i<=10; i++) {
    dt=dt.withMinute(i);
    System.out.printf("%02d%02d -> ", dt.getHour(), dt.getMinute());
    // the rounding step:
    dt=dt.plusMinutes((65-dt.getMinute())%5);
    System.out.printf("%02d%02d%n", dt.getHour(), dt.getMinute());
}

1601 -> 1605
1602 -> 1605
1603 -> 1605
1604 -> 1605
1605 -> 1605
1606 -> 1610
1607 -> 1610
1608 -> 1610
1609 -> 1610
1610 -> 1610

(in this example, I clear the seconds and nanos only once as they stay zero).

Holger
  • 285,553
  • 42
  • 434
  • 765
  • Thanks. That's what I wanted. It works perfectly. I am confused what 65 is in this equation? – King Khan Jun 03 '16 at 12:39
  • 3
    The logic is `(5-x) mod 5`, but the modulo operator `%` doesn’t do what we want when the value is negative, hence this code uses `(65-x)%5` instead, so, since `x` is always between `0..59`, the value on the left of `%` is always positive and `(65-x) % 5 == (65-x) mod 5`. – Holger Jun 03 '16 at 12:56
10

Alternatively to what Holger suggests, you can create a TemporalAdjuster, which will allow you to write something like date.with(nextOrSameMinutes(5)):

public static void main(String[] args) {
  for (int i = 0; i <= 10; i++) {
    LocalDateTime d = LocalDateTime.of(LocalDate.now(), LocalTime.of(16, i, 0));
    LocalDateTime nearest5 = d.with(nextOrSameMinutes(5));
    System.out.println(d.toLocalTime() + " -> " + nearest5.toLocalTime());
  }
}

public static TemporalAdjuster nextOrSameMinutes(int minutes) {
  return temporal -> {
    int minute = temporal.get(ChronoField.MINUTE_OF_HOUR);
    int nearestMinute = (int) Math.ceil(1d * minute / minutes) * minutes;
    int adjustBy = nearestMinute - minute;
    return temporal.plus(adjustBy, ChronoUnit.MINUTES);
  };
}

Note that this doesn't truncate the seconds/nanos from the original date. If you want that, you can amend the end of the adjuster to:

if (adjustBy == 0
        && (temporal.get(ChronoField.SECOND_OF_MINUTE) > 0 || temporal.get(ChronoField.NANO_OF_SECOND) > 0)) {
  adjustBy += 5;
}
return temporal.plus(adjustBy, ChronoUnit.MINUTES)
          .with(ChronoField.SECOND_OF_MINUTE, 0)
          .with(ChronoField.NANO_OF_SECOND, 0);
assylias
  • 321,522
  • 82
  • 660
  • 783
  • 2
    That’s the great thing about that API, you can always replace `foo.bar()` with `foo.with(f -> f.bar())` or `foo.with(Foo::bar)`… – Holger Jun 03 '16 at 14:43
  • 3
    @Holger Useability with lambdas and method references is very nice, yes I agree, but there is also a dark side. `TemporalAdjuster` is not type-safe in contrast to `Collection`- and `Stream`-API. If you try to apply this adjuster on `Instant`(as example) then the compiler will say okay but you get an exception at runtime. – Meno Hochschild Jun 04 '16 at 14:41
  • I know i'm a few years too late. But in your nextOrSameMinutes method, you never use the 'minutes' parameter. – zelite Nov 28 '21 at 17:41
  • @zelite well spotted! – assylias Nov 29 '21 at 06:11