Android/Java - 日期差异(以天为单位)

81

我正在使用以下代码获取当前日期(以格式 12/31/1999,即月/日/年):

Textview txtViewData;
txtViewDate.setText("Today is " +
        android.text.format.DateFormat.getDateFormat(this).format(new Date()));

我有另一个日期的格式为:2010-08-25(即yyyy/mm/dd),

我想以天数的形式找出两个日期之间的差异,如何计算天数差?

换句话说,我想找到当前日期 - yyyy/mm/dd 格式日期之间的差异。


这段代码使用了已经被java.time类所取代的老旧日期时间类,对于较旧版本的Java和Android,请参考ThreeTen-BackportThreeTenABP项目。 - Basil Bourque
类似的问题,但是使用时间段而不是整个日期:在Android中计算日期之间的天数差 - Basil Bourque
18个回答

126

这并不是一个可靠的方法,最好使用JodaTime

  Calendar thatDay = Calendar.getInstance();
  thatDay.set(Calendar.DAY_OF_MONTH,25);
  thatDay.set(Calendar.MONTH,7); // 0-11 so 1 less
  thatDay.set(Calendar.YEAR, 1985);

  Calendar today = Calendar.getInstance();

  long diff = today.getTimeInMillis() - thatDay.getTimeInMillis(); //result in millis

这里提供一个近似值……

long days = diff / (24 * 60 * 60 * 1000);

要从字符串中解析日期,可以使用

  String strThatDay = "1985/08/25";
  SimpleDateFormat formatter = new SimpleDateFormat("yyyy/MM/dd");
  Date d = null;
  try {
   d = formatter.parse(strThatDay);//catch exception
  } catch (ParseException e) {
   // TODO Auto-generated catch block
   e.printStackTrace();
  } 


  Calendar thatDay = Calendar.getInstance();
  thatDay.setTime(d); //rest is the same....

虽然,由于您确定了日期格式... 您还可以对其子字符串执行Integer.parseInt()来获取它们的数字值。


1
@stOle 没有得到准确的答案,也许是你代码中的小错误,即使我设置了字符串 strThatDay = "2010/10/03";,我也得到了 274 天的差距,它应该只有 1 天,感谢你的支持。 - Paresh Mayani
1
@Gevorg,我确实推荐了它。 :) 我喜欢JodaTime。 - st0le
你还需要考虑到一个事实,即在Calendar.MONTH中,一月用月份0而不是1来表示 :) - alexm
3
由于毫秒数的除法运算可能存在舍入问题(或缺乏舍入),因此有时候这段代码会出现一天的误差。下面的代码可以解决这个问题:Math.round(millisBetweenDates * 1f / TimeUnit.MILLISECONDS.convert(1, TimeUnit.DAYS)); - Ciske
FYI,旧的日期时间类(如java.util.Datejava.util.Calendarjava.text.SimpleDateFormat)现在已经被遗弃,并由java.time类所取代。Java 6和Java 7中的许多java.time功能已经被移植到ThreeTen-Backport项目中。该项目已进一步适配早期的Android版本,其中包括ThreeTenABP。请查看如何使用ThreeTenABP…… - Basil Bourque
显示剩余7条评论

83

这不是我的作品,我在这里找到了答案。未来不想有损坏的链接 :)

关键是要考虑夏令时设置,参见完整代码。

TimeZone.setDefault(TimeZone.getTimeZone("Europe/London"));

或者尝试将 TimeZone 作为参数传递给 daysBetween() 方法,并在 sDateeDate 对象上调用 setTimeZone() 方法。

那么就这样吧:

public static Calendar getDatePart(Date date){
    Calendar cal = Calendar.getInstance();       // get calendar instance
    cal.setTime(date);      
    cal.set(Calendar.HOUR_OF_DAY, 0);            // set hour to midnight
    cal.set(Calendar.MINUTE, 0);                 // set minute in hour
    cal.set(Calendar.SECOND, 0);                 // set second in minute
    cal.set(Calendar.MILLISECOND, 0);            // set millisecond in second
    
    return cal;                                  // return the date part
}

这里获取的getDatePart()。

/**
 * This method also assumes endDate >= startDate
**/
public static long daysBetween(Date startDate, Date endDate) {
  Calendar sDate = getDatePart(startDate);
  Calendar eDate = getDatePart(endDate);

  long daysBetween = 0;
  while (sDate.before(eDate)) {
      sDate.add(Calendar.DAY_OF_MONTH, 1);
      daysBetween++;
  }
  return daysBetween;
}

细节注意: 计算两个日期之间的差异并不像减去两个日期并将结果除以(24 * 60 * 60 * 1000)那样简单。事实上,这是错误的!

例如: 03/24/2007和03/25/2007之间的差异应该是1天;然而,在英国,使用上述方法,你会得到0天!

请自行查看以下代码。使用毫秒方式进行计算会导致四舍五入误差,特别是在出现夏令时等小问题时变得最明显。

完整代码:

import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.TimeZone;

public class DateTest {

public class DateTest {

static SimpleDateFormat sdf = new SimpleDateFormat("dd-MMM-yyyy");

public static void main(String[] args) {

  TimeZone.setDefault(TimeZone.getTimeZone("Europe/London"));

  //diff between these 2 dates should be 1
  Date d1 = new Date("01/01/2007 12:00:00");
  Date d2 = new Date("01/02/2007 12:00:00");

  //diff between these 2 dates should be 1
  Date d3 = new Date("03/24/2007 12:00:00");
  Date d4 = new Date("03/25/2007 12:00:00");

  Calendar cal1 = Calendar.getInstance();cal1.setTime(d1);
  Calendar cal2 = Calendar.getInstance();cal2.setTime(d2);
  Calendar cal3 = Calendar.getInstance();cal3.setTime(d3);
  Calendar cal4 = Calendar.getInstance();cal4.setTime(d4);

  printOutput("Manual   ", d1, d2, calculateDays(d1, d2));
  printOutput("Calendar ", d1, d2, daysBetween(cal1, cal2));
  System.out.println("---");
  printOutput("Manual   ", d3, d4, calculateDays(d3, d4));
  printOutput("Calendar ", d3, d4, daysBetween(cal3, cal4));
}


private static void printOutput(String type, Date d1, Date d2, long result) {
  System.out.println(type+ "- Days between: " + sdf.format(d1)
                    + " and " + sdf.format(d2) + " is: " + result);
}

/** Manual Method - YIELDS INCORRECT RESULTS - DO NOT USE**/
/* This method is used to find the no of days between the given dates */
public static long calculateDays(Date dateEarly, Date dateLater) {
  return (dateLater.getTime() - dateEarly.getTime()) / (24 * 60 * 60 * 1000);
}

/** Using Calendar - THE CORRECT WAY**/
public static long daysBetween(Date startDate, Date endDate) {
  ...
}

输出:

手动计算 - 从 2007 年 1 月 1 日到 2007 年 1 月 2 日之间的天数为:1

日历计算 - 从 2007 年 1 月 1 日到 2007 年 1 月 2 日之间的天数为:1


手动计算 - 从 2007 年 3 月 24 日到 2007 年 3 月 25 日之间的天数为:0

日历计算 - 从 2007 年 3 月 24 日到 2007 年 3 月 25 日之间的天数为:1


同意。使用顶层方法可以获得更可靠、更优雅的解决方案。 - Roger Alien
对于方法:daysBetween,如果日期是2012年7月24日15:00,endDate是2012年7月24日16:00,则日期在endDate之前,但不是整整一天,而只是一个小时。我有什么遗漏吗?或者对于这种情况,daysBetween的结果会出错(因为预期结果为零,但根据给定的计算应该为1而不是零)? - AgentKnopf
@Zainodis,从我的记忆中,我已经更新了代码。我猜这应该解决了问题。 - Samuel
@SamQuest 谢谢你的更新!我采用了更加天真的方法:当开始时间和结束时间在同一天、同一月、同一年时,while循环将停止并返回结果。这也确保了,如果在第一次迭代中开始时间和结束时间在同一天/月/年(尽管从时间上看开始时间在结束时间之前),那么零将被正确返回。 - AgentKnopf
您,先生,值得一份掌声! - marienke
如果您想比较日期并忽略年份,例如计算距离生日或周年纪念日还有多少天,可以在getDatePart中添加cal.set(Calendar.YEAR, Calendar.YEAR);。在daysBetween中的Calendar eDate = getDatePart(endDate);这一行后面添加if (sDate.after(eDate)) {return -1;}。对daysBetween的更改是返回标志-1,表示该日期已经过去了。 - E. A. Bagby

38

大多数答案都很好,对于你的问题:

我想找到日期之间的天数差异,怎样计算日期之间的天数差异?

我建议采用非常简单和直接的方法来计算日期之间的差异,并且可以在任何时区保证给出正确的差异:

int difference= 
((int)((startDate.getTime()/(24*60*60*1000))
-(int)(endDate.getTime()/(24*60*60*1000))));

就是这样!


这对我也起作用了。其他的方法太复杂,而且太准确了 :) - Siddharth
1
最好先减去再除以,以避免重复除以。 - ravindu1024
@ravindu1024 这样做会使得如果startDate小于endDate,则差异为+1。在这种情况下,存在+1的差异。可以通过将答案加上-1来解决。 - sHOLE
@sHOLE 怎么做?我的意思是你应该计算 (t1-t2)/C 而不是 t1/C - t2/C。因为 t1/C 和 t2/C 都不会为零,我看不出这样做会影响答案。 - ravindu1024
@ravindu1024 我理解你的意思,并在阅读这个答案时也有同样的疑问。只有在实现后,我才注意到为什么没有那样做(我上面提到的原因)。 - sHOLE

25

3
谢谢支持,但如果使用Android/JAVA代码可以完成的话,不愿意使用其他API。 - Paresh Mayani
2
+1 for Joda。Java日历API非常混乱,而Joda则干净美观。 - LuxuryMode
JodaTime在Android的多个设备上存在多个错误,我不知道为什么,我遇到了几个问题。 - josemwarrior
1
Joda时间库将向您的项目添加4744个方法。如果要避免65K方法限制,请明智选择。 - Lior Iluz

14

这段代码考虑到了夏令时问题,时间复杂度为O(1)。

private final static long MILLISECS_PER_DAY = 24 * 60 * 60 * 1000;

private static long getDateToLong(Date date) {
    return Date.UTC(date.getYear(), date.getMonth(), date.getDate(), 0, 0, 0);
}

public static int getSignedDiffInDays(Date beginDate, Date endDate) {
    long beginMS = getDateToLong(beginDate);
    long endMS = getDateToLong(endDate);
    long diff = (endMS - beginMS) / (MILLISECS_PER_DAY);
    return (int)diff;
}

public static int getUnsignedDiffInDays(Date beginDate, Date endDate) {
    return Math.abs(getSignedDiffInDays(beginDate, endDate));
}

5

这是我认为最简单和最好的计算方法,也许对你有用。

       try {
            /// String CurrDate=  "10/6/2013";
            /// String PrvvDate=  "10/7/2013";
            Date date1 = null;
            Date date2 = null;
            SimpleDateFormat df = new SimpleDateFormat("M/dd/yyyy");
            date1 = df.parse(CurrDate);
            date2 = df.parse(PrvvDate);
            long diff = Math.abs(date1.getTime() - date2.getTime());
            long diffDays = diff / (24 * 60 * 60 * 1000);


            System.out.println(diffDays);

        } catch (Exception e1) {
            System.out.println("exception " + e1);
        }

@PareshMayani 只是在检查日志cat。 - Rishi Gautam

4

简而言之

ChronoUnit.DAYS.between( 
    LocalDate.parse( "1999-12-28" ) , 
    LocalDate.parse( "12/31/1999" , DateTimeFormatter.ofPattern( "MM/dd/yyyy" ) ) 
)

详情

其他答案已经过时。最早版本的Java中捆绑的旧日期时间类被证明设计不良,令人困惑和麻烦。请避免使用它们。

java.time

Joda-Time项目作为旧类的替代品非常成功。这些类为Java 8及更高版本内置的java.time框架提供了灵感。

许多java.time功能被反向移植到Java 6和7中的ThreeTen-Backport,并在ThreeTenABP中进一步适用于Android。

LocalDate

LocalDate类表示仅包含日期值而不包括时间和时区的值。

解析字符串

如果您的输入字符串采用标准的ISO 8601格式,则LocalDate类可以直接解析该字符串。

LocalDate start = LocalDate.parse( "1999-12-28" );

如果不是ISO 8601格式,请使用DateTimeFormatter定义格式化模式。
String input = "12/31/1999";
DateTimeFormatter formatter = DateTimeFormatter.ofPattern( "MM/dd/yyyy" );
LocalDate stop = LocalDate.parse( input , formatter );

通过ChronoUnit计算经过的天数

现在获取两个LocalDate对象之间经过的天数。ChronoUnit枚举可以计算经过的时间。

long totalDays = ChronoUnit.DAYS.between( start , stop ) ; 

如果您不熟悉Java枚举,了解它们比大多数其他编程语言中的传统枚举更强大和有用。请参阅Enum类文档,Oracle教程Wikipedia以了解更多信息。


关于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更多


java.time.LocalDate 在 Android 不受支持。 - Mahdi Astanei
1
@MahdiAstanei 请再读一下我关于ThreeTenABP Android库的第三段。它非常值得添加到您的应用程序中,因为旧的日期时间类确实很糟糕。 - Basil Bourque

3

最好和最简单的方法是这样做

  public int getDays(String begin) throws ParseException {
     long MILLIS_PER_DAY = 24 * 60 * 60 * 1000;
     SimpleDateFormat dateFormat = new SimpleDateFormat("dd-MM-yyyy", Locale.ENGLISH);

    long begin = dateFormat.parse(begin).getTime();
    long end = new Date().getTime(); // 2nd date want to compare
    long diff = (end - begin) / (MILLIS_PER_DAY);
    return (int) diff;
}

3

从Sam Quest的答案中得到的正确方法只适用于第一个日期早于第二个日期的情况。此外,如果两个日期在同一天内,它将返回1。

对我来说,这是最好的解决方案。就像大多数其他解决方案一样,由于错误的夏令时偏移量,它仍会在一年中的两天显示不正确的结果。

private final static long MILLISECS_PER_DAY = 24 * 60 * 60 * 1000;

long calculateDeltaInDays(Calendar a, Calendar b) {

    // Optional: avoid cloning objects if it is the same day
    if(a.get(Calendar.ERA) == b.get(Calendar.ERA) 
            && a.get(Calendar.YEAR) == b.get(Calendar.YEAR)
            && a.get(Calendar.DAY_OF_YEAR) == b.get(Calendar.DAY_OF_YEAR)) {
        return 0;
    }
    Calendar a2 = (Calendar) a.clone();
    Calendar b2 = (Calendar) b.clone();
    a2.set(Calendar.HOUR_OF_DAY, 0);
    a2.set(Calendar.MINUTE, 0);
    a2.set(Calendar.SECOND, 0);
    a2.set(Calendar.MILLISECOND, 0);
    b2.set(Calendar.HOUR_OF_DAY, 0);
    b2.set(Calendar.MINUTE, 0);
    b2.set(Calendar.SECOND, 0);
    b2.set(Calendar.MILLISECOND, 0);
    long diff = a2.getTimeInMillis() - b2.getTimeInMillis();
    long days = diff / MILLISECS_PER_DAY;
    return Math.abs(days);
}

2
所有这些解决方案都存在两个问题之一。 由于舍入误差,闰日和秒等问题,或者由于需要循环遍历两个未知日期之间的天数,因此解决方案并不完全准确。
该解决方案解决了第一个问题,并将第二个问题改进了大约365倍,如果您知道最大范围,那么改进会更好。
/**
 * @param thisDate
 * @param thatDate
 * @param maxDays
 *            set to -1 to not set a max
 * @returns number of days covered between thisDate and thatDate, inclusive, i.e., counting both
 *          thisDate and thatDate as an entire day. Will short out if the number of days exceeds
 *          or meets maxDays
 */
public static int daysCoveredByDates(Date thisDate, Date thatDate, int maxDays) {
    //Check inputs
    if (thisDate == null || thatDate == null) {
        return -1;
    }

    //Set calendar objects
    Calendar startCal = Calendar.getInstance();
    Calendar endCal = Calendar.getInstance();
    if (thisDate.before(thatDate)) {
        startCal.setTime(thisDate);
        endCal.setTime(thatDate);
    }
    else {
        startCal.setTime(thatDate);
        endCal.setTime(thisDate);
    }

    //Get years and dates of our times.
    int startYear = startCal.get(Calendar.YEAR);
    int endYear = endCal.get(Calendar.YEAR);
    int startDay = startCal.get(Calendar.DAY_OF_YEAR);
    int endDay = endCal.get(Calendar.DAY_OF_YEAR);

    //Calculate the number of days between dates.  Add up each year going by until we catch up to endDate.
    while (startYear < endYear && maxDays >= 0 && endDay - startDay + 1 < maxDays) {
        endDay += startCal.getActualMaximum(Calendar.DAY_OF_YEAR); //adds the number of days in the year startDate is currently in
        ++startYear;
        startCal.set(Calendar.YEAR, startYear); //reup the year
    }
    int days = endDay - startDay + 1;

    //Honor the maximum, if set
    if (maxDays >= 0) {
        days = Math.min(days, maxDays);
    }
    return days;
}

如果你需要计算两个日期之间的天数(不包括后面那一天),只需在看到 endDay - startDay + 1 时去掉 + 1 即可。

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