计算两个Java日期实例之间的差异

501

我在Scala中使用Java的java.util.Date类,想要比较一个Date对象和当前时间。我知道可以通过使用getTime()计算时间差:

(new java.util.Date()).getTime() - oldDate.getTime()

然而,这只给我留下了一个表示毫秒的long。是否有更简单、更好的方法来获得时间差?


12
为什么joda time没有得到更多的喜爱?如果你需要处理Java中的日期,它几乎是最佳选择。 - Doctor Jones
9
请检查我优雅的两行代码解决方案,不使用Joda库并将结果以任何TimeUnit显示,详情请见https://dev59.com/c3I_5IYBdhLWcg3wBub4#10650881。 - Sebastien Lorber
8
谴责那些推荐Joda时间库而不推荐真正的Java解决方案的人... - Zizouz212
5
关于推荐Joda-Time,Java捆绑的旧日期时间类很糟糕,真的很糟糕,设计差、令人困惑且麻烦。这些类非常糟糕以至于Joda-Time成为它们的替代品并取得了巨大成功。甚至Sun/Oracle也放弃了它们,并采用了java.time包作为Java 8及更高版本的一部分。java.time类受到Joda-Time的启发。Joda-Time和java.time都由同一个人Stephen Colbourne领导。我想说,“谁要是推荐使用DateCalendarSimpleDateFormat就太不应该了”。 - Basil Bourque
4
当问题提出时,Joda Time可能是一个很好的答案,但如今对于任何能使用Java 8的人来说,最好的答案是使用java.time.Period和/或Duration。请查看下面的Basil Bourque的答案。 - Ole V.V.
显示剩余4条评论
46个回答

23

有很多方法可以找到日期和时间之间的差异。我知道的最简单的方法之一是:

      Calendar calendar1 = Calendar.getInstance();
      Calendar calendar2 = Calendar.getInstance();
      calendar1.set(2012, 04, 02);
      calendar2.set(2012, 04, 04);
      long milsecs1= calendar1.getTimeInMillis();
      long milsecs2 = calendar2.getTimeInMillis();
      long diff = milsecs2 - milsecs1;
      long dsecs = diff / 1000;
      long dminutes = diff / (60 * 1000);
      long dhours = diff / (60 * 60 * 1000);
      long ddays = diff / (24 * 60 * 60 * 1000);

      System.out.println("Your Day Difference="+ddays);

打印语句只是一个例子 - 您可以按照自己的喜好进行格式化。


5
欢迎来到Stack Overflow。您可以看到,这个问题和答案已经有好几年的历史了。您的回答应该比现有的回答更有价值。请在您的回答中添加更多信息以解决问题。 - Jayan
4
夏令时调整导致一天有23或25个小时的情况怎么办? - Joni

10

将日期的毫秒数相减可以奏效(正如另一篇帖子中所描述的),但是在清除日期的时间部分时,必须使用HOUR_OF_DAY而不是HOUR:

public static final long MSPERDAY = 60 * 60 * 24 * 1000;
...
final Calendar dateStartCal = Calendar.getInstance();
dateStartCal.setTime(dateStart);
dateStartCal.set(Calendar.HOUR_OF_DAY, 0); // Crucial.
dateStartCal.set(Calendar.MINUTE, 0);
dateStartCal.set(Calendar.SECOND, 0);
dateStartCal.set(Calendar.MILLISECOND, 0);
final Calendar dateEndCal = Calendar.getInstance();
dateEndCal.setTime(dateEnd);
dateEndCal.set(Calendar.HOUR_OF_DAY, 0); // Crucial.
dateEndCal.set(Calendar.MINUTE, 0);
dateEndCal.set(Calendar.SECOND, 0);
dateEndCal.set(Calendar.MILLISECOND, 0);
final long dateDifferenceInDays = ( dateStartCal.getTimeInMillis()
                                  - dateEndCal.getTimeInMillis()
                                  ) / MSPERDAY;
if (dateDifferenceInDays > 15) {
    // Do something if difference > 15 days
}

1
这是确定两个日期之间差异的最佳方法,而无需使用标准库之外的方法。大多数其他答案将一天视为任意的24小时期间。如果减去闰秒而不是增加,由于截断,最终计算可能会有误差,因为绝对差异可能略小于MSPERDAY的整数倍。 - JulianSymes

9
如果您不想使用JodaTime或类似的工具,最好的解决方案可能是这个:
final static long MILLIS_PER_DAY = 24 * 3600 * 1000;
long msDiff= date1.getTime() - date2.getTime();
long daysDiff = Math.round(msDiff / ((double)MILLIS_PER_DAY));

每天的毫秒数并不总是相同的(因为夏令时和闰秒),但非常接近,而且在较长时间内由于夏令时的偏差会抵消。因此,进行除法然后四舍五入将得到正确的结果(至少只要使用的本地日历不包含除DST和闰秒之外的奇怪时间跳跃)。
请注意,这仍然假定date1date2设置为同一天的相同时间。对于不同时间的日期差异,您首先必须定义“日期差异”的含义,正如Jon Skeet所指出的那样。

问题出在date1.getTime()date2.getTime()。因此,将2016-03-03和2016-03-31做差,将得到27天的计算结果,而你实际上需要28天。 - malat
@malat:抱歉,我不明白。我刚刚尝试了一下,用我的代码将2016年3月3日到2016年3月31日进行比较,得出的结果是28天。如果在你的例子中得出了其他结果,请考虑将其作为单独的问题提出。 - sleske
另外,你有看到我的警告吗?“这仍然假设date1和date2设置为同一天的相同时间”。你在测试中可能使用了不同的时间吗? - sleske
啊,我错过了使用 Math.round() 的技巧,这在“现实世界”的日历中似乎是安全的。抱歉打扰了。 - malat

8

请看 Joda Time,它是Java的改进日期/时间API,应该能与Scala很好地配合使用。


Java 8中已经被java.time (JSR-310)所取代。 - malat

5
注意:startDate和endDate是java.util.Date类型。
import org.joda.time.Duration;
import org.joda.time.Interval;
// Use .getTime() unless it is a joda DateTime object
Interval interval = new Interval(startDate.getTime(), endDate.getTime());
Duration period = interval.toDuration();
//gives the number of days elapsed between start and end date.
period.getStandardDays();

与天数类似,您也可以获得小时、分钟和秒数。
period.getStandardHours();
period.getStandardMinutes();
period.getStandardSeconds();

5

让我展示一下Joda Interval和Days之间的区别:

DateTime start = new DateTime(2012, 2, 6, 10, 44, 51, 0);
DateTime end = new DateTime(2012, 2, 6, 11, 39, 47, 1);
Interval interval = new Interval(start, end);
Period period = interval.toPeriod();
System.out.println(period.getYears() + " years, " + period.getMonths() + " months, " + period.getWeeks() + " weeks, " + period.getDays() + " days");
System.out.println(period.getHours() + " hours, " + period.getMinutes() + " minutes, " + period.getSeconds() + " seconds ");
//Result is:
//0 years, 0 months, *1 weeks, 1 days*
//0 hours, 54 minutes, 56 seconds 

//Period can set PeriodType,such as PeriodType.yearMonthDay(),PeriodType.yearDayTime()...
Period p = new Period(start, end, PeriodType.yearMonthDayTime());
System.out.println(p.getYears() + " years, " + p.getMonths() + " months, " + p.getWeeks() + " weeks, " + p.getDays() + "days");
System.out.println(p.getHours() + " hours, " + p.getMinutes() + " minutes, " + p.getSeconds() + " seconds ");
//Result is:
//0 years, 0 months, *0 weeks, 8 days*
//0 hours, 54 minutes, 56 seconds 

5
如果你需要像“2天03小时42分钟07秒”这样格式化的返回字符串,可以尝试以下方法:
public String fill2(int value)
{
    String ret = String.valueOf(value);

    if (ret.length() < 2)
        ret = "0" + ret;            
    return ret;
}

public String get_duration(Date date1, Date date2)
{                   
    TimeUnit timeUnit = TimeUnit.SECONDS;

    long diffInMilli = date2.getTime() - date1.getTime();
    long s = timeUnit.convert(diffInMilli, TimeUnit.MILLISECONDS);

    long days = s / (24 * 60 * 60);
    long rest = s - (days * 24 * 60 * 60);
    long hrs = rest / (60 * 60);
    long rest1 = rest - (hrs * 60 * 60);
    long min = rest1 / 60;      
    long sec = s % 60;

    String dates = "";
    if (days > 0) dates = days + " Days ";

    dates += fill2((int) hrs) + "h ";
    dates += fill2((int) min) + "m ";
    dates += fill2((int) sec) + "s ";

    return dates;
}

2
哇,为什么要自己写代码,当Joda-Time已经提供了已经编写和调试好的Period类呢? - Basil Bourque
8
当我只需要32行代码时,为什么要下载一个压缩文件大小为4.1 MB的库并将其添加到我的项目中? - Ingo
1
因为Joda-Time就像薯片一样:你不能只吃一个。你会在很多地方使用Joda-Time。而且Joda-Time经过了充分的测试和使用,代码非常成熟。此外,Joda-Time可以解析和生成标准的ISO 8601 Duration字符串,这对于序列化或报告这些值可能非常方便。还有,Joda-Time包括格式化程序,以打印这些值的本地化表示。 - Basil Bourque
@Basil Bourque:谢谢提示。我已经更正了源代码。这个错误是翻译成英语的结果。 - Ingo
我刚刚测试了你的代码(将“std”更改为“hrs”),并与Joda-Time进行了比较。结果很好,你的代码在我的每个测试用例中都与Joda-Time一致。但我仍然建议不要自己编写日期时间库(因为风险太大,不是你时间的好利用方式,而且Joda-Time具有许多理想的功能,例如轻松处理时区)。 - Basil Bourque
显示剩余2条评论

5
int daysDiff = (date1.getTime() - date2.getTime()) / MILLIS_PER_DAY;

5
除非这些 Date 实例是由 UTC 时间派生的,否则这是错误的。请参考 Jon Skeet 的答案。 - sleske
5
@sleske,你是不对的。他已经通过使用Date失去了时区信息,因此在这种情况下,这种比较方式是可以达到的最佳结果。 - Bozho
1
好的,我猜我们都是对的。确实取决于日期实例来自哪里。不过,这是一个非常常见的错误,应该提一下。 - sleske
当我使用你的解决方案时,它显示:类型不匹配:无法将long转换为int。我认为你应该添加强制转换,或者是我漏掉了什么? - Ad Infinitum

5

在浏览了其他答案后,为了保留Java 7的日期类型,同时更加精确/标准地使用Java 8的diff方法,

public static long daysBetweenDates(Date d1, Date d2) {
    Instant instant1 = d1.toInstant();
    Instant instant2 = d2.toInstant();
    long diff = ChronoUnit.DAYS.between(instant1, instant2);
    return diff;
}

4
public static String getDifferenceBtwTime(Date dateTime) {

    long timeDifferenceMilliseconds = new Date().getTime() - dateTime.getTime();
    long diffSeconds = timeDifferenceMilliseconds / 1000;
    long diffMinutes = timeDifferenceMilliseconds / (60 * 1000);
    long diffHours = timeDifferenceMilliseconds / (60 * 60 * 1000);
    long diffDays = timeDifferenceMilliseconds / (60 * 60 * 1000 * 24);
    long diffWeeks = timeDifferenceMilliseconds / (60 * 60 * 1000 * 24 * 7);
    long diffMonths = (long) (timeDifferenceMilliseconds / (60 * 60 * 1000 * 24 * 30.41666666));
    long diffYears = (long)(timeDifferenceMilliseconds / (1000 * 60 * 60 * 24 * 365));

    if (diffSeconds < 1) {
        return "one sec ago";
    } else if (diffMinutes < 1) {
        return diffSeconds + " seconds ago";
    } else if (diffHours < 1) {
        return diffMinutes + " minutes ago";
    } else if (diffDays < 1) {
        return diffHours + " hours ago";
    } else if (diffWeeks < 1) {
        return diffDays + " days ago";
    } else if (diffMonths < 1) {
        return diffWeeks + " weeks ago";
    } else if (diffYears < 12) {
        return diffMonths + " months ago";
    } else {
        return diffYears + " years ago";
    }
}   

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