计算两个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个回答

0

我曾经喜欢 TimeUnit-based 的方法,直到发现它只适用于数量上的特殊情况,即一个 TimeUnit 包含多少个下一级单位的数量固定。当你想知道两个时间单位之间有多少个月、年等时,这种方法就会失效。

这里有一种计数方法,虽然不如其他一些方法高效,但对我来说似乎很管用,并且还考虑了夏令时的影响。

public static String getOffsetAsString( Calendar cNow, Calendar cThen) {
    Calendar cBefore;
    Calendar cAfter;
    if ( cNow.getTimeInMillis() < cThen.getTimeInMillis()) {
        cBefore = ( Calendar) cNow.clone();
        cAfter = cThen;
    } else {
        cBefore = ( Calendar) cThen.clone();
        cAfter = cNow;
    }
    // compute diff
    Map<Integer, Long> diffMap = new HashMap<Integer, Long>();
    int[] calFields = { Calendar.YEAR, Calendar.MONTH, Calendar.DAY_OF_MONTH, Calendar.HOUR_OF_DAY, Calendar.MINUTE, Calendar.SECOND, Calendar.MILLISECOND};
    for ( int i = 0; i < calFields.length; i++) {
        int field = calFields[ i];
        long    d = computeDist( cAfter, cBefore, field);
        diffMap.put( field, d);
    }
    final String result = String.format( "%dY %02dM %dT %02d:%02d:%02d.%03d",
            diffMap.get( Calendar.YEAR), diffMap.get( Calendar.MONTH), diffMap.get( Calendar.DAY_OF_MONTH), diffMap.get( Calendar.HOUR_OF_DAY), diffMap.get( Calendar.MINUTE), diffMap.get( Calendar.SECOND), diffMap.get( Calendar.MILLISECOND));
    return result;
}

private static int computeDist( Calendar cAfter, Calendar cBefore, int field) {
    cBefore.setLenient( true);
    System.out.print( "D " + new Date( cBefore.getTimeInMillis()) + " --- " + new Date( cAfter.getTimeInMillis()) + ": ");
    int count = 0;
    if ( cAfter.getTimeInMillis() > cBefore.getTimeInMillis()) {
        int fVal = cBefore.get( field);
        while ( cAfter.getTimeInMillis() >= cBefore.getTimeInMillis()) {
            count++;
            fVal = cBefore.get( field);
            cBefore.set( field, fVal + 1);
            System.out.print( count + "/"  + ( fVal + 1) + ": " + new Date( cBefore.getTimeInMillis()) + " ] ");
        }
        int result = count - 1;
        cBefore.set( field, fVal);
        System.out.println( "" + result + " at: " + field + " cb = " + new Date( cBefore.getTimeInMillis()));
        return result;
    }
    return 0;
}

0
只需在每个上调用 getTime,取差值并除以一天中的毫秒数。

5
除非这些日期实例是从协调世界时派生而来,否则这是错误的。请参考Jon Skeet的答案。 - sleske

0

另一种纯Java变体:

public boolean isWithin30Days(Calendar queryCalendar) {

    // 1. Take the date you are checking, and roll it back N days
    Calendar queryCalMinus30Days = Calendar.getInstance();
    queryCalMinus30Days.setTime(queryCalendar.getTime());
    queryCalMinus30Days.add(Calendar.DATE, -30); // subtract 30 days from the calendar

    // 2. Get respective milliseconds for the two Calendars: now & queryCal minus N days 
    long nowL = Calendar.getInstance().getTimeInMillis();
    long queryCalMinus30DaysL = queryCalMinus30Days.getTimeInMillis();

    // 3. if nowL is still less than the queryCalMinus30DaysL, it means queryCalendar is more than 30 days into future
    boolean isWithin30Days = nowL >= queryCalMinus30DaysL;

    return isWithin30Days;
}

感谢这里的起始代码:https://dev59.com/Tl0a5IYBdhLWcg3wipNA#30207726


-1

由于日期可以包含小时和分钟,最终结果会被向下取整,导致值不正确。例如,你计算今天晚上 10 点后天凌晨 12 点之间的差异,那么最终的结果将是1,因为实际上它的差异是1.08或者其他东西,然后在调用TimeUnit.MILLISECONDS.toDays(..)时被向下取整。这就是为什么你需要考虑到这一点,所以在我的解决方案中,我从一天的毫秒数中减去毫秒的余数。此外,如果你想计算结束日期,你需要+1。

import java.util.Date;
import java.util.concurrent.TimeUnit;

public static long getDaysBetween(Date date1, Date date2, boolean includeEndDate) {
        long millisInDay = 60 * 60 * 24 * 1000;
        long difference = Math.abs(date1.getTime() - date2.getTime());
        long add = millisInDay - (difference % millisInDay);//is used to calculate true number of days, because by default hours, minutes are also counted

        return TimeUnit.MILLISECONDS.toDays(difference + add) + (includeEndDate ? 1 : 0);
    }

测试:

Date date1 = new Date(121, Calendar.NOVEMBER, 27); //2021 Nov 27
Date date2 = new Date(121, Calendar.DECEMBER, 29); //2021 Dec 29
System.out.println( getDaysBetween(date1, date2, false) ); //32 days difference
System.out.println( getDaysBetween(date1, date2, true) ); //33 days difference

这并不完全正确,因为一天并不总是24小时。而且没有人应该再使用DateCalendar了。 - Ole V.V.
@OleV.V. 一天有多少小时? - M22
那要看是哪个时区的哪一天呢?大多数情况下是24小时,有时候可能是23或25,也可能不是整数。例如,今年10月31日在克里斯蒂安斯塔德就有25个小时。 - Ole V.V.
我现在才注意到你正在使用3个参数的“Date”构造函数。唉!由于其在不同时区下工作不可靠,它已经被弃用了近25年。不要这样做!无论如何,我同意你的代码应该给出32和33天。但是当我运行它时,我得到了33和34。你的代码并不像你声称的那样工作。 - Ole V.V.

-1

这将与joda api完美配合使用。

 import java.util.Calendar;
    import java.util.Date;
    import java.util.GregorianCalendar;
    import java.text.ParseException;
    import java.text.SimpleDateFormat;
    import org.joda.time.DateTime;
    import org.joda.time.Months;
    import org.joda.time.Weeks;
    import org.joda.time.Years;
    import java.util.LinkedHashMap;
    
    public Map<String,Integer> Datedifference(String fromDate,String toDate) throws ParseException {    
                Map<String,Integer> dateUtilMap = new LinkedHashMap<String, Integer>();
                Date date1=new SimpleDateFormat("dd-MM-yyyy").parse(fromDate);  
                Date date2=new SimpleDateFormat("dd-MM-yyyy").parse(toDate);  
                Calendar fromDay = new GregorianCalendar();
                fromDay.setTime(date1);
                Calendar toDay = new GregorianCalendar();
                toDay.setTime(date2);
                
                int days = (int) (1+(toDay.getTimeInMillis() -fromDay.getTimeInMillis()) / 1000 / 60 / 60 / 24);
                
                if(days >1) {
                    
                DateTime dateTime1 = new DateTime(fromDay);
                DateTime dateTime2 = new DateTime(toDay);
                
                int weeks = Weeks.weeksBetween(dateTime1.dayOfWeek().withMinimumValue().minusDays(1), 
                        dateTime2.dayOfWeek().withMaximumValue().plusDays(1)).getWeeks();
                
                int months = Months.monthsBetween(dateTime1.dayOfMonth().withMinimumValue().minusDays(1),
                        dateTime2.dayOfMonth().withMaximumValue().plusDays(1)).getMonths();
                
                int years =1+Years.yearsBetween(dateTime1, dateTime2).getYears();
                
                dateUtilMap.put("days", days);
                dateUtilMap.put("weeks", weeks);
                dateUtilMap.put("months", months);
                dateUtilMap.put("years", years);
                System.out.println(dateUtilMap);
                return dateUtilMap;        
                } else {
                    System.out.println("From Date Must be samller than to Date");
                 return null;
                } 
        }

1
感谢您想要做出贡献。强烈不建议使用此代码。Joda-Time项目正在维护中,但更糟糕的是,您还在使用声名狼藉的麻烦重重的SimpleDateFormat和设计不佳、笨重的DateCalendar类。您的代码比必要的代码长得多,复杂得多,可读性差得多。而且没有正确缩进。您回答的是另一个问题。问题是关于两个Date对象(而不是两个字符串)。 - Ole V.V.
Datedifference("10-05-2023", "11-05-2023")(从今天到明天)得到了 {days=2, weeks=1, months=1, years=1}。我有点惊讶,但好吧,我想每个人都有自己对“完美”的定义。然后我尝试了 Datedifference("26-03-2023", "27-03-2023"),结果是——null?!怎么回事?我必须说,你的代码在任何定义下都是不正确的。 - Ole V.V.

-3
如果你有d1和d2作为你的日期,最好的解决方案可能是以下内容:
int days1 = d1.getTime()/(60*60*24*1000);//find the number of days since the epoch.
int days2 = d2.getTime()/(60*60*24*1000);

那就说吧

days2-days1

或者其他什么


3
-1,除非日期实例是从UTC时间派生的,否则是错误的。请参考Jon Skeet的答案。 - sleske
这是不正确的。在内部,日期实例是一个薄包装器,它代表了 POSIX Epoch 时间的 long 值。这仅对 UTC 定义。因此,dateObject.getTime() 返回的值始终为 UTC。 - scottb

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