【问题标题】:One second difference when comparing two Date() with difference of one day比较两个 Date() 与一天的差异时的一秒差异
【发布时间】:2021-12-03 00:37:49
【问题描述】:

我用 getDateAfter(int days) 方法编写了简单的 TimeService 并对其进行了测试:

@Test
@Order(7)
public void getDateAfterCorrect() throws InterruptedException {
    waitIfNeeded();
    LocalDateTime today = LocalDateTime.now();
    LocalDateTime tomorrow = timeService.getDateAfter(1).toInstant()
        .atZone(ZoneId.systemDefault()).toLocalDateTime();
    long diff = ChronoUnit.SECONDS.between(today, tomorrow);
    long secondsAtDay = 86400;
    Assertions.assertEquals(secondsAtDay, diff);
}

一天应该是 86400 秒,但 diff 是 86399。 我试图通过实现waitIfNeeded() 方法来考虑一部分代码可以在其他时间执行而不是其他时间

private void waitIfNeeded() throws InterruptedException {
    var currentMillis = Instant.now().get(ChronoField.MILLI_OF_SECOND);
    if (currentMillis > 500) {
        Thread.sleep(1000 - currentMillis);
    }
}

您是否知道为什么我无法进行此测试以及其他可能出现问题的地方(我假设诸如编程语言如何处理步骤年等...)

【问题讨论】:

  • 好吧,不知道timeService.getDateAfter() 是做什么的,很难说。您能否提供一个minimal reproducible example
  • 默认时区中的当前时间首选ZonedDateTIme 而不是LocalDateTIme

标签: java spring date time java-time


【解决方案1】:

我设法简化了测试并且可以正常工作,现在可以了:

@Test
@Order(7)
public void getDateAfterCorrect() throws InterruptedException {
    waitIfNeeded();
    long today = timeService.getDate().toInstant().getEpochSecond();
    long tommorow = timeService.getDateAfter(1).toInstant().getEpochSecond();
    Assertions.assertEquals(86400, tommorow - today);
}

但有趣的是为什么使用其他方法比较两个日期会产生这样的结果,如果有深入了解的人可以回答它,可能很少有人会感兴趣。

【讨论】:

    【解决方案2】:

    java.util 日期时间 API 已过时且容易出错。建议彻底停止使用,改用modern Date-Time API*

    除此之外,您无需自己执行计算(减法),而是使用Instant#until 返回指定ChronoUnit 中的持续时间。

    import java.time.Instant;
    import java.time.temporal.ChronoUnit;
    
    public class Main {
        public static void main(String[] args) {
            // This is a sample Instant. In your case, it will be returned by
            // timeService.getDate().toInstant()
            Instant today = Instant.now();
    
            // This is a sample Instant after one day. In your case, it will be returned by
            // timeService.getDateAfter(1).toInstant()
            Instant tomorrow = today.plus(1, ChronoUnit.DAYS);
    
            long seconds = today.until(tomorrow, ChronoUnit.SECONDS);
    
            // In your case, you will use Assertions.assertEquals(86400, seconds);
            System.out.println(seconds);
        }
    }
    

    输出:

    86400
    

    ONLINE DEMO

    Trail: Date Time 了解有关现代日期时间 API 的更多信息。


    * 如果您正在为一个 Android 项目工作,并且您的 Android API 级别仍然不符合 Java-8,请检查 Java 8+ APIs available through desugaring。请注意,Android 8.0 Oreo 已经提供了support for java.time

    【讨论】:

      【解决方案3】:

      解释:为什么是86399?缺乏精度

      我假设 timeService.getDateAfter(1) 返回一个老式的 Date 对象。您不应该再在代码中使用Date,它已被 java.time 取代,这是您多年前也在使用的现代 Java 日期和时间 API。但我们仍然很好奇,为什么您使用 Date 的代码没有给出一天 86 400 秒的预期结果。

      Date 具有毫秒精度。 java.time 具有纳秒精度,从 Java 9 开始,许多类的 now 方法在大多数平台上都具有微秒精度。因此,例如 LocalDateTime.now() 返回整秒后的 0.001234 秒。几微秒后,可能在整秒后的 0.001432 处,您的时间服务返回一个 Date,其值是在同一整秒后 0.001 秒。从今天 0.001234 到明天 0.001 之间不是一整天,也不是完全 24 小时,因此以秒为单位的差被截断为 86 399。

      当我在循环中运行您的代码时,我第一次得到 86 400。这一定是因为我在两次调用之间经过了一整毫秒的时间。可能是因为 JVM 预热。以下时间我得到了 86 399。

      一个可能的解决办法

      获得一致精度的一种方法是将所有内容截断到毫秒。甚至几秒钟。通过对您的代码进行以下更改,我始终得到 86 400。

          LocalDateTime today = LocalDateTime.now().truncatedTo(ChronoUnit.MILLIS);
      

      当使用ChronoUnit.SECONDS 而不是.MILLIS 时,我得到了相同的结果。我相信这也可以在一定程度上解释为什么您自己的答案的改变会奏效。不过,我认为您需要注意,两次通话之间确实会经过一段时间,并且您无法控制多少时间。因此,在极少数情况下,您可能会得到 86 401 甚至更高的数字。即使我在几次跑步中都没有观察到它。

      我曾经在一个很多单元测试偶尔失败的地方工作过。即使我养成了在有问题的单元测试中输入 cmets 关于其零星失败的习惯,这也很烦人,也是对代码不信任的根源。请不要将自己和您的同事置于同样的境地。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-10-17
        • 2017-05-04
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多