Maybe something more elegant is possible, but the following seems to be working fine.
Make sure to look at the DST tests, though, because you might find it surprising to have 05:00 and 07:00 as the start of the 6 hours duration.
Maybe that's not what you actually want in these cases.
public class TruncationTest {
static ZonedDateTime truncateToDuration(ZonedDateTime zonedDateTime, Duration duration) {
ZonedDateTime startOfDay = zonedDateTime.truncatedTo(ChronoUnit.DAYS);
long millisSinceStartOfDay = zonedDateTime.toInstant().toEpochMilli() - startOfDay.toInstant().toEpochMilli();
long millisToSubtract = millisSinceStartOfDay % duration.toMillis();
return zonedDateTime.truncatedTo(ChronoUnit.MILLIS).minus(millisToSubtract, ChronoUnit.MILLIS);
}
@Test
public void test() {
Duration oneMinute = Duration.of(1, ChronoUnit.MINUTES);
Duration fiveMinutes = Duration.of(5, ChronoUnit.MINUTES);
Duration fifteenMinutes = Duration.of(15, ChronoUnit.MINUTES);
Duration oneHour = Duration.of(1, ChronoUnit.HOURS);
Duration sixHours = Duration.of(6, ChronoUnit.HOURS);
Duration oneDay = Duration.of(1, ChronoUnit.DAYS);
ZoneId zone = ZoneId.systemDefault();
ZonedDateTime now = LocalDateTime.parse("2018-02-24T10:37:07.123").atZone(zone);
assertEquals(LocalDateTime.parse("2018-02-24T10:37:00.000").atZone(zone),
truncateToDuration(now, oneMinute));
assertEquals(LocalDateTime.parse("2018-02-24T10:35:00.000").atZone(zone),
truncateToDuration(now, fiveMinutes));
assertEquals(LocalDateTime.parse("2018-02-24T10:30:00.000").atZone(zone),
truncateToDuration(now, fifteenMinutes));
assertEquals(LocalDateTime.parse("2018-02-24T10:00:00.000").atZone(zone),
truncateToDuration(now, oneHour));
assertEquals(LocalDateTime.parse("2018-02-24T06:00:00.000").atZone(zone),
truncateToDuration(now, sixHours));
assertEquals(LocalDateTime.parse("2018-02-24T00:00:00.000").atZone(zone),
truncateToDuration(now, oneDay));
}
@Test
public void testOnFirstDST() {
Duration oneMinute = Duration.of(1, ChronoUnit.MINUTES);
Duration fiveMinutes = Duration.of(5, ChronoUnit.MINUTES);
Duration fifteenMinutes = Duration.of(15, ChronoUnit.MINUTES);
Duration oneHour = Duration.of(1, ChronoUnit.HOURS);
Duration sixHours = Duration.of(6, ChronoUnit.HOURS);
Duration oneDay = Duration.of(1, ChronoUnit.DAYS);
ZoneId zone = ZoneId.of("Europe/Paris");
ZonedDateTime now = LocalDateTime.parse("2018-03-25T10:37:07.123").atZone(zone);
assertEquals(LocalDateTime.parse("2018-03-25T10:37:00.000").atZone(zone),
truncateToDuration(now, oneMinute));
assertEquals(LocalDateTime.parse("2018-03-25T10:35:00.000").atZone(zone),
truncateToDuration(now, fiveMinutes));
assertEquals(LocalDateTime.parse("2018-03-25T10:30:00.000").atZone(zone),
truncateToDuration(now, fifteenMinutes));
assertEquals(LocalDateTime.parse("2018-03-25T10:00:00.000").atZone(zone),
truncateToDuration(now, oneHour));
assertEquals(LocalDateTime.parse("2018-03-25T07:00:00.000").atZone(zone),
truncateToDuration(now, sixHours));
assertEquals(LocalDateTime.parse("2018-03-25T00:00:00.000").atZone(zone),
truncateToDuration(now, oneDay));
}
@Test
public void testOnSecondDST() {
Duration oneMinute = Duration.of(1, ChronoUnit.MINUTES);
Duration fiveMinutes = Duration.of(5, ChronoUnit.MINUTES);
Duration fifteenMinutes = Duration.of(15, ChronoUnit.MINUTES);
Duration oneHour = Duration.of(1, ChronoUnit.HOURS);
Duration sixHours = Duration.of(6, ChronoUnit.HOURS);
Duration oneDay = Duration.of(1, ChronoUnit.DAYS);
ZoneId zone = ZoneId.of("Europe/Paris");
ZonedDateTime now = LocalDateTime.parse("2018-10-28T10:37:07.123").atZone(zone);
assertEquals(LocalDateTime.parse("2018-10-28T10:37:00.000").atZone(zone),
truncateToDuration(now, oneMinute));
assertEquals(LocalDateTime.parse("2018-10-28T10:35:00.000").atZone(zone),
truncateToDuration(now, fiveMinutes));
assertEquals(LocalDateTime.parse("2018-10-28T10:30:00.000").atZone(zone),
truncateToDuration(now, fifteenMinutes));
assertEquals(LocalDateTime.parse("2018-10-28T10:00:00.000").atZone(zone),
truncateToDuration(now, oneHour));
assertEquals(LocalDateTime.parse("2018-10-28T05:00:00.000").atZone(zone),
truncateToDuration(now, sixHours));
assertEquals(LocalDateTime.parse("2018-10-28T00:00:00.000").atZone(zone),
truncateToDuration(now, oneDay));
}
}