在Java中使用秒计算日期之间的天数会导致更少的天数?

yc0p9oo0  于 2023-04-04  发布在  Java
关注(0)|答案(2)|浏览(147)

我有下面的helper方法

private Date getDate(int year, int month, int day) {
        Calendar cal = Calendar.getInstance();
        cal.set(Calendar.YEAR, year);
        cal.set(Calendar.MONTH, month);
        cal.set(Calendar.DAY_OF_MONTH, day);
        cal.setTimeZone(TimeZone.getTimeZone("GMT"));
        return cal.getTime();
    }

我公司的一个人实现了这个方法:

public static int getDaysBetween(Date d1, Date d2) {
        return (int)((d2.getTime() - d1.getTime()) / (1000 * 60 * 60 * 24));
    }

有人能解释一下为什么下面的答案是28

System.err.println(DateUtils.getDaysBetween(
                getDate(2023, 01, 01), getDate(2023, 02, 01)));
wqsoz72f

wqsoz72f1#

TL;医生

如果你真的想数秒的话。

Duration
.between(
    LocalDate
    .of( y1 , m1 , d1 )               // Returns a `LocalDate` object.
    .atStartOfDay( ZoneOffset.UTC )   // Returns a `ZonedDateTime` object.
    .toInstant() ,                    // Returns a `Instant` object.
    LocalDate
    .of( y2 , m2 , d2 ) 
    .atStartOfDay( ZoneOffset.UTC )
    .toInstant() ,
)                                     // Returns a `Duration` object.
.toSeconds()                          // Returns a `long` primitive value.

但是如果你想计算日期之间的日历天数,你工作得太辛苦了。

long daysElapsed = 
    ChronoUnit
    .DAYS
    .between(
        LocalDate.of( 2023 , 1 , 1 ) , 
        LocalDate.of( 2023 , 2 , 1 )
    )
;

详情

避免使用遗留的日期时间类

Answer by Zedki在技术上是正确的,但是使用了有严重缺陷的日期-时间类,这些类在几年前就被JSR 310定义的现代 java.time 类所取代。
Calendar类的众多缺陷之一是,它通过从零开始的索引计数来计算月数。愚蠢且令人困惑:1月是0,12月是11。
相比之下,java.time 类使用合理的计数。月份从1月到12月运行1-12。

java.time

java.util.Date类被java.time.Instant替换,两者都表示UTC中的时刻,即与UTC的偏移量为0小时-分钟-秒。
输入的year-month-day可以替换为java.time.LocalDate对象。该类表示仅日期值,没有时间,也没有时区或偏移量。
你的方法名getDate不是很有描述性。显然你想确定指定日期的第一个时刻,以UTC的形式显示(偏移量为零)。所以我们可以使用描述性名称firstMomentOfTheDayInUtc
要确定一天的第一个时刻,总是让 java.time 来完成这项工作。永远不要假设一天从00:00开始。在某些时区的某些日期的某些日子从另一个时间开始,例如01:00。在UTC中,我们确实知道根据定义每天都从00:00开始,但我们仍然可以让 java.time 来计算。我们最终得到一个ZonedDateTime对象,表示通过特定时区的挂钟时间看到的时刻。
Instant类是 java.time 的基本构建块。因此,我们从ZonedDateTime对象中提取Instant,并从方法中返回该对象。

private Instant firstMomentOfTheDayInUtc ( LocalDate localDate ) 
{
    ZonedDateTime zdt = localDate.atStartOfDay( ZoneOffset.UTC ) ;
    Instant instant = zdt.toInstant() ;
    return instant ;
}

然后你想得到两个Instant对象之间的运行时间,作为整秒的计数。不需要你的方法getDaysBetween,因为代码在 java.time 中非常简单。
Duration类表示未附加到时间轴的时间跨度。

Duration duration = Duration.between( instantX , instantY ) ;

然后询问总秒数。

long seconds = duration.toSeconds() ;

如果你只是想知道已经过去的日历天数,可以使用枚举对象ChronoUnit.DAYS和它的between方法。

long daysElapsed = 
    ChronoUnit.DAYS.between(
        LocalDate.of( 2023 , 1 , 1 ) , 
        LocalDate.of( 2023 , 2 , 1 )
    );
ncecgwcz

ncecgwcz2#

在遗留类Calendar中,月份是零索引的。这意味着1月表示为0,2月表示为1,依此类推。因此,当您调用getDate(2023,01,01)时,它实际上为2023年2月1日创建了一个java.util.Date对象,而不是2023年1月1日。
类似地,当您调用getDate(2023,02,01)时,它将创建一个2023年3月1日的java.util.Date对象。
因此,当你减去这两个日期的时间戳,再除以一天中的毫秒数,你会得到28天的结果,这是2023年2月1日和2023年3月1日之间的正确天数。
要解决此问题,您应该更改getDate方法以使用零索引月份。例如:

private Date getDate(int year, int month, int day) {
        Calendar cal = Calendar.getInstance();
        cal.set(Calendar.YEAR, year);
        cal.set(Calendar.MONTH, month - 1); // zero-indexed month
        cal.set(Calendar.DAY_OF_MONTH, day);
        cal.setTimeZone(TimeZone.getTimeZone("GMT"));
    return cal.getTime();
}

相关问题