为什么 2020 年 3 月 30 日和 2020 年 3 月 1 日之间的差值错误地给出了 28 天而不是 29 天?

Joe*_*Joe 129 java datediff date java-7

TimeUnit.DAYS.convert(
   Math.abs(
      new SimpleDateFormat("dd-MM-yyyy HH:mm:ss").parse("30-03-2020 00:00:00").getTime() - 
      new SimpleDateFormat("dd-MM-yyyy HH:mm:ss").parse("1-03-2020 00:00:00").getTime()
   ),
   TimeUnit.MILLISECONDS)
Run Code Online (Sandbox Code Playgroud)

结果是 28,而它应该是 29。

时区/位置可能是问题吗?

And*_*eas 211

问题在于,由于夏令时的变化(2020 年 3 月 8 日星期日),这些日期之间有28 天 23 小时。将结果TimeUnit.DAYS.convert(...) 截断为 28 天。

要查看问题(我在美国东部时区):

SimpleDateFormat fmt = new SimpleDateFormat("dd-MM-yyyy HH:mm:ss");
long diff = fmt.parse("30-03-2020 00:00:00").getTime() -
            fmt.parse("1-03-2020 00:00:00").getTime();

System.out.println(diff);
System.out.println("Days: " + TimeUnit.DAYS.convert(Math.abs(diff), TimeUnit.MILLISECONDS));
System.out.println("Hours: " + TimeUnit.HOURS.convert(Math.abs(diff), TimeUnit.MILLISECONDS));
System.out.println("Days: " + TimeUnit.HOURS.convert(Math.abs(diff), TimeUnit.MILLISECONDS) / 24.0);
Run Code Online (Sandbox Code Playgroud)

输出

SimpleDateFormat fmt = new SimpleDateFormat("dd-MM-yyyy HH:mm:ss");
long diff = fmt.parse("30-03-2020 00:00:00").getTime() -
            fmt.parse("1-03-2020 00:00:00").getTime();

System.out.println(diff);
System.out.println("Days: " + TimeUnit.DAYS.convert(Math.abs(diff), TimeUnit.MILLISECONDS));
System.out.println("Hours: " + TimeUnit.HOURS.convert(Math.abs(diff), TimeUnit.MILLISECONDS));
System.out.println("Days: " + TimeUnit.HOURS.convert(Math.abs(diff), TimeUnit.MILLISECONDS) / 24.0);
Run Code Online (Sandbox Code Playgroud)

要修复,请使用没有 DST 的时区,例如UTC

SimpleDateFormat fmt = new SimpleDateFormat("dd-MM-yyyy HH:mm:ss");
fmt.setTimeZone(TimeZone.getTimeZone("UTC"));
long diff = fmt.parse("30-03-2020 00:00:00").getTime() -
            fmt.parse("1-03-2020 00:00:00").getTime();
Run Code Online (Sandbox Code Playgroud)

输出

2502000000
Days: 28
Hours: 695
Days: 28.958333333333332
Run Code Online (Sandbox Code Playgroud)

  • “修复”不要与时间做数学运算。使用正确的日期/时间库。 (126认同)
  • @AndyTurner 使用内置的 Java 7 API 进行修复。既然它*可以*被修复,那么展示如何是一个有效的答案。强迫某人包含一个完整的库(Joda-Time、ThreeTen 等)只是为了进行这一计算将是矫枉过正的。当然,建议使用库,但不是*必需*。 (63认同)
  • 我谨表示不同意。如果你想算,就算对;付出正确的代价。 (40认同)
  • 即使在 UTC 中,六月的最后一天有时也会短一秒,没有任何真正的警告或可预测性。始终使用日期时间库。 (27认同)
  • 我的 0.02 欧元:在 Java 7 中无需任何外部库的“正确”方法是使用 `GregorianCalendar` 对象并一次添加 1 天,直到到达结束日期。我愿意付出高昂的代价来避免这种情况。添加已成为 Java 8、9、10、11、12、13...一部分的库的反向移植并不昂贵。相反,下次你需要用日期或时间做任何事情时,它就已经是一种收获了。 (16认同)
  • @Affe Java时间API都没有处理第二次调整,所以这不是问题。他们都定义每天正好有 86400 秒。请参阅[(Oracle) Java JVM 如何知道发生闰秒?](/sf/answers/2169233461/)。 (5认同)

MC *_*ror 41

安德烈亚斯的回答中已经提到了这个问题的原因。

问题是到底想计算什么。您声明实际差异应该是 29 而不是 28,并询问“位置/区域时间是否可能是一个问题”这一事实表明您实际上想要计算什么。显然,您想摆脱任何时区差异。

我假设您只想计算天数,而没有时间和时区。

爪哇 8

在下面的示例中,如何正确计算天数之间的天数,我使用了一个准确表示该天数的类 – 一个没有时间和时区的日期 – LocalDate

DateTimeFormatter formatter = DateTimeFormatter.ofPattern("d-MM-yyyy HH:mm:ss");
LocalDate start = LocalDate.parse("1-03-2020 00:00:00", formatter);
LocalDate end = LocalDate.parse("30-03-2020 00:00:00", formatter);

long daysBetween = ChronoUnit.DAYS.between(start, end);
Run Code Online (Sandbox Code Playgroud)

需要注意的是ChronoUnitDateTimeFormatterLocalDate至少需要Java 8,这是不提供给你,根据标记。然而,这也许是为了未来的读者。

正如 Ole VV 所提到的,还有ThreeTen Backport,它将 Java 8 日期和时间 API 功能向后移植到 Java 6 和 7。

  • @OleV.V。我知道有ThreeTen,有些用户可能已经提到过几次了。(我本来打算链接到一个 SEDE 查询,返回用户 5772882 包含文本“ThreeTen”的所有帖子和评论;-),但不幸的是,在撰写本文时它处于离线状态。)我将更新该帖子。 (2认同)
  • @OleVV 这根本不是一件坏事。我认为大多数人只是对“java.time”一无所知,因为在学校他们仍然使用旧的课程。但 Java 8 日期和时间 API 设计得非常好——*不*使用它将会是一种损失。 (2认同)