Java日历:获取两个日期/时间之间的差异-少了一个

12

我看到许多有关此主题的问题和答案,但没有人解决我的特定问题。我扩展了java Calendar类(标准 - 没有第三方库),需要找到两个任意日期之间的天数差异。

方法:

  1. 将两个日期的时间更改为午夜。
  2. 将日期转换为毫秒。
  3. 找到两个日期之间的差异。
  4. 将结果除以一天中的毫秒数(24 * 60 * 60 * 1000)。
  5. 结果应该是天数的差异。

有时候是这样,有时候不是这样。 即使在同一日期上的测试也可能相差一天。 发生了什么?


顺便说一下,扩展 Calendar 而不是生成一个操作 Calendar 实例的辅助库是否是一个好主意呢? - Duncan Jones
2
一个问题是你假设一天有86400000毫秒。 - jarnbjo
7个回答

14

Joda Time库对此类问题提供了非常好的支持:

LocalDate d1 = new LocalDate(calendar1.getTimeInMillis());
LocalDate d2 = new LocalDate(calendar2.getTimeInMillis());
int days = Days.daysBetween(d1, d2).getDays();

更新(来自@basil-bourque的反馈):

从Java 8开始,新的时间库java.time被引入,现在有一种类似的选项可以不依赖外部库来实现:

int days = Duration.between(calendar1.toInstant(), calendar2.toInstant()).toDays();

这真的是一个很好的解决方案,我认为考虑一些外部库是明智的,特别是像JodaTime这样小巧精致的库。它完美地工作并且能够提供你想要的一切。 - user1052080
FYI,Joda-Time 项目现已进入 维护模式,团队建议迁移到 java.time 类。 - Basil Bourque
@BasilBourque 谢谢,已添加使用 JRE 8 库中的 java.time 版本。 - Arne Burmeister
LocalDateTime 不适合这种情况。该类有意丢失所有偏移和时区信息。因此,您将使用通用的24小时制天数,并忽略表示夏令时(DST)等时区中的异常情况。请参见我的答案获取详细信息。 - Basil Bourque
对我没用。它说.toInstant()不存在!它必须是哪个日历? - doctorram
@doctorram java.util.Calendar.toInstant() 可在Java 8运行时库中使用。 - Arne Burmeister

6

正如其他用户建议的那样,您还需要将日历的毫秒值设置为零,以仅比较日期。可以使用以下代码片段实现:

someCalendar.set(Calendar.MILLISECOND, 0)

另外,请注意时区的变化(例如从冬令时到夏令时的转换)可能导致一天内的毫秒数少于或多于86,400,000。


1
优雅的解决方案!我应该更仔细地阅读文档(就像我们都有时间一样!)。 - SMBiggs
FYI,诸如 java.util.Calendar 的老旧日期时间类现在已经成为遗留系统,被 java.time 类所取代。 - Basil Bourque

6

简而言之

ChronoUnit.DAYS.between( then , now )

天真的计算

在你的代码中,你假设每一天都是精确的二十四小时。这是不正确的。例如夏令时(DST)等异常情况意味着一天可能会有23个小时、25个小时或其他数字。时区的意义在于跟踪这些异常的历史记录。

此外,你假设一天从午夜开始。这也是不正确的。由于异常情况,一些日子从其他时间开始,比如01:00

避免使用旧的日期时间类

你正在使用麻烦的旧日期时间类,例如 java.util.Datejava.util.Calendarjava.text.SimpleTextFormat,现在已经被 遗弃,被 java.time 类所取代。

时区

请以continent/region的格式指定正确的时区名称,例如America/MontrealAfrica/CasablancaPacific/Auckland。永远不要使用三到四个字母的缩写,比如ESTIST,因为它们不是真正的时区,没有标准化,甚至不唯一(!)。

不要扩展java.time

不要扩展(子类化)java.time类(它们被标记为final)。

也不要将其接口泛化为您的业务逻辑;坚持使用该框架的具体类。虽然在其他框架中泛化是有意义的,比如Java Collections,但在java.time中不是这样。

使用java.time

这项工作使用java.time类会更加容易。 ZonedDateTime代表时间线上带有指定时区(ZoneId)的时刻。
ZoneId z = ZoneId.of( "America/Montreal" );

// of(int year, int month, int dayOfMonth, int hour, int minute, int second, int nanoOfSecond, ZoneId zone)
ZonedDateTime then = ZonedDateTime.of( 2017 , 1 , 23 , 12 , 34 , 56 , 123456789 , z );

ZonedDateTime now = ZonedDateTime.now( z );

ChronoUnit 枚举可以计算一对时刻之间的经过时间。

long days = ChronoUnit.DAYS.between( then , now );

请查看这段代码在IdeOne.com上的运行情况

then.toString(): 2017-01-23T12:34:56.123456789-05:00[America/Montreal]

now.toString(): 2017-03-01T21:26:04.884-05:00[America/Montreal]

days: 37

将旧实例转换为java.time

如果您有GregorianCalendar对象,请使用添加到旧类中的新方法将其转换为java.time。

ZonedDateTime zdt = myGregCal.toZonedDateTime() ;

如果您知道一个 Calendar 实例实际上是一个 GregorianCalendar,请将其转换。
GregorianCalendar myGregCal = (GregorianCalendar) myCal ;

半开区间

java.time类采用半开区间的方式定义时间跨度,即起始时间是包含在内的,而结束时间是不包含在内的。


关于 java.time

java.time 框架已内置于 Java 8 及以上版本。这些类替代了老旧的 遗留 日期时间类,例如 java.util.DateCalendarSimpleDateFormat

Joda-Time 项目现在处于维护模式,建议迁移到 java.time 类。

要了解更多信息,请参见Oracle教程。在Stack Overflow上搜索许多示例和解释。规范是JSR 310

如何获取java.time类?

ThreeTen-Extra项目扩展了java.time的附加类。该项目是java.time可能未来添加的试验场。你可能会在这里找到一些有用的类,例如Interval, YearWeek, YearQuarter更多


4

我已经写了一种更简单的方法来解决这个问题,即使我也遇到了某些日期多出一天的问题。

这是我的当前方法,

    public long getDays(long currentTime, long endDateTime) {

    Calendar endDateCalendar;
    Calendar currentDayCalendar;


    //expiration day
    endDateCalendar = Calendar.getInstance(TimeZone.getTimeZone("EST"));
    endDateCalendar.setTimeInMillis(endDateTime);
    endDateCalendar.set(Calendar.MILLISECOND, 0);
    endDateCalendar.set(Calendar.MINUTE, 0);
    endDateCalendar.set(Calendar.HOUR, 0);

    //current day
    currentDayCalendar = Calendar.getInstance(TimeZone.getTimeZone("EST"));
    currentDayCalendar.setTimeInMillis(currentTime);
    currentDayCalendar.set(Calendar.MILLISECOND, 0);
    currentDayCalendar.set(Calendar.MINUTE, 0);
    currentDayCalendar.set(Calendar.HOUR, 0);

    long remainingDays = Math.round((float) (endDateCalendar.getTimeInMillis() - currentDayCalendar.getTimeInMillis()) / (24 * 60 * 60 * 1000));

    return remainingDays;
}

之前我使用了这段代码,其余部分相同:

long remainingDays = TimeUnit.MILLISECONDS.toDays(
            Math.abs(expiryCalendar.getTimeInMillis() - currentDayCalendar.getTimeInMillis()));

但是对我来说似乎没有起作用。

我做了这个:double remainingDays = (cal.getTimeInMillis() - calNow.getTimeInMillis()) / (24.0 * 60 * 60 * 1000); - Felipe

3

在第一步中,您通过将时间设置为午夜来识别问题:这确保了所有小时、分钟和秒都为零。但是您还没有走得远!您还必须确保毫秒也被归零。

下面是一些代码来确保这一点。

protected long truncate_to_seconds (long date_in_millis) {
    long l = date_in_millis / 1000L;
    l *= 1000L;
    return l;
} 

2
不需要使用truncate方法,他可以使用set(Calendar.MILLISECOND, 0)将毫秒数归零。 - Duncan Jones
@DuncanJones 更好了!我没有看到日历库的这个方面,谢谢!! - SMBiggs
@DuncanJones:请将此作为答案添加,以便我可以给您适当的荣誉。 - SMBiggs

1

我认为在计算差异之前,您需要从日期中截取时间部分,如下所示:

   DateFormat formatter= new SimpleDateFormat("MM/dd/yyyy");
   String truncatedDateString1 = formatter.format(date1);
   Date truncatedDate1 = formatter.parse(truncatedDateString1);

   String truncatedDateString2 = formatter.format(date2);
   Date truncatedDate2 = formatter.parse(truncatedDateString2);

   long timeDifference = truncatedDate2.getTime()- truncatedDate1.getTime();

   int daysInBetween = timeDifference / (24*60*60*1000);

希望这个有效。

0

我已按照您的要求编写了函数。我使用了日历,将所有时间部分设置为0后再进行比较。

    int countDays(Date dateBegin, Date dateEnd) {
    if (dateBegin == null || dateEnd == null)
        return 0;
    Calendar from = asCalendar(dateBegin); // asCalendar() Initialise a Calendar from a Date
    Calendar to = asCalendar(dateEnd);
    // Set the time part to 0
    from.set(Calendar.MILLISECOND, 0);
    from.set(Calendar.SECOND, 0);
    from.set(Calendar.MINUTE, 0);
    from.set(Calendar.HOUR_OF_DAY, 0);
    to.set(Calendar.MILLISECOND, 0);
    to.set(Calendar.SECOND, 0);
    to.set(Calendar.MINUTE, 0);
    to.set(Calendar.HOUR_OF_DAY, 0);
    int nbJours = 0;
    for (Calendar c = from ; c.before(to) ; c.add(Calendar.DATE, +1))
    {
        nbJours++;
    }
    for (Calendar c = from ; c.after(to) ; c.add(Calendar.DATE, -1))
    {
        nbJours--;
    }
    return nbJours;
    }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接