Java TimeUnit.MILLISECONDS.toDays() 给出错误结果

问题描述 投票:0回答:5

我正在尝试计算两天之间的天数差异。由于某种原因,比较 01-03-2013 和 01-04-2013 给出的结果是 30,比较 01-03-2013 和 31-03-2013 的结果也是如此

Calendar cal = Calendar.getInstance();
cal.clear();

cal.set(2013, Calendar.MARCH, 1);
Date start = cal.getTime();

cal.set(2013, Calendar.APRIL, 1);
Date end = cal.getTime();

long days = TimeUnit.MILLISECONDS.toDays(end.getTime() - start.getTime());
System.out.println("!!! Amount of days : " + String.valueOf(days));

>> 30

cal.set(2013, Calendar.MARCH, 1);
start = cal.getTime();

cal.set(2013, Calendar.MARCH, 31);
end = cal.getTime();

days = TimeUnit.MILLISECONDS.toDays(end.getTime() - start.getTime());
System.out.println("!!! Amount of days : " + String.valueOf(days));

>> 30

这是为什么?

java date calendar timeunit
5个回答
9
投票

如果您所在时区的夏令时于 3 月 31 日开始,您将获得这些结果。

由于夏令时开始,3 月 1 日至 4 月 1 日期间,有 30 个 24 小时制天和 1 个 23 小时制天。 如果将总毫秒数除以 24 x 60 x 60 x 1000,则将得到 30 加 23/24。 该值向下舍入为 30。


1
投票

时区

David Wallace 的正确答案解释说,夏令时或其他异常会影响代码的结果。依赖默认时区(或完全忽略时区)会让您陷入这种麻烦。

使 Span 包含-排除

此外,定义时间跨度的正确方法是使开始具有包容性,使结束具有排除性。因此,如果您想要三月,您需要从第一天的第一时刻到 after 三月(4 月 1 日)的第一天的第一时刻。

要详细讨论这个想法,请参阅我的其他答案,例如 this onethis one

这是我从其他答案中摘取的图表:

Timeline showing ( >= 第一天的开始 ) 和 ( < start of day 8 )

乔达时间

与 Java 捆绑在一起的 java.util.Date/Calendar 类是出了名的麻烦。避开他们。使用 Joda-Time 或 Java 8 中的新 java.time.* 包(受 Joda-Time 启发)。

Joda-Time 2.3 库提供了专门针对时间跨度的类:Period、Duration 和 Interval。该库还有一些方便的静态实用方法,例如 Days.daysBetween



Joda-Time 的 DateTime 对象确实知道自己的

时区,不像 java.util.Date/Calendar 看起来有时区但实际上没有。

// Specify a timezone rather than rely on default. DateTimeZone timeZone = DateTimeZone.forID( "Europe/Paris" ); DateTime marchFirst = new DateTime( 2013, DateTimeConstants.MARCH, 1, 0, 0, 0, timeZone ); DateTime aprilFirst = new DateTime( 2013, DateTimeConstants.APRIL, 1, 0, 0, 0, timeZone ); int days = Days.daysBetween( marchFirst, aprilFirst).getDays();

转储到控制台...

System.out.println( "marchFirst: " + marchFirst ); System.out.println( "aprilFirst: " + aprilFirst ); // Note the change in time zone offset in the output. System.out.println( "days: " + days );

运行时,注意:

marchFirst: 2013-03-01T00:00:00.000+01:00 aprilFirst: 2013-04-01T00:00:00.000+02:00 days: 31
    

0
投票
当我在美国西海岸时区运行此版本的代码时:

java.util.Calendar cal = java.util.Calendar.getInstance(); cal.clear(); cal.set( 2013, java.util.Calendar.MARCH, 1 ); java.util.Date start = cal.getTime(); cal.set( 2013, java.util.Calendar.APRIL, 1 ); java.util.Date end = cal.getTime(); long days = java.util.concurrent.TimeUnit.MILLISECONDS.toDays( end.getTime() - start.getTime() ); System.out.println( "!!! Amount of days : " + String.valueOf( days ) ); cal.set( 2013, java.util.Calendar.MARCH, 1 ); start = cal.getTime(); cal.set( 2013, java.util.Calendar.MARCH, 31 ); end = cal.getTime(); days = java.util.concurrent.TimeUnit.MILLISECONDS.toDays( end.getTime() - start.getTime() ); System.out.println( "!!! Amount of days : " + String.valueOf( days ) );

我得到:

!!! Amount of days : 30 !!! Amount of days : 29

有关解释,请参阅 David Wallace 对

此答案的评论。

2013 年夏令时(美国)于 3 月 10 日星期日凌晨 2:00 开始。


0
投票
我在系统中执行了相同的代码,它给我的输出为:

!!! Amount of days : 31

请再次检查您的代码。


0
投票

java.time

2014 年 3 月,Java 8 引入了现代的

java.time

 日期时间 API,取代了
容易出错的遗留java.util
 日期时间 API。任何新代码都应使用 
java.time
 API。如果您收到 
java.util.Date
 的实例,请使用 
java.time.Instant
将其转换为
Date#toInstant
,并根据您的要求从中派生
java.time
 的其他日期时间类。

另外,下面是

Joda-Time主页上的通知:

请注意,从 Java SE 8 开始,用户被要求迁移到 java.time

 (JSR-310) - JDK 的核心部分,它将取代它
项目。

使用现代日期时间 API 的解决方案

使用现代日期时间 API 的解决方案

您可以使用

ChronoUnit.DAYS.between

 来计算两个日期之间的天数。

演示:

public class Main { public static void main(String[] args) { System.out.println(ChronoUnit.DAYS.between( LocalDate.of(2013, Month.MARCH, 1), LocalDate.of(2013, Month.APRIL,1))); System.out.println(ChronoUnit.DAYS.between( LocalDate.of(2013, Month.MARCH, 1), LocalDate.of(2013, Month.MARCH,31))); } }

在线演示

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

© www.soinside.com 2019 - 2024. All rights reserved.