如何将 ZonedDateTime 舍入到 Duration

How to round a ZonedDateTime to a Duration

我有一个存储在 timeStamp 变量中的 ZonedDateTime。

我还有一个时长 barDuration,可以是一分钟、5 分钟、15 分钟、一小时、6 小时或一天。

我正在绘制宽度为 barDuration.

的蜡烛图

如果条形持续时间设置为 1 分钟,我需要条形从一整分钟开始(例如 9:43:00)。

如果设置为 5 分钟,我需要柱状图从 9:45、9:50、9:55 等开始。

如果设置为 15 分钟,则条形图从 9:45、10:00、10:15 等开始。

barDuration 为 1 分钟时,我知道我可以使用以下方法计算柱的结束时间:

timeStamp.truncatedTo(ChronoUnit.MINUTES).plus(barDuration)

但这是硬编码的,我想直接使用 barDuration 来执行截断。如何做到这一点?

编辑:

我找到 here 一个解决方案 Clocks:

Clock baseClock = Clock.tickMinutes(timeStamp.getZone());
Clock barClock = Clock.tick(baseClock, barDuration);
LocalDateTime now =  LocalDateTime.now(barClock);
LocalDateTime barEndTime =  now.plus(barDuration);

这可行,但它不是基于我的 timeStamp,而是基于使用 LocalDateTime.now(),这不是我想要的(我不确定我机器上的本地时间是与我从远程服务器检索到的时间戳同步)。

也许更优雅的东西是可能的,但下面的似乎工作正常。

不过,请务必查看 DST 测试,因为您可能 会发现以 05:00 和 07:00 作为开始持续时间 6 小时。 在这些情况下,也许这不是您真正想要的。

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

作为对 的简单补充,这里有一个 Java 9 版本,它更加时间单位中立。

static ZonedDateTime truncateToDuration(ZonedDateTime zonedDateTime, Duration duration) {
    ZonedDateTime startOfDay = zonedDateTime.truncatedTo(ChronoUnit.DAYS);
    return startOfDay.plus(duration.multipliedBy(
            Duration.between(startOfDay, zonedDateTime).dividedBy(duration)));
}

我没有彻底测试过,但我希望它也可以四舍五入到例如 500 纳米。我将自一天开始以来的持续时间除以四舍五入的持续时间,然后立即相乘,以获得该持续时间的整数。

如果您的持续时间不以一个小时为单位,或者您的一天从整整一个小时以外的其他时间开始(如果发生这种情况),则可能会出现有趣的结果。