【问题标题】:Why does the difference between 30 March and 1 March 2020 erroneously give 28 days instead of 29?为什么 2020 年 3 月 30 日和 2020 年 3 月 1 日之间的差异错误地给出了 28 天而不是 29 天?
【发布时间】:2020-05-21 02:25:26
【问题描述】:
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)

结果是 28,而应该是 29。

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

【问题讨论】:

  • 注意:请不要再使用SimpleDateFormat,因为它已经过时了。请改用来自java.time 的包。在SimpleDateFormat 的情况下,使用DateTimeFormatter。如果是 Java 7,请参阅下面的 Andy Turner 的评论。
  • 不要按时做数学。使用适当的时间库(java.time [虽然我注意到您使用的是 Java 7],ThreeTenBp,Joda)。
  • 我很想参加有人去的会议“好的,现在我们已经确定了时区,让我们采用 100% 的愚蠢模式并实施这个叫做夏令时的东西昨晚酸痛之后,我在梦中。”
  • @gmauch TimeUnit 不会假装对夏令时一无所知。正如javadoc所说:纳秒定义为千分之一微秒,微秒定义为千分之一毫秒,毫秒定义为千分之一秒,一分钟定义为六十秒,一小时定义为六十分钟,以及一天二十四小时 --- 由于 DST 导致一年中的 2 天不完全是 24 小时,所以当涉及 DST 时TimeUnit 会出错。
  • 此问题仅发生在处于夏令时时区的计算机上。它在没有夏令时的时区给出正确的天数 (29)!

标签: java date java-7 datediff


【解决方案1】:

问题在于,由于夏令时转换(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);

输出

2502000000
Days: 28
Hours: 695
Days: 28.958333333333332

要解决此问题,请使用没有 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();

输出

2505600000
Days: 29
Hours: 696
Days: 29.0

【讨论】:

  • “修复”不要用时间计算。使用适当的日期/时间库。
  • @AndyTurner 修复,使用内置的 Java 7 API。由于它可以被修复,因此显示如何是一个有效的答案。强迫某人包含一个完整的库(Joda-Time、ThreeTen 等)只是为了进行这一计算将是矫枉过正。当然,建议使用库,但不是必需
  • 我不同意。如果你想做一个计算,那就做对吧;为做正确的事付出代价。
  • 我的 0.02 欧元:在没有任何外部库的情况下,在 Java 7 中“正确”的做法是使用 GregorianCalendar 对象并一次添加 1 天,直到到达结束日期。我会付出高昂的代价来避免这种情况。并且添加已经是 Java 8、9、10、11、12、13 的一部分的库的后向移植,……的代价并不高。相反,下次你需要对日期或时间做任何事情时,它已经是一个收获。
  • 即使在 UTC 中,6 月的最后一天偶尔也会短一秒,没有任何真正的警告或可预测性。始终使用日期时间库。
【解决方案2】:

Andreas's answer中已经提到了这个问题的原因。

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

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

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

请注意,ChronoUnitDateTimeFormatterLocalDate 至少需要 Java 8,根据 标签,您无法使用该版本。但是,它可能是面向未来的读者的。

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

【讨论】:

  • @OleV.V.我知道有 ThreeTen,有些用户可能已经提到过几次。 (我正要链接到一个 SEDE 查询,返回用户 5772882 的所有帖子和 cmets,其中包含文本 ThreeTen ;-),但不幸的是,在撰写本文时它处于离线状态。)我会更新帖子。
  • @OleVV 这根本不是一件坏事。我想大多数人根本不知道java.time,因为在学校他们仍然使用旧课程。但是 Java 8 日期和时间 API 设计得非常好 - 使用它将是一种损失。
猜你喜欢
  • 1970-01-01
  • 2021-01-15
  • 2021-09-01
  • 2022-07-20
  • 1970-01-01
  • 1970-01-01
  • 2020-12-07
  • 1970-01-01
  • 2021-03-22
相关资源
最近更新 更多