为什么2020年3月30日与2020年3月1日之间的差额错误地赋予了28天而不是29天?


124
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。

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


17
注意:请不要再使用了SimpleDateFormat,因为它已经过时了。使用包来自java.time。如果SimpleDateFormat是,请使用DateTimeFormatter。如果是Java 7,请参见下面的Andy Turner的评论。
MC皇帝

28
不要准时做数学。使用适当的时间库((java.time尽管我注意到您使用的是Java 7),ThreeTenBp和Joda)。
安迪·特纳

14
我很想参加一个有人去的会议,“好吧,现在我们已经确定了时区,让我们走100%的arse模式并实现这种称为夏令时的事情,这是我酸之旅之后梦a以求的事情昨晚。”
MonkeyZeus

5
@gmauch TimeUnit并不假装对DST有任何了解。如javadoc所说:纳秒定义为千分之一微秒,一毫秒定义为千分之一毫秒,一毫秒定义为千分之一秒,一分钟定义为六十秒,一小时定义为六十分钟,一天定义为二十四小时---由于DST导致一年中的2天不完全是24小时,TimeUnit因此在涉及DST时会出错。
安德里亚斯

1
仅在具有夏时制的时区中的计算机上会出现此问题。它在没有夏令时的时区中给出正确的天数(29)!
Gopinath

Answers:


207

问题在于,由于夏令时的改变(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

121
“要解决”不要与时俱进。使用正确的日期/时间库。
安迪·特纳

62
@AndyTurner使用内置的Java 7 API进行修复。由于可以固定,因此显示有效答案。强迫某人仅包括一个完整的库(Joda-Time,ThreeTen等)来进行一次计算就太过分了。当然,建议使用库,但不是必需的
安德烈亚斯(Andreas)

38
我谨不同意。如果要进行计算,请正确执行;付出正确的代价。
安迪·特纳

16
我的0.02欧元:在Java 7中无需任何外部库的“正确”方法是使用一个GregorianCalendar对象,并每次添加1天,直到达到结束日期为止。我会付出高昂的代价来避免这种情况。并添加已经是Java 8、9、10、11、12、13…一部分的库的backport并不昂贵。相反,下次您需要对日期或时间做任何事情时,这已经是一个收获。
Ole VV

27
即使在UTC,六月的最后一天有时也太短了一秒钟,而没有任何实际的警告或可预测性。始终使用日期时间库。
是Affe

41

这个问题的原因已经在Andreas的答案中提到。

问题是您到底想算什么。您说出实际的差异应该是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);

请注意,根据标记ChronoUnitDateTimeFormatter并且LocalDate至少需要Java 8,您无法使用Java 8 。但是,也许是将来的读者。

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


2
@ OleV.V。我知道这里有ThreeTen,有些用户可能提到过几次。(我正要链接到SEDE查询,该查询将返回用户5772882的所有帖子和评论;其中包含文本ThreeTen;-),但是不幸的是,在撰写本文时,它处于脱机状态。)我将更新该帖子。
MC皇帝

2
@OleVV根本不是一件坏事。我认为大多数人对此一无所知java.time,因为在学校他们仍然使用旧课程。但是Java 8 Date and Time API的设计非常好– 使用它会造成损失。
MC皇帝
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.